Compare commits

...

48 Commits

Author SHA1 Message Date
7bbdeacc5e 🔖 1.0.0.a8 2023-11-22 16:11:57 +08:00
dependabot[bot]
782792e455 ⬆️ Bump nonebot-plugin-orm from 0.5.1 to 0.6.0 (#203) 2023-11-22 08:11:15 +00:00
dependabot[bot]
bd10549b4c ⬆️ Bump ruff from 0.1.5 to 0.1.6 (#202) 2023-11-22 08:02:43 +00:00
dependabot[bot]
035e6d4782 ⬆️ Bump nonebot-plugin-alconna from 0.33.3 to 0.33.6 (#201) 2023-11-22 08:02:33 +00:00
003e6619d8 iorank 指令不再去尝试更新数据 2023-11-22 15:58:55 +08:00
c0fa92df30 🚨 fix Incompatible overrides 2023-11-22 15:57:04 +08:00
7cdb0f3547 为 IO Rank 添加重试机制 2023-11-22 15:49:33 +08:00
b773fb44a1 ️ 为 IO 添加缓存 2023-11-22 13:22:18 +08:00
c75c6b73bd 🙈 更新.gitignore 2023-11-22 13:02:17 +08:00
67782c3156 添加依赖 aiocache 2023-11-22 13:01:41 +08:00
1e02858913 💥 🗃️ 将 pydantic 模型序列化后再存数据库 2023-11-21 20:47:56 +08:00
60605d0dca 🐛 修复 IO Z段位 不显示glicko和rd的bug 2023-11-21 13:58:39 +08:00
0d589450bd 将处理过程中的 dataclass 换成 pydantic 2023-11-21 00:50:32 +08:00
dependabot[bot]
2f144acf0c ⬆️ Bump nonebot-adapter-satori from 0.7.0 to 0.8.0 (#200)
Bumps [nonebot-adapter-satori](https://github.com/nonebot/adapter-satori) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/nonebot/adapter-satori/releases)
- [Commits](https://github.com/nonebot/adapter-satori/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: nonebot-adapter-satori
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-11-17 01:49:57 +08:00
87e6a544a2 🔖 1.0.0.a7 2023-11-16 21:34:41 +08:00
74db1931fd 🐛 修复在多个无效参数时 Alconna 自动回复的bug 2023-11-16 21:33:24 +08:00
1ca6d1f86a 使用 zoneinfo 处理时区,并优化数据库查询逻辑 2023-11-16 16:27:06 +08:00
dependabot[bot]
7361789245 ⬆️ Bump nonebot-plugin-orm from 0.5.0 to 0.5.1 (#199) 2023-11-15 15:44:10 +00:00
fe69d8d2fe 🔖 1.0.0.a6 2023-11-15 14:37:41 +08:00
2737119865 🐛 修复 茶服 命令参数设置错误的bug 2023-11-15 14:36:54 +08:00
34a654b5df 🐛 修复只输入主命令时不发送帮助提示的bug 2023-11-15 14:34:27 +08:00
f9f39618a1 💚 修复Release CI 2023-11-15 14:02:22 +08:00
81a3c9cb79 🔖 1.0.0.a5 2023-11-15 11:44:23 +08:00
4a15c45e0a 💚 修复Release CI 2023-11-15 11:44:23 +08:00
e90ad53ee6 🐛 修复在事件响应器异常退出后 Recorder 继续执行的bug 2023-11-15 11:37:12 +08:00
0c968be163 避免 Alconna 在 ParamsUnmatched 时自动回复
🎨 将通用 handle 封装一下
2023-11-15 11:04:09 +08:00
bfadac4f79 添加配置项 请求超时时间 2023-11-15 00:43:12 +08:00
89f09cd66c 🔥 删除配置项 db_url 2023-11-15 00:37:04 +08:00
777703362e 🎨 先断开连接再删文件 2023-11-15 00:28:54 +08:00
ea5308877c 🎨 🔥 去除一个没什么用的函数 2023-11-15 00:26:55 +08:00
3cc93925a6 🐛 修复迁移旧数据库时拿错config字段的bug 2023-11-15 00:14:03 +08:00
e0bd0a9252 🐛 修复 io user info 解析错误的bug 2023-11-15 00:07:05 +08:00
d31ce48a18 💚 修复Release CI 2023-11-14 13:15:51 +08:00
7da38e0346 🔖 Release 1.0.0.a4 2023-11-14 13:11:46 +08:00
呵呵です
84368a16c3 👷 更改 dependabot 推送分支 2023-11-14 13:44:58 +08:00
6a10ede5ba 👷 更新Release CI 2023-11-14 12:56:06 +08:00
4c205e516f 🔥 去除命令解析失败时发送提醒 2023-11-14 12:54:42 +08:00
c1feccd608 🔖 1.0.0.a3 2023-11-14 09:07:08 +08:00
f27d7b4440 🐛 修复 io record 解析错误的bug 2023-11-14 09:02:22 +08:00
7fa498de48 👽️ io record 添加一个pentas字段 2023-11-14 09:00:35 +08:00
ff7c5847a3 🐛 修复 io record 解析错误的bug 2023-11-14 08:59:44 +08:00
呵呵です
b75c42987d 🔀 Merge pull request #198 from shoucandanghehe/dev
🔖 1.0.0.a2
2023-11-14 01:46:27 +08:00
0daea46eea 🔖 1.0.0.a2 2023-11-14 01:45:40 +08:00
ccb0bae32c 🐛 修复 io record 解析错误的bug 2023-11-14 01:44:56 +08:00
dependabot[bot]
63afcf9ad1 ⬆️ Bump pandas from 2.1.1 to 2.1.3 (#195) 2023-11-13 17:42:40 +00:00
dependabot[bot]
6d3d2a38b0 ⬆️ Bump nonebot-plugin-alconna from 0.32.0 to 0.33.3 (#196) 2023-11-13 17:42:30 +00:00
1b7e51b773 🔖 1.0.0.a1.post1 2023-11-14 00:47:52 +08:00
c09d10b799 🐛 修复排行榜 Users.League 的部分字段为 None 时 错误处理的错误 2023-11-14 00:47:52 +08:00
29 changed files with 631 additions and 324 deletions

View File

@@ -7,6 +7,6 @@ version: 2
updates:
- package-ecosystem: "pip" # See documentation for possible values
directory: "/" # Location of package manifests
target-branch: "dev"
target-branch: "main"
schedule:
interval: "daily"

View File

@@ -8,18 +8,42 @@ on:
jobs:
release:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v3
- name: Install poetry
run: pipx install poetry
shell: bash
- uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install Poetry
cache: "poetry"
- run: poetry install
shell: bash
- name: Get Version
id: version
run: |
pip install poetry
- name: Build
shell: bash
run: |
poetry install
poetry env use python
poetry publish --build -u ${{ secrets.USERNAME }} -p ${{ secrets.PASSWORD }} -n
echo "VERSION=$(poetry version -s)" >> $GITHUB_OUTPUT
echo "TAG_VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
echo "TAG_NAME=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
- name: Check Version
if: steps.version.outputs.VERSION != steps.version.outputs.TAG_VERSION
run: exit 1
- name: Build Package
run: poetry build
- name: Publish Package to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
- name: Publish Package to GitHub Release
run: gh release create ${{ steps.version.outputs.TAG_NAME }} dist/*.tar.gz dist/*.whl -t "🔖 ${{ steps.version.outputs.TAG_NAME }}" --generate-notes
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

2
.gitignore vendored
View File

@@ -5,6 +5,6 @@ Untitled*
*copy*
.vscode
*dev*
*cache*
*_cache*
*backup*
*.pyc

View File

@@ -11,4 +11,4 @@ CACHE_PATH: Path = get_cache_dir('nonebot_plugin_tetris_stats')
class Config(BaseModel):
"""配置类"""
db_url: str = 'sqlite://data/nonebot_plugin_tetris_stats/data.db'
tetris_req_timeout: float = 30.0

View File

@@ -64,7 +64,7 @@ def upgrade(name: str = '') -> None:
if name:
return
try:
db_path = Path(config.db_path)
db_path = Path(config.db_url)
except AttributeError:
db_path = Path('data/nonebot_plugin_tetris_stats/data.db')
if db_path.exists() is False:
@@ -84,7 +84,7 @@ def upgrade(name: str = '') -> None:
raise RuntimeError('nonebot_plugin_tetris_stats: 请先安装 0.4.4 版本完成迁移之后再升级')
logger.info('nonebot_plugin_tetris_stats: 发现来自老版本的数据, 正在迁移...')
migrate_old_data(connection)
db_path.unlink()
db_path.unlink()
def downgrade(name: str = '') -> None:

View File

@@ -0,0 +1,112 @@
"""Recreate HistoricalData
迁移 ID: 9f6582279ce2
父迁移: 9cd1647db502
创建时间: 2023-11-21 08:35:50.393246
"""
from __future__ import annotations
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
from sqlalchemy.dialects import sqlite
from nonebot_plugin_tetris_stats.db.models import PydanticType
revision: str = '9f6582279ce2'
down_revision: str | Sequence[str] | None = '9cd1647db502'
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
def upgrade(name: str = '') -> None:
if name:
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_historicaldata_command_type')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_historicaldata_game_platform')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_historicaldata_source_account')
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_historicaldata_source_type')
op.drop_table('nonebot_plugin_tetris_stats_historicaldata')
op.create_table(
'nonebot_plugin_tetris_stats_historicaldata',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('trigger_time', sa.DateTime(), nullable=False),
sa.Column('bot_platform', sa.String(length=32), nullable=True),
sa.Column('bot_account', sa.String(), nullable=True),
sa.Column('source_type', sa.String(length=32), nullable=True),
sa.Column('source_account', sa.String(), nullable=True),
sa.Column('message', sa.PickleType(), nullable=True),
sa.Column('game_platform', sa.String(length=32), nullable=False),
sa.Column('command_type', sa.String(length=16), nullable=False),
sa.Column('command_args', sa.JSON(), nullable=False),
sa.Column('game_user', PydanticType(), nullable=False),
sa.Column('processed_data', PydanticType(), nullable=False),
sa.Column('finish_time', sa.DateTime(), nullable=False),
sa.PrimaryKeyConstraint('id', name=op.f('pk_nonebot_plugin_tetris_stats_historicaldata')),
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_command_type'), ['command_type'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_game_platform'), ['game_platform'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_source_account'), ['source_account'], unique=False
)
batch_op.create_index(
batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_source_type'), ['source_type'], unique=False
)
# ### end Alembic commands ###
def downgrade(name: str = '') -> None:
if name:
return
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_source_type'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_source_account'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_game_platform'))
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_command_type'))
op.drop_table('nonebot_plugin_tetris_stats_historicaldata')
op.create_table(
'nonebot_plugin_tetris_stats_historicaldata',
sa.Column('id', sa.INTEGER(), nullable=False),
sa.Column('trigger_time', sa.DATETIME(), nullable=False),
sa.Column('bot_platform', sa.VARCHAR(length=32), nullable=True),
sa.Column('bot_account', sa.VARCHAR(), nullable=True),
sa.Column('source_type', sa.VARCHAR(length=32), nullable=True),
sa.Column('source_account', sa.VARCHAR(), nullable=True),
sa.Column('message', sa.BLOB(), nullable=True),
sa.Column('game_platform', sa.VARCHAR(length=32), nullable=False),
sa.Column('command_type', sa.VARCHAR(length=16), nullable=False),
sa.Column('command_args', sqlite.JSON(), nullable=False),
sa.Column('game_user', sa.BLOB(), nullable=False),
sa.Column('processed_data', sa.BLOB(), nullable=False),
sa.Column('finish_time', sa.DATETIME(), nullable=False),
sa.PrimaryKeyConstraint('id', name='pk_nonebot_plugin_tetris_stats_historicaldata'),
)
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
batch_op.create_index(
'ix_nonebot_plugin_tetris_stats_historicaldata_source_type', ['source_type'], unique=False
)
batch_op.create_index(
'ix_nonebot_plugin_tetris_stats_historicaldata_source_account', ['source_account'], unique=False
)
batch_op.create_index(
'ix_nonebot_plugin_tetris_stats_historicaldata_game_platform', ['game_platform'], unique=False
)
batch_op.create_index(
'ix_nonebot_plugin_tetris_stats_historicaldata_command_type', ['command_type'], unique=False
)
# ### end Alembic commands ###

View File

@@ -1,14 +1,32 @@
from datetime import datetime
from typing import Any
from nonebot.adapters import Message
from nonebot_plugin_orm import Model
from sqlalchemy import JSON, DateTime, PickleType, String
from pydantic import BaseModel
from sqlalchemy import JSON, DateTime, Dialect, PickleType, String, TypeDecorator
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column
from ..game_data_processor import ProcessedData, User
from ..game_data_processor.schemas import BaseProcessedData, BaseUser
from ..utils.typing import CommandType, GameType
class PydanticType(TypeDecorator):
impl = JSON
def process_bind_param(self, value: Any | None, dialect: Dialect) -> str: # noqa: ANN401
# 将 Pydantic 模型实例转换为 JSON
if isinstance(value, BaseModel):
return value.json()
raise TypeError
def process_result_value(self, value: Any | None, dialect: Dialect) -> BaseModel: # noqa: ANN401
# 将 JSON 转换回 Pydantic 模型实例
if isinstance(value, str | bytes):
return BaseModel.parse_raw(value)
raise TypeError
class Bind(MappedAsDataclass, Model):
id: Mapped[int] = mapped_column(init=False, primary_key=True)
chat_platform: Mapped[str] = mapped_column(String(32), index=True)
@@ -28,6 +46,6 @@ class HistoricalData(MappedAsDataclass, Model):
game_platform: Mapped[GameType] = mapped_column(String(32), index=True, init=False)
command_type: Mapped[CommandType] = mapped_column(String(16), index=True, init=False)
command_args: Mapped[list[str]] = mapped_column(JSON, init=False)
game_user: Mapped[User] = mapped_column(PickleType, init=False)
processed_data: Mapped[ProcessedData] = mapped_column(PickleType, init=False)
game_user: Mapped[BaseUser] = mapped_column(PydanticType, init=False)
processed_data: Mapped[BaseProcessedData] = mapped_column(PydanticType, init=False)
finish_time: Mapped[datetime] = mapped_column(DateTime, init=False)

View File

@@ -1,26 +1,16 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import UTC, datetime
from typing import Any
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import AlcMatches, AlconnaMatcher
from ..utils.exception import MessageFormatError
from ..utils.recorder import Recorder
from ..utils.typing import CommandType, GameType
@dataclass
class User:
"""游戏用户"""
@dataclass
class RawResponse:
"""原始请求数据"""
@dataclass
class ProcessedData:
"""处理/验证后的数据"""
from ..utils.recorder import Recorder # noqa: E402 避免循环导入
from .schemas import BaseProcessedData as ProcessedData
from .schemas import BaseRawResponse as RawResponse
from .schemas import BaseUser as User
class Processor(ABC):
@@ -65,6 +55,9 @@ class Processor(ABC):
def __del__(self) -> None:
finish_time = datetime.now(tz=UTC)
if Recorder.is_error_event(self.event_id):
Recorder.del_error_event(self.event_id)
return
historical_data = Recorder.get_historical_data(self.event_id)
historical_data.game_platform = self.game_platform
historical_data.command_type = self.command_type
@@ -75,6 +68,24 @@ class Processor(ABC):
Recorder.update_historical_data(self.event_id, historical_data)
def add_default_handlers(matcher: type[AlconnaMatcher]) -> None:
@matcher.handle()
async def _(matcher: Matcher, account: MessageFormatError):
await matcher.finish(str(account))
@matcher.handle()
async def _(matcher: Matcher, matches: AlcMatches):
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"查看帮助'
)
@matcher.handle()
async def _(matcher: Matcher, other: Any): # noqa: ANN401
await matcher.finish()
from . import ( # noqa: F401, E402
io_data_processor,
top_data_processor,

View File

@@ -1,21 +1,23 @@
from datetime import timedelta
from datetime import UTC, datetime, timedelta
from zoneinfo import ZoneInfo
from arclet.alconna import Alconna, Arg, ArgFlag, Args, CommandMeta, Option
from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, Option
from nonebot.adapters import Bot, Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import AlcMatches, At, on_alconna
from nonebot_plugin_alconna import At, on_alconna
from nonebot_plugin_orm import get_session
from sqlalchemy import select
from sqlalchemy import func, select
from ...db import query_bind_info
from ...utils.exception import MessageFormatError, NeedCatchError
from ...utils.exception import NeedCatchError
from ...utils.metrics import get_metrics
from ...utils.platform import get_platform
from ...utils.typing import Me
from .. import add_default_handlers
from ..constant import BIND_COMMAND, QUERY_COMMAND
from .constant import GAME_TYPE
from .model import IORank
from .processor import Processor, User, check_rank_data, identify_user_info
from .processor import Processor, User, identify_user_info
from .typing import Rank
alc = on_alconna(
@@ -65,6 +67,7 @@ alc = on_alconna(
dest='rank',
help_text='查询 IO 段位信息',
),
Arg('other', AllParam, flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL]),
meta=CommandMeta(
description='查询 TETR.IO 的信息',
example='io绑定scdhh\nio查我\niorankx',
@@ -130,21 +133,32 @@ async def _(event: Event, matcher: Matcher, account: User):
@alc.assign('rank')
async def _(event: Event, matcher: Matcher, rank: Rank):
async def _(matcher: Matcher, rank: Rank):
if rank == 'z':
await matcher.finish('暂不支持查询未知段位')
try:
await check_rank_data()
except NeedCatchError as e:
await matcher.finish(str(f'段位信息获取失败\n{e}'))
async with get_session() as session:
data = (
await session.scalars(select(IORank).where(IORank.rank == rank).order_by(IORank.id.desc()).limit(5))
).all()
latest_data = data[0]
message = f'{rank.upper()} 段 分数线 {latest_data.tr_line:.2f} TR, {latest_data.player_count} 名玩家\n'
if len(data) > 1:
message += f'对比 {(latest_data.create_time-data[-1].create_time).total_seconds()/3600:.2f} 小时前趋势: {f"{difference:.2f}" if (difference:=latest_data.tr_line-data[-1].tr_line) > 0 else f"{-difference:.2f}" if difference < 0 else ""}'
latest_data = (
await session.scalars(select(IORank).where(IORank.rank == rank).order_by(IORank.id.desc()).limit(1))
).one()
compare_data = (
await session.scalars(
select(IORank)
.where(IORank.rank == rank)
.order_by(
func.abs(
func.julianday(IORank.create_time)
- func.julianday(latest_data.create_time - timedelta(hours=24))
)
)
.limit(1)
)
).one()
message = ''
if (datetime.now(UTC) - latest_data.create_time) > timedelta(hours=7):
message += 'Warning: 数据超过7小时未更新, 请联系Bot主人查看后台\n'
message += f'{rank.upper()} 段 分数线 {latest_data.tr_line:.2f} TR, {latest_data.player_count} 名玩家\n'
if compare_data.id != latest_data.id:
message += f'对比 {(latest_data.create_time-compare_data.create_time).total_seconds()/3600:.2f} 小时前趋势: {f"{difference:.2f}" if (difference:=latest_data.tr_line-compare_data.tr_line) > 0 else f"{-difference:.2f}" if difference < 0 else ""}'
else:
message += '暂无对比数据'
avg = get_metrics(pps=latest_data.avg_pps, apm=latest_data.avg_apm, vs=latest_data.avg_vs)
@@ -169,19 +183,9 @@ async def _(event: Event, matcher: Matcher, rank: Rank):
f'APM: {latest_data.high_apm[1]} By: {latest_data.high_apm[0]["name"].upper()}\n'
f'ADPM: {max_vs.adpm} ( {max_vs.vs}vs ) By: {latest_data.high_vs[0]["name"].upper()}\n'
'\n'
f'数据更新时间: {(latest_data.create_time+timedelta(hours=8)).strftime("%Y-%m-%d %H:%M:%S")}'
f'数据更新时间: {latest_data.create_time.replace(tzinfo=ZoneInfo("UTC")).astimezone(ZoneInfo("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")}'
)
await matcher.finish(message)
@alc.handle()
async def _(matcher: Matcher, account: MessageFormatError):
await matcher.finish(str(account))
@alc.handle()
async def _(matcher: Matcher, matches: AlcMatches):
if matches.head_matched:
await matcher.finish(
f'{matches.error_info!r}\n' if matches.error_info is not None else '' + '输入"io --help"查看帮助'
)
add_default_handlers(alc)

View File

@@ -0,0 +1,28 @@
from datetime import UTC, datetime
from aiocache import Cache as ACache # type: ignore[import-untyped]
from nonebot.log import logger
from pydantic import parse_raw_as
from ...utils.request import Request
from .schemas.base import FailedModel, SuccessModel
class Cache:
cache = ACache(ACache.MEMORY)
@classmethod
async def get(cls, url: str) -> bytes:
cached_data = await cls.cache.get(url)
if cached_data is None:
response_data = await Request.request(url)
parsed_data: SuccessModel | FailedModel = parse_raw_as(SuccessModel | FailedModel, response_data) # type: ignore[arg-type]
if isinstance(parsed_data, SuccessModel):
await cls.cache.add(
url,
response_data,
(parsed_data.cache.cached_until - datetime.now(UTC)).total_seconds(),
)
return response_data
logger.debug(f'{url}: Cache hit!')
return cached_data

View File

@@ -1,6 +1,5 @@
from collections import defaultdict
from collections.abc import Callable
from dataclasses import asdict, dataclass
from datetime import UTC, datetime, timedelta
from math import floor
from re import match
@@ -14,23 +13,20 @@ from sqlalchemy import select
from ...db import create_or_update_bind
from ...utils.exception import MessageFormatError, RequestError, WhatTheFuckError
from ...utils.request import Request, splice_url
from ...utils.request import splice_url
from ...utils.retry import retry
from ...utils.typing import GameType
from .. import ProcessedData as ProcessedDataMeta
from .. import Processor as ProcessorMeta
from .. import RawResponse as RawResponseMeta
from .. import User as UserMeta
from .cache import Cache
from .constant import BASE_URL, GAME_TYPE, RANK_PERCENTILE
from .model import IORank
from .schemas.league_all import FailedModel as LeagueAllFailed
from .schemas.league_all import LeagueAll
from .schemas.league_all import User as LeagueAllUser
from .schemas.league_all import ValidUser as LeagueAllUser
from .schemas.response import ProcessedData, RawResponse
from .schemas.user import User
from .schemas.user_info import FailedModel as InfoFailed
from .schemas.user_info import (
NeverPlayedLeague,
NeverRatedLeague,
UserInfo,
)
from .schemas.user_info import NeverPlayedLeague, NeverRatedLeague, UserInfo
from .schemas.user_info import SuccessModel as InfoSuccess
from .schemas.user_records import FailedModel as RecordsFailed
from .schemas.user_records import SoloRecord, UserRecords
@@ -40,24 +36,6 @@ from .typing import Rank
driver = get_driver()
@dataclass
class User(UserMeta):
ID: str | None = None
name: str | None = None
@dataclass
class RawResponse(RawResponseMeta):
user_info: bytes | None = None
user_records: bytes | None = None
@dataclass
class ProcessedData(ProcessedDataMeta):
user_info: InfoSuccess | None = None
user_records: RecordsSuccess | None = None
def identify_user_info(info: str) -> User | MessageFormatError:
if match(r'^[a-f0-9]{24}$', info):
return User(ID=info)
@@ -113,7 +91,7 @@ class Processor(ProcessorMeta):
async def get_user_info(self) -> InfoSuccess:
"""获取用户数据"""
if self.processed_data.user_info is None:
self.raw_response.user_info = await Request.request(
self.raw_response.user_info = await Cache.get(
splice_url([BASE_URL, 'users/', f'{self.user.ID or self.user.name}'])
)
user_info: UserInfo = parse_raw_as(UserInfo, self.raw_response.user_info) # type: ignore[arg-type]
@@ -125,20 +103,10 @@ class Processor(ProcessorMeta):
async def get_user_records(self) -> RecordsSuccess:
"""获取Solo数据"""
if self.processed_data.user_records is None:
self.raw_response.user_records = await Request.request(
splice_url(
[
BASE_URL,
'users/',
f'{self.user.ID or self.user.name}/',
'records',
]
)
)
user_records: UserRecords = parse_raw_as(
UserRecords, # type: ignore[arg-type]
self.raw_response.user_records,
self.raw_response.user_records = await Cache.get(
splice_url([BASE_URL, 'users/', f'{self.user.ID or self.user.name}/', 'records'])
)
user_records: UserRecords = parse_raw_as(UserRecords, self.raw_response.user_records) # type: ignore[arg-type]
if isinstance(user_records, RecordsFailed):
raise RequestError(f'用户Solo数据请求错误:\n{user_records.error}')
self.processed_data.user_records = user_records
@@ -155,12 +123,13 @@ class Processor(ProcessorMeta):
else:
if isinstance(league, NeverRatedLeague):
ret_message += f'用户 {user_name} 暂未完成定级赛, 最近十场的数据:'
elif 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})'
)
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 )"
@@ -185,13 +154,11 @@ class Processor(ProcessorMeta):
@scheduler.scheduled_job('cron', hour='0,6,12,18', minute=0)
@retry(exception_type=RequestError, delay=timedelta(minutes=15))
async def get_io_rank_data() -> None:
league_all: LeagueAll = parse_raw_as(
LeagueAll, # type: ignore[arg-type]
await Request.request(splice_url([BASE_URL, 'users/lists/league/all'])),
)
league_all: LeagueAll = parse_raw_as(LeagueAll, await Cache.get(splice_url([BASE_URL, 'users/lists/league/all']))) # type: ignore[arg-type]
if isinstance(league_all, LeagueAllFailed):
raise RequestError(f'用户Solo数据请求错误:\n{league_all.error}')
raise RequestError(f'排行榜数据请求错误:\n{league_all.error}')
def pps(user: LeagueAllUser) -> float:
return user.league.pps
@@ -214,9 +181,9 @@ async def get_io_rank_data() -> None:
sort: Callable[[list[LeagueAllUser], Callable[[LeagueAllUser], float]], LeagueAllUser],
) -> tuple[dict[str, str], float]:
user = sort(users, field)
return asdict(User(ID=user.id, name=user.username)), field(user)
return User(ID=user.id, name=user.username).dict(), field(user)
users = league_all.data.users
users = [i for i in league_all.data.users if isinstance(i, LeagueAllUser)]
rank_to_users: defaultdict[Rank, list[LeagueAllUser]] = defaultdict(list)
for i in users:
rank_to_users[i.league.rank].append(i)
@@ -247,8 +214,8 @@ async def get_io_rank_data() -> None:
@driver.on_startup
async def check_rank_data() -> None:
async def _() -> None:
async with get_session() as session:
latest_time = await session.scalar(select(IORank.create_time).order_by(IORank.id.desc()).limit(1))
if latest_time is None or datetime.now(tz=UTC) - latest_time.replace(tzinfo=UTC) > timedelta(hours=6):
await get_io_rank_data()
if latest_time is None or datetime.now(tz=UTC) - latest_time.replace(tzinfo=UTC) > timedelta(hours=6):
await get_io_rank_data()

View File

@@ -7,7 +7,7 @@ from .base import SuccessModel as BaseSuccessModel
class SuccessModel(BaseSuccessModel):
class Data(BaseModel):
class User(BaseModel):
class ValidUser(BaseModel):
class League(BaseModel):
gamesplayed: int
gameswon: int
@@ -30,10 +30,34 @@ class SuccessModel(BaseSuccessModel):
verified: bool
country: str | None
users: list[User]
class InvalidUser(BaseModel):
class League(BaseModel):
gamesplayed: int
gameswon: int
rating: float
glicko: float | None
rd: float | None
rank: Rank
bestrank: Rank
apm: float | None
pps: float | None
vs: float | None
decaying: bool
id: str = Field(..., alias='_id')
username: str
role: str
xp: float
league: League
supporter: bool
verified: bool
country: str | None
users: list[ValidUser | InvalidUser]
data: Data
LeagueAll = SuccessModel | FailedModel
User = SuccessModel.Data.User
ValidUser = SuccessModel.Data.ValidUser
InvalidUser = SuccessModel.Data.InvalidUser

View File

@@ -0,0 +1,14 @@
from ... import ProcessedData as ProcessedDataMeta
from ... import RawResponse as RawResponseMeta
from .user_info import SuccessModel as InfoSuccess
from .user_records import SuccessModel as RecordsSuccess
class RawResponse(RawResponseMeta):
user_info: bytes | None = None
user_records: bytes | None = None
class ProcessedData(ProcessedDataMeta):
user_info: InfoSuccess | None = None
user_records: RecordsSuccess | None = None

View File

@@ -0,0 +1,12 @@
from ...schemas import BaseUser
class User(BaseUser):
ID: str | None = None
name: str | None = None
@property
def unique_identifier(self) -> str:
if self.ID is None:
raise ValueError('不完整的User!')
return self.ID

View File

@@ -29,9 +29,9 @@ class SuccessModel(BaseSuccessModel):
prev_at: Literal[-1]
percentile: Literal[-1]
percentile_rank: Literal['z']
apm: None
pps: None
vs: None
apm: None = Field(None)
pps: None = Field(None)
vs: None = Field(None)
decaying: bool
class NeverRatedLeague(BaseModel):
@@ -111,7 +111,7 @@ class SuccessModel(BaseSuccessModel):
Ignore this field if the user is not a supporter."""
bio: str | None
connections: Connections
friend_count: int
friend_count: int | None
distinguishment: Distinguishment | None
user: User

View File

@@ -19,6 +19,7 @@ class EndContext(BaseModel):
doubles: int
triples: int
quads: int
pentas: int | None
realtspins: int
minitspins: int
minitspinsingles: int
@@ -32,7 +33,7 @@ class EndContext(BaseModel):
class Garbage(BaseModel):
sent: int
received: int
attack: int
attack: int | None
cleared: int
class Finesse(BaseModel):
@@ -45,18 +46,18 @@ class EndContext(BaseModel):
level_lines: int
level_lines_needed: int
inputs: int
holds: int
holds: int | None
time: Time
score: int
zenlevel: int
zenprogress: int
level: int
combo: int
currentcombopower: int # WTF
currentcombopower: int | None # WTF
topcombo: int
btb: int
topbtb: int
currentbtbchainpower: int | None # WTF * 2 40l 里有 但是 blitz 没有
currentbtbchainpower: int | None # WTF * 2
tspins: int
piecesplaced: int
clears: Clears

View File

@@ -0,0 +1,25 @@
from abc import ABC, abstractmethod
from pydantic import BaseModel
class BaseUser(ABC, BaseModel):
"""游戏用户"""
def __eq__(self, __value: object) -> bool:
if isinstance(__value, BaseUser):
return self.unique_identifier == __value.unique_identifier
return False
@property
@abstractmethod
def unique_identifier(self) -> str:
raise NotImplementedError
class BaseRawResponse(BaseModel):
"""原始请求数据"""
class BaseProcessedData(BaseModel):
"""处理/验证后的数据"""

View File

@@ -1,13 +1,14 @@
from arclet.alconna import Alconna, Arg, ArgFlag, Args, CommandMeta, Option
from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, Option
from nonebot.adapters import Bot, Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import AlcMatches, At, on_alconna
from nonebot_plugin_alconna import At, on_alconna
from nonebot_plugin_orm import get_session
from ...db import query_bind_info
from ...utils.exception import MessageFormatError, NeedCatchError
from ...utils.exception import NeedCatchError
from ...utils.platform import get_platform
from ...utils.typing import Me
from .. import add_default_handlers
from ..constant import BIND_COMMAND, QUERY_COMMAND
from .constant import GAME_TYPE
from .processor import Processor, User, identify_user_info
@@ -51,6 +52,7 @@ alc = on_alconna(
dest='query',
help_text='查询 TOP 游戏信息',
),
Arg('other', AllParam, flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL]),
meta=CommandMeta(
description='查询 TetrisOnline波兰服 的信息',
example='top绑定scdhh\ntop查我',
@@ -113,14 +115,4 @@ async def _(event: Event, matcher: Matcher, account: User):
await matcher.finish(str(e))
@alc.handle()
async def _(matcher: Matcher, account: MessageFormatError):
await matcher.finish(str(account))
@alc.handle()
async def _(matcher: Matcher, matches: AlcMatches):
if matches.head_matched:
await matcher.finish(
f'{matches.error_info!r}\n' if matches.error_info is not None else '' + '输入"top --help"查看帮助'
)
add_default_handlers(alc)

View File

@@ -13,26 +13,18 @@ from ...db import create_or_update_bind
from ...utils.exception import MessageFormatError, RequestError
from ...utils.request import Request, splice_url
from ...utils.typing import GameType
from .. import ProcessedData as ProcessedDataMeta
from .. import Processor as ProcessorMeta
from .. import RawResponse as RawResponseMeta
from .. import User as UserMeta
from ..schemas import BaseUser
from .constant import BASE_URL, GAME_TYPE
from .schemas.response import ProcessedData, RawResponse
@dataclass
class User(UserMeta):
class User(BaseUser):
name: str
@dataclass
class RawResponse(RawResponseMeta):
user_profile: bytes | None = None
@dataclass
class ProcessedData(ProcessedDataMeta):
user_profile: str | None = None
@property
def unique_identifier(self) -> str:
return self.name
@dataclass

View File

@@ -0,0 +1,9 @@
from ...schemas import BaseProcessedData, BaseRawResponse
class RawResponse(BaseRawResponse):
user_profile: bytes | None = None
class ProcessedData(BaseProcessedData):
user_profile: str | None = None

View File

@@ -1,13 +1,14 @@
from arclet.alconna import Alconna, Arg, ArgFlag, Args, CommandMeta, Option
from arclet.alconna import Alconna, AllParam, Arg, ArgFlag, Args, CommandMeta, Option
from nonebot.adapters import Bot, Event
from nonebot.matcher import Matcher
from nonebot_plugin_alconna import AlcMatches, At, on_alconna
from nonebot_plugin_alconna import At, on_alconna
from nonebot_plugin_orm import get_session
from ...db import query_bind_info
from ...utils.exception import MessageFormatError, NeedCatchError
from ...utils.exception import NeedCatchError
from ...utils.platform import get_platform
from ...utils.typing import Me
from .. import add_default_handlers
from ..constant import BIND_COMMAND, QUERY_COMMAND
from .constant import GAME_TYPE
from .processor import Processor, User, identify_user_info
@@ -52,6 +53,7 @@ alc = on_alconna(
dest='query',
help_text='查询 茶服 游戏信息',
),
Arg('other', AllParam, flags=[ArgFlag.HIDDEN, ArgFlag.OPTIONAL]),
meta=CommandMeta(
description='查询 TetrisOnline茶服 的信息',
example='茶服查我',
@@ -138,14 +140,4 @@ async def _(event: Event, matcher: Matcher, account: User):
await matcher.finish(str(e))
@alc.handle()
async def _(matcher: Matcher, account: MessageFormatError):
await matcher.finish(str(account))
@alc.handle()
async def _(matcher: Matcher, matches: AlcMatches):
if matches.head_matched:
await matcher.finish(
f'{matches.error_info!r}\n' if matches.error_info is not None else '' + '输入"茶服 --help"查看帮助'
)
add_default_handlers(alc)

View File

@@ -1,6 +1,5 @@
from dataclasses import dataclass
from re import match
from typing import Any
from urllib.parse import urlencode
from nonebot_plugin_orm import get_session
@@ -10,32 +9,24 @@ from ...db import create_or_update_bind
from ...utils.exception import MessageFormatError, RequestError
from ...utils.request import Request, splice_url
from ...utils.typing import GameType
from .. import ProcessedData as ProcessedDataMeta
from .. import Processor as ProcessorMeta
from .. import RawResponse as RawResponseMeta
from .. import User as UserMeta
from ..schemas import BaseUser
from .constant import BASE_URL, GAME_TYPE
from .schemas.response import ProcessedData, RawResponse
from .schemas.user_info import SuccessModel as InfoSuccess
from .schemas.user_info import UserInfo
from .schemas.user_profile import UserProfile
@dataclass
class User(UserMeta):
class User(BaseUser):
teaid: str | None = None
name: str | None = None
@dataclass
class RawResponse(RawResponseMeta):
user_profile: dict[frozenset[tuple[str, Any]], bytes]
user_info: bytes | None = None
@dataclass
class ProcessedData(ProcessedDataMeta):
user_profile: dict[frozenset[tuple[str, Any]], UserProfile]
user_info: InfoSuccess | None = None
@property
def unique_identifier(self) -> str:
if self.teaid is None:
raise ValueError('不完整的User!')
return self.teaid
@dataclass
@@ -135,23 +126,23 @@ class Processor(ProcessorMeta):
self.processed_data.user_info = user_info
return self.processed_data.user_info
async def get_user_profile(self, other_parameter: dict[str, Any] | None = None) -> UserProfile:
async def get_user_profile(self, other_parameter: dict[str, str | bytes] | None = None) -> UserProfile:
"""获取用户数据"""
if other_parameter is None:
other_parameter = {}
fset = frozenset(other_parameter.items())
if self.processed_data.user_profile.get(fset) is None:
self.raw_response.user_profile[fset] = await Request.request(
params = urlencode(dict(sorted(other_parameter.items())))
if self.processed_data.user_profile.get(params) is None:
self.raw_response.user_profile[params] = await Request.request(
splice_url(
[
BASE_URL,
'getProfile',
f'?{urlencode({"id":self.user.teaid or self.user.name},**other_parameter)}',
f'?{urlencode({"id":self.user.teaid or self.user.name,**other_parameter})}',
]
)
)
self.processed_data.user_profile[fset] = UserProfile.parse_raw(self.raw_response.user_profile[fset])
return self.processed_data.user_profile[fset]
self.processed_data.user_profile[params] = UserProfile.parse_raw(self.raw_response.user_profile[params])
return self.processed_data.user_profile[params]
async def get_game_data(self) -> GameData | None:
"""获取游戏数据"""

View File

@@ -0,0 +1,13 @@
from ...schemas import BaseProcessedData, BaseRawResponse
from .user_info import SuccessModel as InfoSuccess
from .user_profile import UserProfile
class RawResponse(BaseRawResponse):
user_profile: dict[str, bytes]
user_info: bytes | None = None
class ProcessedData(BaseProcessedData):
user_profile: dict[str, UserProfile]
user_info: InfoSuccess | None = None

View File

@@ -1,6 +1,7 @@
import sys
from os import environ
from platform import system
from re import sub
from nonebot import get_driver
from nonebot.log import logger
@@ -33,12 +34,12 @@ class BrowserManager:
raise ImportError('加载失败, Windows 必须设置 FASTAPI_RELOAD=false 才能正常运行 playwright')
logger.info('开始 安装/更新 playwright 浏览器')
environ['PLAYWRIGHT_DOWNLOAD_HOST'] = 'https://npmmirror.com/mirrors/playwright/'
if cls._handle_error(cls._call_playwright(['', 'install', 'firefox'])):
if cls._call_playwright(['', 'install', 'firefox']):
logger.success('安装/更新 playwright 浏览器成功')
else:
logger.warning('playwright 浏览器 安装/更新 失败, 尝试使用原始仓库下载')
del environ['PLAYWRIGHT_DOWNLOAD_HOST']
if cls._handle_error(cls._call_playwright(['', 'install', 'firefox'])):
if cls._call_playwright(['', 'install', 'firefox']):
logger.success('安装/更新 playwright 浏览器成功')
else:
logger.error('安装/更新 playwright 浏览器失败')
@@ -52,26 +53,20 @@ class BrowserManager:
logger.success('playwright 启动成功')
@classmethod
def _call_playwright(cls, argv: list[str]) -> BaseException:
def _call_playwright(cls, argv: list[str]) -> bool:
"""等价于调用 playwright 的命令行程序"""
argv_backup = sys.argv.copy()
from re import sub
sys.argv[0] = sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0])
sys.argv = argv
try:
main()
except BaseException as e: # noqa: BLE001 不在这里处理 playwright 的异常
return e
except SystemExit as e:
return e.code == 0
except BaseException: # noqa: BLE001
return False
finally:
sys.argv = argv_backup
return SystemExit(0)
@classmethod
def _handle_error(cls, error: BaseException) -> bool:
if isinstance(error, SystemExit) and error.code == 0:
return True
return False
return True
@classmethod
async def _start_browser(cls) -> Browser:

View File

@@ -15,6 +15,7 @@ driver = get_driver()
class Recorder:
matchers: ClassVar[set[type[Matcher]]] = set()
historical_data: ClassVar[dict[int, tuple[HistoricalData, bool]]] = {}
error_event: ClassVar[set[int]] = set()
@classmethod
def create_historical_data(cls, event_id: int, historical_data: HistoricalData) -> None:
@@ -32,17 +33,27 @@ class Recorder:
@classmethod
async def save_historical_data(cls, event_id: int) -> None:
if event_id not in cls.historical_data:
raise KeyError
historical_data, completed = cls.historical_data.pop(event_id)
historical_data, completed = cls.del_historical_data(event_id)
if completed:
async with get_session() as session:
session.add(historical_data)
await session.commit()
@classmethod
def del_historical_data(cls, event_id: int) -> None:
cls.historical_data.pop(event_id)
def del_historical_data(cls, event_id: int) -> tuple[HistoricalData, bool]:
return cls.historical_data.pop(event_id)
@classmethod
def add_error_event(cls, event_id: int) -> None:
cls.error_event.add(event_id)
@classmethod
def del_error_event(cls, event_id: int) -> None:
cls.error_event.remove(event_id)
@classmethod
def is_error_event(cls, event_id: int) -> bool:
return event_id in cls.error_event
@driver.on_startup
@@ -73,7 +84,9 @@ def _(bot: Bot, event: Event, matcher: Matcher):
@run_postprocessor
async def _(event: Event, matcher: Matcher, exception: Exception | None):
if isinstance(matcher, tuple(Recorder.matchers)):
event_id = id(event)
if exception is not None:
Recorder.del_historical_data(id(event))
Recorder.add_error_event(event_id)
Recorder.del_historical_data(event_id)
else:
await Recorder.save_historical_data(id(event))
await Recorder.save_historical_data(event_id)

View File

@@ -7,11 +7,12 @@ from nonebot.log import logger
from playwright.async_api import Response
from ujson import JSONDecodeError, dumps, loads
from ..config.config import CACHE_PATH
from ..config.config import CACHE_PATH, Config
from .browser import BrowserManager
from .exception import RequestError
driver = get_driver()
config = Config.parse_obj(driver.config)
@driver.on_startup
@@ -115,7 +116,7 @@ class Request:
async def request(cls, url: str, *, is_json: bool = True) -> bytes:
"""请求api"""
try:
async with AsyncClient(cookies=cls._cookies) as session:
async with AsyncClient(cookies=cls._cookies, timeout=config.tetris_req_timeout) as session:
response = await session.get(url, headers=cls._headers)
if is_json:
loads(response.content)

View File

@@ -0,0 +1,37 @@
from asyncio import sleep
from collections.abc import Awaitable, Callable
from datetime import timedelta
from functools import wraps
from typing import TypeVar, cast
from nonebot.log import logger
T = TypeVar('T')
def retry(
max_attempts: int = 3,
exception_type: type[BaseException] | tuple[type[BaseException], ...] = Exception,
delay: timedelta | None = None,
) -> Callable[[Callable[..., Awaitable[T]]], Callable[..., Awaitable[T]]]:
def decorator(func: Callable[..., Awaitable[T]]) -> Callable[..., Awaitable[T]]:
@wraps(func)
async def wrapper(*args, **kwargs) -> T: # noqa: ANN002, ANN003
attempts = 0
while attempts < max_attempts + 1:
try:
return await func(*args, **kwargs)
except exception_type as e: # noqa: PERF203
logger.exception(e)
attempts += 1
if attempts <= max_attempts:
if delay is not None:
await sleep(delay.total_seconds())
logger.debug(f'Retrying: {func.__name__} ({attempts}/{max_attempts})')
continue
raise
raise RuntimeError('Unexpectedly reached the end of the retry loop')
return cast(Callable[..., Awaitable[T]], wrapper)
return decorator

219
poetry.lock generated
View File

@@ -1,5 +1,21 @@
# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand.
[[package]]
name = "aiocache"
version = "0.12.2"
description = "multi backend asyncio cache"
optional = false
python-versions = "*"
files = [
{file = "aiocache-0.12.2-py2.py3-none-any.whl", hash = "sha256:9b6fa30634ab0bfc3ecc44928a91ff07c6ea16d27d55469636b296ebc6eb5918"},
{file = "aiocache-0.12.2.tar.gz", hash = "sha256:b41c9a145b050a5dcbae1599f847db6dd445193b1f3bd172d8e0fe0cb9e96684"},
]
[package.extras]
memcached = ["aiomcache (>=0.5.2)"]
msgpack = ["msgpack (>=0.5.5)"]
redis = ["redis (>=4.2.0)"]
[[package]]
name = "aiofiles"
version = "23.2.1"
@@ -197,6 +213,16 @@ typing-extensions = ">=4.5.0"
[package.extras]
all = ["email-validator (>=2.0.0)", "httpx (>=0.23.0)", "itsdangerous (>=1.1.0)", "jinja2 (>=2.11.2)", "orjson (>=3.2.1)", "pydantic-extra-types (>=2.0.0)", "pydantic-settings (>=2.0.0)", "python-multipart (>=0.0.5)", "pyyaml (>=5.3.1)", "ujson (>=4.0.1,!=4.0.2,!=4.1.0,!=4.2.0,!=4.3.0,!=5.0.0,!=5.1.0)", "uvicorn[standard] (>=0.12.0)"]
[[package]]
name = "fleep"
version = "1.0.1"
description = "File format determination library"
optional = false
python-versions = ">=3.1"
files = [
{file = "fleep-1.0.1.tar.gz", hash = "sha256:c8f62b258ee5364d7f6c1ed1f3f278e99020fc3f0a60a24ad1e10846e31d104c"},
]
[[package]]
name = "greenlet"
version = "3.0.0"
@@ -854,13 +880,13 @@ typing-extensions = ">=4.0.0,<5.0.0"
[[package]]
name = "nonebot-adapter-satori"
version = "0.7.0"
version = "0.8.0"
description = "Satori Protocol Adapter for Nonebot2"
optional = false
python-versions = ">=3.8"
files = [
{file = "nonebot_adapter_satori-0.7.0-py3-none-any.whl", hash = "sha256:14a6e846c0f4e46077c28ca403e5f16d18162b63b6aa4eb68f9e6f52153ab138"},
{file = "nonebot_adapter_satori-0.7.0.tar.gz", hash = "sha256:0f62b87182359beaf477e77b12a0ca665273de7a24833695514ff18817b23618"},
{file = "nonebot_adapter_satori-0.8.0-py3-none-any.whl", hash = "sha256:25d9726a961b73b6900a4dba3a8ec4f5d228a6cb96078b542379c9d6c9d3cefe"},
{file = "nonebot_adapter_satori-0.8.0.tar.gz", hash = "sha256:267c5b708ec2dd77b405d3ec779e817506e6d6914cc06688b529b43fe4288727"},
]
[package.dependencies]
@@ -868,18 +894,19 @@ nonebot2 = ">=2.1.0"
[[package]]
name = "nonebot-plugin-alconna"
version = "0.32.0"
version = "0.33.6"
description = "Alconna Adapter for Nonebot"
optional = false
python-versions = ">=3.8"
files = [
{file = "nonebot_plugin_alconna-0.32.0-py3-none-any.whl", hash = "sha256:3126b845ebbb4118a46513977a6ce91783a4c6158febba03a4b00ac565a6756b"},
{file = "nonebot_plugin_alconna-0.32.0.tar.gz", hash = "sha256:ab53fadec56f2a5fcf75adb95a5312db48620217c3b3dcd90878ab0394062ff8"},
{file = "nonebot_plugin_alconna-0.33.6-py3-none-any.whl", hash = "sha256:89989dd6dc1bccd8a1603159b57e9e96da438eb0bbbdbbcd2bfcf747594e12c3"},
{file = "nonebot_plugin_alconna-0.33.6.tar.gz", hash = "sha256:54e761ef621c8285cd9ea2f81324f9f52f6fb6292d3ddb87387dbdcaeaa8dc1b"},
]
[package.dependencies]
arclet-alconna = ">=1.7.31,<2.0.0"
arclet-alconna-tools = ">=0.6.7,<0.7.0"
fleep = ">=1.0.1"
nepattern = ">=0.5.14,<0.6.0"
nonebot2 = ">=2.1.0"
@@ -915,13 +942,13 @@ typing-extensions = ">=4.0.0"
[[package]]
name = "nonebot-plugin-orm"
version = "0.5.0"
version = "0.6.0"
description = "SQLAlchemy ORM support for nonebot"
optional = false
python-versions = "<4.0,>=3.8"
files = [
{file = "nonebot_plugin_orm-0.5.0-py3-none-any.whl", hash = "sha256:e8d0e3b11c592e8d7cef3d635f500a5f7bf45b042eb225907c40dd23e4e2b2bd"},
{file = "nonebot_plugin_orm-0.5.0.tar.gz", hash = "sha256:2bf51bdc30ee3dc797c5032dc92dbcf34d7eb42d09dd6b527228033807d33a56"},
{file = "nonebot_plugin_orm-0.6.0-py3-none-any.whl", hash = "sha256:482f35d102749eda93cd635608ec79e81718d080a80fa325c24d8fa7316f7d83"},
{file = "nonebot_plugin_orm-0.6.0.tar.gz", hash = "sha256:e1b4db52da60b53f33eac95bb4b9875d7000d71f47ec6a034aa908adde41c33e"},
]
[package.dependencies]
@@ -932,7 +959,7 @@ importlib-resources = {version = ">=6.1,<7.0", markers = "python_version < \"3.1
nonebot-plugin-localstore = ">=0.5,<1.0"
nonebot2 = ">=2.1,<3.0"
sqlalchemy = ">=2.0,<3.0"
typing-extensions = {version = ">=4.8,<5.0", markers = "python_version < \"3.12\""}
typing-extensions = {version = ">=4.8,<5.0", markers = "python_version < \"3.11\""}
[package.extras]
aiomysql = ["aiomysql (>=0.2,<1.0)"]
@@ -976,43 +1003,47 @@ websockets = ["websockets (>=10.0)"]
[[package]]
name = "numpy"
version = "1.26.1"
version = "1.26.2"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = "<3.13,>=3.9"
python-versions = ">=3.9"
files = [
{file = "numpy-1.26.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:82e871307a6331b5f09efda3c22e03c095d957f04bf6bc1804f30048d0e5e7af"},
{file = "numpy-1.26.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cdd9ec98f0063d93baeb01aad472a1a0840dee302842a2746a7a8e92968f9575"},
{file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d78f269e0c4fd365fc2992c00353e4530d274ba68f15e968d8bc3c69ce5f5244"},
{file = "numpy-1.26.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ab9163ca8aeb7fd32fe93866490654d2f7dda4e61bc6297bf72ce07fdc02f67"},
{file = "numpy-1.26.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:78ca54b2f9daffa5f323f34cdf21e1d9779a54073f0018a3094ab907938331a2"},
{file = "numpy-1.26.1-cp310-cp310-win32.whl", hash = "sha256:d1cfc92db6af1fd37a7bb58e55c8383b4aa1ba23d012bdbba26b4bcca45ac297"},
{file = "numpy-1.26.1-cp310-cp310-win_amd64.whl", hash = "sha256:d2984cb6caaf05294b8466966627e80bf6c7afd273279077679cb010acb0e5ab"},
{file = "numpy-1.26.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cd7837b2b734ca72959a1caf3309457a318c934abef7a43a14bb984e574bbb9a"},
{file = "numpy-1.26.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1c59c046c31a43310ad0199d6299e59f57a289e22f0f36951ced1c9eac3665b9"},
{file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d58e8c51a7cf43090d124d5073bc29ab2755822181fcad978b12e144e5e5a4b3"},
{file = "numpy-1.26.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6081aed64714a18c72b168a9276095ef9155dd7888b9e74b5987808f0dd0a974"},
{file = "numpy-1.26.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:97e5d6a9f0702c2863aaabf19f0d1b6c2628fbe476438ce0b5ce06e83085064c"},
{file = "numpy-1.26.1-cp311-cp311-win32.whl", hash = "sha256:b9d45d1dbb9de84894cc50efece5b09939752a2d75aab3a8b0cef6f3a35ecd6b"},
{file = "numpy-1.26.1-cp311-cp311-win_amd64.whl", hash = "sha256:3649d566e2fc067597125428db15d60eb42a4e0897fc48d28cb75dc2e0454e53"},
{file = "numpy-1.26.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:1d1bd82d539607951cac963388534da3b7ea0e18b149a53cf883d8f699178c0f"},
{file = "numpy-1.26.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:afd5ced4e5a96dac6725daeb5242a35494243f2239244fad10a90ce58b071d24"},
{file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a03fb25610ef560a6201ff06df4f8105292ba56e7cdd196ea350d123fc32e24e"},
{file = "numpy-1.26.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcfaf015b79d1f9f9c9fd0731a907407dc3e45769262d657d754c3a028586124"},
{file = "numpy-1.26.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e509cbc488c735b43b5ffea175235cec24bbc57b227ef1acc691725beb230d1c"},
{file = "numpy-1.26.1-cp312-cp312-win32.whl", hash = "sha256:af22f3d8e228d84d1c0c44c1fbdeb80f97a15a0abe4f080960393a00db733b66"},
{file = "numpy-1.26.1-cp312-cp312-win_amd64.whl", hash = "sha256:9f42284ebf91bdf32fafac29d29d4c07e5e9d1af862ea73686581773ef9e73a7"},
{file = "numpy-1.26.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:bb894accfd16b867d8643fc2ba6c8617c78ba2828051e9a69511644ce86ce83e"},
{file = "numpy-1.26.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e44ccb93f30c75dfc0c3aa3ce38f33486a75ec9abadabd4e59f114994a9c4617"},
{file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9696aa2e35cc41e398a6d42d147cf326f8f9d81befcb399bc1ed7ffea339b64e"},
{file = "numpy-1.26.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5b411040beead47a228bde3b2241100454a6abde9df139ed087bd73fc0a4908"},
{file = "numpy-1.26.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:1e11668d6f756ca5ef534b5be8653d16c5352cbb210a5c2a79ff288e937010d5"},
{file = "numpy-1.26.1-cp39-cp39-win32.whl", hash = "sha256:d1d2c6b7dd618c41e202c59c1413ef9b2c8e8a15f5039e344af64195459e3104"},
{file = "numpy-1.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:59227c981d43425ca5e5c01094d59eb14e8772ce6975d4b2fc1e106a833d5ae2"},
{file = "numpy-1.26.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:06934e1a22c54636a059215d6da99e23286424f316fddd979f5071093b648668"},
{file = "numpy-1.26.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:76ff661a867d9272cd2a99eed002470f46dbe0943a5ffd140f49be84f68ffc42"},
{file = "numpy-1.26.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:6965888d65d2848e8768824ca8288db0a81263c1efccec881cb35a0d805fcd2f"},
{file = "numpy-1.26.1.tar.gz", hash = "sha256:c8c6c72d4a9f831f328efb1312642a1cafafaa88981d9ab76368d50d07d93cbe"},
{file = "numpy-1.26.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:3703fc9258a4a122d17043e57b35e5ef1c5a5837c3db8be396c82e04c1cf9b0f"},
{file = "numpy-1.26.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cc392fdcbd21d4be6ae1bb4475a03ce3b025cd49a9be5345d76d7585aea69440"},
{file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:36340109af8da8805d8851ef1d74761b3b88e81a9bd80b290bbfed61bd2b4f75"},
{file = "numpy-1.26.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bcc008217145b3d77abd3e4d5ef586e3bdfba8fe17940769f8aa09b99e856c00"},
{file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:3ced40d4e9e18242f70dd02d739e44698df3dcb010d31f495ff00a31ef6014fe"},
{file = "numpy-1.26.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b272d4cecc32c9e19911891446b72e986157e6a1809b7b56518b4f3755267523"},
{file = "numpy-1.26.2-cp310-cp310-win32.whl", hash = "sha256:22f8fc02fdbc829e7a8c578dd8d2e15a9074b630d4da29cda483337e300e3ee9"},
{file = "numpy-1.26.2-cp310-cp310-win_amd64.whl", hash = "sha256:26c9d33f8e8b846d5a65dd068c14e04018d05533b348d9eaeef6c1bd787f9919"},
{file = "numpy-1.26.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b96e7b9c624ef3ae2ae0e04fa9b460f6b9f17ad8b4bec6d7756510f1f6c0c841"},
{file = "numpy-1.26.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:aa18428111fb9a591d7a9cc1b48150097ba6a7e8299fb56bdf574df650e7d1f1"},
{file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:06fa1ed84aa60ea6ef9f91ba57b5ed963c3729534e6e54055fc151fad0423f0a"},
{file = "numpy-1.26.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca5482c3dbdd051bcd1fce8034603d6ebfc125a7bd59f55b40d8f5d246832b"},
{file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:854ab91a2906ef29dc3925a064fcd365c7b4da743f84b123002f6139bcb3f8a7"},
{file = "numpy-1.26.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:f43740ab089277d403aa07567be138fc2a89d4d9892d113b76153e0e412409f8"},
{file = "numpy-1.26.2-cp311-cp311-win32.whl", hash = "sha256:a2bbc29fcb1771cd7b7425f98b05307776a6baf43035d3b80c4b0f29e9545186"},
{file = "numpy-1.26.2-cp311-cp311-win_amd64.whl", hash = "sha256:2b3fca8a5b00184828d12b073af4d0fc5fdd94b1632c2477526f6bd7842d700d"},
{file = "numpy-1.26.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a4cd6ed4a339c21f1d1b0fdf13426cb3b284555c27ac2f156dfdaaa7e16bfab0"},
{file = "numpy-1.26.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5d5244aabd6ed7f312268b9247be47343a654ebea52a60f002dc70c769048e75"},
{file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6a3cdb4d9c70e6b8c0814239ead47da00934666f668426fc6e94cce869e13fd7"},
{file = "numpy-1.26.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa317b2325f7aa0a9471663e6093c210cb2ae9c0ad824732b307d2c51983d5b6"},
{file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:174a8880739c16c925799c018f3f55b8130c1f7c8e75ab0a6fa9d41cab092fd6"},
{file = "numpy-1.26.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f79b231bf5c16b1f39c7f4875e1ded36abee1591e98742b05d8a0fb55d8a3eec"},
{file = "numpy-1.26.2-cp312-cp312-win32.whl", hash = "sha256:4a06263321dfd3598cacb252f51e521a8cb4b6df471bb12a7ee5cbab20ea9167"},
{file = "numpy-1.26.2-cp312-cp312-win_amd64.whl", hash = "sha256:b04f5dc6b3efdaab541f7857351aac359e6ae3c126e2edb376929bd3b7f92d7e"},
{file = "numpy-1.26.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4eb8df4bf8d3d90d091e0146f6c28492b0be84da3e409ebef54349f71ed271ef"},
{file = "numpy-1.26.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1a13860fdcd95de7cf58bd6f8bc5a5ef81c0b0625eb2c9a783948847abbef2c2"},
{file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:64308ebc366a8ed63fd0bf426b6a9468060962f1a4339ab1074c228fa6ade8e3"},
{file = "numpy-1.26.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baf8aab04a2c0e859da118f0b38617e5ee65d75b83795055fb66c0d5e9e9b818"},
{file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d73a3abcac238250091b11caef9ad12413dab01669511779bc9b29261dd50210"},
{file = "numpy-1.26.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:b361d369fc7e5e1714cf827b731ca32bff8d411212fccd29ad98ad622449cc36"},
{file = "numpy-1.26.2-cp39-cp39-win32.whl", hash = "sha256:bd3f0091e845164a20bd5a326860c840fe2af79fa12e0469a12768a3ec578d80"},
{file = "numpy-1.26.2-cp39-cp39-win_amd64.whl", hash = "sha256:2beef57fb031dcc0dc8fa4fe297a742027b954949cabb52a2a376c144e5e6060"},
{file = "numpy-1.26.2-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:1cc3d5029a30fb5f06704ad6b23b35e11309491c999838c31f124fee32107c79"},
{file = "numpy-1.26.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94cc3c222bb9fb5a12e334d0479b97bb2df446fbe622b470928f5284ffca3f8d"},
{file = "numpy-1.26.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:fe6b44fb8fcdf7eda4ef4461b97b3f63c466b27ab151bec2366db8b197387841"},
{file = "numpy-1.26.2.tar.gz", hash = "sha256:f65738447676ab5777f11e6bbbdb8ce11b785e105f690bc45966574816b6d3ea"},
]
[[package]]
@@ -1028,50 +1059,50 @@ files = [
[[package]]
name = "pandas"
version = "2.1.1"
version = "2.1.3"
description = "Powerful data structures for data analysis, time series, and statistics"
optional = false
python-versions = ">=3.9"
files = [
{file = "pandas-2.1.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:58d997dbee0d4b64f3cb881a24f918b5f25dd64ddf31f467bb9b67ae4c63a1e4"},
{file = "pandas-2.1.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:02304e11582c5d090e5a52aec726f31fe3f42895d6bfc1f28738f9b64b6f0614"},
{file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffa8f0966de2c22de408d0e322db2faed6f6e74265aa0856f3824813cf124363"},
{file = "pandas-2.1.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c1f84c144dee086fe4f04a472b5cd51e680f061adf75c1ae4fc3a9275560f8f4"},
{file = "pandas-2.1.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:75ce97667d06d69396d72be074f0556698c7f662029322027c226fd7a26965cb"},
{file = "pandas-2.1.1-cp310-cp310-win_amd64.whl", hash = "sha256:4c3f32fd7c4dccd035f71734df39231ac1a6ff95e8bdab8d891167197b7018d2"},
{file = "pandas-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9e2959720b70e106bb1d8b6eadd8ecd7c8e99ccdbe03ee03260877184bb2877d"},
{file = "pandas-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:25e8474a8eb258e391e30c288eecec565bfed3e026f312b0cbd709a63906b6f8"},
{file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b8bd1685556f3374520466998929bade3076aeae77c3e67ada5ed2b90b4de7f0"},
{file = "pandas-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc3657869c7902810f32bd072f0740487f9e030c1a3ab03e0af093db35a9d14e"},
{file = "pandas-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:05674536bd477af36aa2effd4ec8f71b92234ce0cc174de34fd21e2ee99adbc2"},
{file = "pandas-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:b407381258a667df49d58a1b637be33e514b07f9285feb27769cedb3ab3d0b3a"},
{file = "pandas-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c747793c4e9dcece7bb20156179529898abf505fe32cb40c4052107a3c620b49"},
{file = "pandas-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:3bcad1e6fb34b727b016775bea407311f7721db87e5b409e6542f4546a4951ea"},
{file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5ec7740f9ccb90aec64edd71434711f58ee0ea7f5ed4ac48be11cfa9abf7317"},
{file = "pandas-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:29deb61de5a8a93bdd033df328441a79fcf8dd3c12d5ed0b41a395eef9cd76f0"},
{file = "pandas-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:4f99bebf19b7e03cf80a4e770a3e65eee9dd4e2679039f542d7c1ace7b7b1daa"},
{file = "pandas-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:84e7e910096416adec68075dc87b986ff202920fb8704e6d9c8c9897fe7332d6"},
{file = "pandas-2.1.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:366da7b0e540d1b908886d4feb3d951f2f1e572e655c1160f5fde28ad4abb750"},
{file = "pandas-2.1.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:9e50e72b667415a816ac27dfcfe686dc5a0b02202e06196b943d54c4f9c7693e"},
{file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc1ab6a25da197f03ebe6d8fa17273126120874386b4ac11c1d687df288542dd"},
{file = "pandas-2.1.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0dbfea0dd3901ad4ce2306575c54348d98499c95be01b8d885a2737fe4d7a98"},
{file = "pandas-2.1.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0489b0e6aa3d907e909aef92975edae89b1ee1654db5eafb9be633b0124abe97"},
{file = "pandas-2.1.1-cp39-cp39-win_amd64.whl", hash = "sha256:4cdb0fab0400c2cb46dafcf1a0fe084c8bb2480a1fa8d81e19d15e12e6d4ded2"},
{file = "pandas-2.1.1.tar.gz", hash = "sha256:fecb198dc389429be557cde50a2d46da8434a17fe37d7d41ff102e3987fd947b"},
{file = "pandas-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:acf08a73b5022b479c1be155d4988b72f3020f308f7a87c527702c5f8966d34f"},
{file = "pandas-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3cc4469ff0cf9aa3a005870cb49ab8969942b7156e0a46cc3f5abd6b11051dfb"},
{file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:35172bff95f598cc5866c047f43c7f4df2c893acd8e10e6653a4b792ed7f19bb"},
{file = "pandas-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59dfe0e65a2f3988e940224e2a70932edc964df79f3356e5f2997c7d63e758b4"},
{file = "pandas-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:0296a66200dee556850d99b24c54c7dfa53a3264b1ca6f440e42bad424caea03"},
{file = "pandas-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:465571472267a2d6e00657900afadbe6097c8e1dc43746917db4dfc862e8863e"},
{file = "pandas-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04d4c58e1f112a74689da707be31cf689db086949c71828ef5da86727cfe3f82"},
{file = "pandas-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:7fa2ad4ff196768ae63a33f8062e6838efed3a319cf938fdf8b95e956c813042"},
{file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4441ac94a2a2613e3982e502ccec3bdedefe871e8cea54b8775992485c5660ef"},
{file = "pandas-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5ded6ff28abbf0ea7689f251754d3789e1edb0c4d0d91028f0b980598418a58"},
{file = "pandas-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:fca5680368a5139d4920ae3dc993eb5106d49f814ff24018b64d8850a52c6ed2"},
{file = "pandas-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:de21e12bf1511190fc1e9ebc067f14ca09fccfb189a813b38d63211d54832f5f"},
{file = "pandas-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a5d53c725832e5f1645e7674989f4c106e4b7249c1d57549023ed5462d73b140"},
{file = "pandas-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7cf4cf26042476e39394f1f86868d25b265ff787c9b2f0d367280f11afbdee6d"},
{file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72c84ec1b1d8e5efcbff5312abe92bfb9d5b558f11e0cf077f5496c4f4a3c99e"},
{file = "pandas-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1f539e113739a3e0cc15176bf1231a553db0239bfa47a2c870283fd93ba4f683"},
{file = "pandas-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fc77309da3b55732059e484a1efc0897f6149183c522390772d3561f9bf96c00"},
{file = "pandas-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:08637041279b8981a062899da0ef47828df52a1838204d2b3761fbd3e9fcb549"},
{file = "pandas-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b99c4e51ef2ed98f69099c72c75ec904dd610eb41a32847c4fcbc1a975f2d2b8"},
{file = "pandas-2.1.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f7ea8ae8004de0381a2376662c0505bb0a4f679f4c61fbfd122aa3d1b0e5f09d"},
{file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd76d67ca2d48f56e2db45833cf9d58f548f97f61eecd3fdc74268417632b8a"},
{file = "pandas-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1329dbe93a880a3d7893149979caa82d6ba64a25e471682637f846d9dbc10dd2"},
{file = "pandas-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:321ecdb117bf0f16c339cc6d5c9a06063854f12d4d9bc422a84bb2ed3207380a"},
{file = "pandas-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:11a771450f36cebf2a4c9dbd3a19dfa8c46c4b905a3ea09dc8e556626060fe71"},
{file = "pandas-2.1.3.tar.gz", hash = "sha256:22929f84bca106921917eb73c1521317ddd0a4c71b395bcf767a106e3494209f"},
]
[package.dependencies]
numpy = [
{version = ">=1.22.4", markers = "python_version < \"3.11\""},
{version = ">=1.23.2", markers = "python_version == \"3.11\""},
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
{version = ">=1.22.4,<2", markers = "python_version < \"3.11\""},
{version = ">=1.23.2,<2", markers = "python_version == \"3.11\""},
{version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""},
]
python-dateutil = ">=2.8.2"
pytz = ">=2020.1"
tzdata = ">=2022.1"
[package.extras]
all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"]
all = ["PyQt5 (>=5.15.6)", "SQLAlchemy (>=1.4.36)", "beautifulsoup4 (>=4.11.1)", "bottleneck (>=1.3.4)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=0.8.1)", "fsspec (>=2022.05.0)", "gcsfs (>=2022.05.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.8.0)", "matplotlib (>=3.6.1)", "numba (>=0.55.2)", "numexpr (>=2.8.0)", "odfpy (>=1.4.1)", "openpyxl (>=3.0.10)", "pandas-gbq (>=0.17.5)", "psycopg2 (>=2.9.3)", "pyarrow (>=7.0.0)", "pymysql (>=1.0.2)", "pyreadstat (>=1.1.5)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "pyxlsb (>=1.0.9)", "qtpy (>=2.2.0)", "s3fs (>=2022.05.0)", "scipy (>=1.8.1)", "tables (>=3.7.0)", "tabulate (>=0.8.10)", "xarray (>=2022.03.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.3)", "zstandard (>=0.17.0)"]
aws = ["s3fs (>=2022.05.0)"]
clipboard = ["PyQt5 (>=5.15.6)", "qtpy (>=2.2.0)"]
compression = ["zstandard (>=0.17.0)"]
@@ -1091,7 +1122,7 @@ plot = ["matplotlib (>=3.6.1)"]
postgresql = ["SQLAlchemy (>=1.4.36)", "psycopg2 (>=2.9.3)"]
spss = ["pyreadstat (>=1.1.5)"]
sql-other = ["SQLAlchemy (>=1.4.36)"]
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-asyncio (>=0.17.0)", "pytest-xdist (>=2.2.0)"]
test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"]
xml = ["lxml (>=4.8.0)"]
[[package]]
@@ -1310,28 +1341,28 @@ files = [
[[package]]
name = "ruff"
version = "0.1.5"
version = "0.1.6"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.1.5-py3-none-macosx_10_7_x86_64.whl", hash = "sha256:32d47fc69261c21a4c48916f16ca272bf2f273eb635d91c65d5cd548bf1f3d96"},
{file = "ruff-0.1.5-py3-none-macosx_10_9_x86_64.macosx_11_0_arm64.macosx_10_9_universal2.whl", hash = "sha256:171276c1df6c07fa0597fb946139ced1c2978f4f0b8254f201281729981f3c17"},
{file = "ruff-0.1.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:17ef33cd0bb7316ca65649fc748acc1406dfa4da96a3d0cde6d52f2e866c7b39"},
{file = "ruff-0.1.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:b2c205827b3f8c13b4a432e9585750b93fd907986fe1aec62b2a02cf4401eee6"},
{file = "ruff-0.1.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bb408e3a2ad8f6881d0f2e7ad70cddb3ed9f200eb3517a91a245bbe27101d379"},
{file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:f20dc5e5905ddb407060ca27267c7174f532375c08076d1a953cf7bb016f5a24"},
{file = "ruff-0.1.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:aafb9d2b671ed934998e881e2c0f5845a4295e84e719359c71c39a5363cccc91"},
{file = "ruff-0.1.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a4894dddb476597a0ba4473d72a23151b8b3b0b5f958f2cf4d3f1c572cdb7af7"},
{file = "ruff-0.1.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a00a7ec893f665ed60008c70fe9eeb58d210e6b4d83ec6654a9904871f982a2a"},
{file = "ruff-0.1.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:a8c11206b47f283cbda399a654fd0178d7a389e631f19f51da15cbe631480c5b"},
{file = "ruff-0.1.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fa29e67b3284b9a79b1a85ee66e293a94ac6b7bb068b307a8a373c3d343aa8ec"},
{file = "ruff-0.1.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:9b97fd6da44d6cceb188147b68db69a5741fbc736465b5cea3928fdac0bc1aeb"},
{file = "ruff-0.1.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:721f4b9d3b4161df8dc9f09aa8562e39d14e55a4dbaa451a8e55bdc9590e20f4"},
{file = "ruff-0.1.5-py3-none-win32.whl", hash = "sha256:f80c73bba6bc69e4fdc73b3991db0b546ce641bdcd5b07210b8ad6f64c79f1ab"},
{file = "ruff-0.1.5-py3-none-win_amd64.whl", hash = "sha256:c21fe20ee7d76206d290a76271c1af7a5096bc4c73ab9383ed2ad35f852a0087"},
{file = "ruff-0.1.5-py3-none-win_arm64.whl", hash = "sha256:82bfcb9927e88c1ed50f49ac6c9728dab3ea451212693fe40d08d314663e412f"},
{file = "ruff-0.1.5.tar.gz", hash = "sha256:5cbec0ef2ae1748fb194f420fb03fb2c25c3258c86129af7172ff8f198f125ab"},
{file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl", hash = "sha256:88b8cdf6abf98130991cbc9f6438f35f6e8d41a02622cc5ee130a02a0ed28703"},
{file = "ruff-0.1.6-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:5c549ed437680b6105a1299d2cd30e4964211606eeb48a0ff7a93ef70b902248"},
{file = "ruff-0.1.6-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cf5f701062e294f2167e66d11b092bba7af6a057668ed618a9253e1e90cfd76"},
{file = "ruff-0.1.6-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:05991ee20d4ac4bb78385360c684e4b417edd971030ab12a4fbd075ff535050e"},
{file = "ruff-0.1.6-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87455a0c1f739b3c069e2f4c43b66479a54dea0276dd5d4d67b091265f6fd1dc"},
{file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:683aa5bdda5a48cb8266fcde8eea2a6af4e5700a392c56ea5fb5f0d4bfdc0240"},
{file = "ruff-0.1.6-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:137852105586dcbf80c1717facb6781555c4e99f520c9c827bd414fac67ddfb6"},
{file = "ruff-0.1.6-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:bd98138a98d48a1c36c394fd6b84cd943ac92a08278aa8ac8c0fdefcf7138f35"},
{file = "ruff-0.1.6-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3a0cd909d25f227ac5c36d4e7e681577275fb74ba3b11d288aff7ec47e3ae745"},
{file = "ruff-0.1.6-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:e8fd1c62a47aa88a02707b5dd20c5ff20d035d634aa74826b42a1da77861b5ff"},
{file = "ruff-0.1.6-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:fd89b45d374935829134a082617954120d7a1470a9f0ec0e7f3ead983edc48cc"},
{file = "ruff-0.1.6-py3-none-musllinux_1_2_i686.whl", hash = "sha256:491262006e92f825b145cd1e52948073c56560243b55fb3b4ecb142f6f0e9543"},
{file = "ruff-0.1.6-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:ea284789861b8b5ca9d5443591a92a397ac183d4351882ab52f6296b4fdd5462"},
{file = "ruff-0.1.6-py3-none-win32.whl", hash = "sha256:1610e14750826dfc207ccbcdd7331b6bd285607d4181df9c1c6ae26646d6848a"},
{file = "ruff-0.1.6-py3-none-win_amd64.whl", hash = "sha256:4558b3e178145491e9bc3b2ee3c4b42f19d19384eaa5c59d10acf6e8f8b57e33"},
{file = "ruff-0.1.6-py3-none-win_arm64.whl", hash = "sha256:03910e81df0d8db0e30050725a5802441c2022ea3ae4fe0609b76081731accbc"},
{file = "ruff-0.1.6.tar.gz", hash = "sha256:1b09f29b16c6ead5ea6b097ef2764b42372aebe363722f1605ecbcd2b9207184"},
]
[[package]]
@@ -2093,4 +2124,4 @@ multidict = ">=4.0"
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "fe5add372ec41e79b27a990ab27afbdcceb625dc81f3ebd20a28e0b8129f6942"
content-hash = "e523f1239c9c0373499c2ee7a9c04a7400334ea94bec5105a8497bdb7da29f98"

View File

@@ -1,6 +1,6 @@
[tool.poetry]
name = 'nonebot-plugin-tetris-stats'
version = '1.0.0.a1'
version = '1.0.0.a8'
description = '一款基于 NoneBot2 的用于查询 Tetris 相关游戏数据的插件'
authors = ['scdhh <wallfjjd@gmail.com>']
readme = 'README.md'
@@ -16,23 +16,24 @@ pandas = '>=1.4.3,<3.0.0'
playwright = '^1.24.1'
ujson = '^5.4.0'
aiofiles = "^23.2.1"
nonebot-plugin-orm = ">=0.1.1,<0.6.0"
nonebot-plugin-orm = ">=0.1.1,<0.7.0"
nonebot-plugin-localstore = "^0.5.1"
httpx = "^0.25.0"
nonebot-plugin-alconna = ">=0.30,<0.33"
nonebot-plugin-alconna = ">=0.30,<0.34"
nonebot-plugin-apscheduler = "^0.3.0"
aiocache = "^0.12.2"
[tool.poetry.group.dev.dependencies]
mypy = '>=0.991,<1.8'
types-ujson = '^5.7.0'
pandas-stubs = '>=1.5.2,<3.0.0'
ruff = '>=0.0.239,<0.1.6'
ruff = '>=0.0.239,<0.1.7'
types-aiofiles = "^23.2.0.0"
nonebot2 = { extras = ["fastapi"], version = "^2.1.1" }
types-lxml = "^2023.3.28"
nonebot-plugin-orm = { extras = ["default"], version = ">=0.3,<0.6" }
nonebot-plugin-orm = { extras = ["default"], version = ">=0.3,<0.7" }
nonebot-adapter-onebot = "^2.3.1"
nonebot-adapter-satori = "^0.7.0"
nonebot-adapter-satori = "^0.8.0"
[tool.poetry.group.debug.dependencies]
objprint = '^0.2.2'