mirror of
https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
synced 2026-03-05 05:36:54 +08:00
Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a57811b0d3 | |||
|
|
7a5170936b | ||
|
|
068c508f57 | ||
|
|
0648ca021b | ||
|
|
65e7fed32b | ||
|
|
fdbb2f3f6e | ||
| 144c223fe9 | |||
|
|
52a6d95434 | ||
| d8255756ca | |||
|
|
13c6d53b6a | ||
| 6493aba7e0 | |||
|
|
b82053be11 | ||
|
|
11bc486420 | ||
|
|
9916902c10 | ||
|
|
e347b41ba6 | ||
|
|
40d0bf06bb | ||
| 026ceaec6c | |||
|
|
e80e1bcda3 | ||
|
|
72e9a2fd87 | ||
|
|
f0b1a1c4f5 | ||
| 0bc3b86820 | |||
|
|
57d0b15242 | ||
|
|
56fe45efcf | ||
| 13f005179f | |||
| 243a8a286d | |||
| 8b7de6745b | |||
| d0fc82d88d |
2
.github/workflows/Test.yml
vendored
2
.github/workflows/Test.yml
vendored
@@ -53,6 +53,6 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Decide whether the needed jobs succeeded or failed
|
- name: Decide whether the needed jobs succeeded or failed
|
||||||
uses: re-actors/alls-green@223e4bb7a751b91f43eda76992bcfbf23b8b0302
|
uses: re-actors/alls-green@2765efec08f0fd63e83ad900f5fd75646be69ff6
|
||||||
with:
|
with:
|
||||||
jobs: ${{ toJSON(needs) }}
|
jobs: ${{ toJSON(needs) }}
|
||||||
|
|||||||
@@ -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.11.6
|
rev: v0.12.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
args: [--fix, --exit-non-zero-on-fix]
|
args: [--fix, --exit-non-zero-on-fix]
|
||||||
|
|||||||
@@ -6,10 +6,8 @@ require_plugins = {
|
|||||||
'nonebot_plugin_apscheduler',
|
'nonebot_plugin_apscheduler',
|
||||||
'nonebot_plugin_localstore',
|
'nonebot_plugin_localstore',
|
||||||
'nonebot_plugin_orm',
|
'nonebot_plugin_orm',
|
||||||
'nonebot_plugin_session_orm',
|
'nonebot_plugin_uninfo',
|
||||||
'nonebot_plugin_session',
|
|
||||||
'nonebot_plugin_user',
|
'nonebot_plugin_user',
|
||||||
'nonebot_plugin_userinfo',
|
|
||||||
'nonebot_plugin_waiter',
|
'nonebot_plugin_waiter',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
from nonebot import get_plugin_config
|
from pathlib import Path
|
||||||
|
|
||||||
|
from nonebot import get_driver, get_plugin_config
|
||||||
from nonebot_plugin_localstore import get_plugin_cache_dir, get_plugin_data_dir
|
from nonebot_plugin_localstore import get_plugin_cache_dir, get_plugin_data_dir
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
@@ -14,10 +16,17 @@ class Proxy(BaseModel):
|
|||||||
top: str | None = None
|
top: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class Dev(BaseModel):
|
||||||
|
enabled: bool = False
|
||||||
|
template_path: Path | None = None
|
||||||
|
enable_template_check: bool = True
|
||||||
|
|
||||||
|
|
||||||
class ScopedConfig(BaseModel):
|
class ScopedConfig(BaseModel):
|
||||||
request_timeout: float = 30.0
|
request_timeout: float = 30.0
|
||||||
screenshot_quality: float = 2
|
screenshot_quality: float = 2
|
||||||
proxy: Proxy = Field(default_factory=Proxy)
|
proxy: Proxy = Field(default_factory=Proxy)
|
||||||
|
dev: Dev = Field(default_factory=Dev)
|
||||||
|
|
||||||
|
|
||||||
class Config(BaseModel):
|
class Config(BaseModel):
|
||||||
@@ -25,3 +34,4 @@ class Config(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
config = get_plugin_config(Config)
|
config = get_plugin_config(Config)
|
||||||
|
global_config = get_driver().config
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
||||||
batch_op.alter_column('create_time', new_column_name='update_time', existing_type=sa.DateTime())
|
batch_op.alter_column('create_time', new_column_name='update_time', existing_type=sa.DateTime())
|
||||||
@@ -41,6 +43,8 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
||||||
batch_op.alter_column('update_time', new_column_name='create_time')
|
batch_op.alter_column('update_time', new_column_name='create_time')
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('file_hash', sa.String(length=128), nullable=True))
|
batch_op.add_column(sa.Column('file_hash', sa.String(length=128), nullable=True))
|
||||||
@@ -38,6 +40,8 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
||||||
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_file_hash'))
|
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_file_hash'))
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"""add verify field
|
||||||
|
|
||||||
|
迁移 ID: 2ff388a8c486
|
||||||
|
父迁移: 3588702dd3a4
|
||||||
|
创建时间: 2025-07-22 18:09:09.734164
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = '2ff388a8c486'
|
||||||
|
down_revision: str | Sequence[str] | None = '3588702dd3a4'
|
||||||
|
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('nb_t_bind', schema=None) as batch_op:
|
||||||
|
batch_op.add_column(sa.Column('verify', sa.Boolean(), nullable=False, server_default='false'))
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('nb_t_bind', schema=None) as batch_op:
|
||||||
|
batch_op.drop_column('verify')
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
"""modify field length
|
||||||
|
|
||||||
|
迁移 ID: 3588702dd3a4
|
||||||
|
父迁移: bc6abd57928f
|
||||||
|
创建时间: 2025-07-19 17:21:17.927162
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = '3588702dd3a4'
|
||||||
|
down_revision: str | Sequence[str] | None = 'bc6abd57928f'
|
||||||
|
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('nb_t_tos_hist_data', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column(
|
||||||
|
'user_unique_identifier',
|
||||||
|
existing_type=sa.VARCHAR(length=24),
|
||||||
|
type_=sa.String(length=256),
|
||||||
|
existing_nullable=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
with op.batch_alter_table('nb_t_tos_hist_data', schema=None) as batch_op:
|
||||||
|
batch_op.alter_column(
|
||||||
|
'user_unique_identifier',
|
||||||
|
existing_type=sa.String(length=256),
|
||||||
|
type_=sa.VARCHAR(length=24),
|
||||||
|
existing_nullable=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -16,7 +16,6 @@ from alembic import op
|
|||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from rich.progress import BarColumn, MofNCompleteColumn, Progress, TaskProgressColumn, TextColumn, TimeRemainingColumn
|
from rich.progress import BarColumn, MofNCompleteColumn, Progress, TaskProgressColumn, TextColumn, TimeRemainingColumn
|
||||||
from sqlalchemy import desc, select
|
from sqlalchemy import desc, select
|
||||||
from sqlalchemy.dialects import sqlite
|
|
||||||
from sqlalchemy.ext.automap import automap_base
|
from sqlalchemy.ext.automap import automap_base
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
@@ -30,7 +29,7 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
|
|
||||||
|
|
||||||
def migrate_old_data() -> None: # noqa: C901
|
def migrate_old_data() -> None: # noqa: C901
|
||||||
from json import dumps, loads
|
from json import dumps, loads # noqa: PLC0415
|
||||||
|
|
||||||
Base = automap_base() # noqa: N806
|
Base = automap_base() # noqa: N806
|
||||||
Base.prepare(autoload_with=op.get_bind())
|
Base.prepare(autoload_with=op.get_bind())
|
||||||
@@ -109,6 +108,8 @@ def migrate_old_data() -> None: # noqa: C901
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'nonebot_plugin_tetris_stats_tetriohistoricaldata',
|
'nonebot_plugin_tetris_stats_tetriohistoricaldata',
|
||||||
@@ -219,23 +220,25 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'nonebot_plugin_tetris_stats_historicaldata',
|
'nonebot_plugin_tetris_stats_historicaldata',
|
||||||
sa.Column('id', sa.INTEGER(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('trigger_time', sa.DATETIME(), nullable=False),
|
sa.Column('trigger_time', sa.DateTime(), nullable=False),
|
||||||
sa.Column('bot_platform', sa.VARCHAR(length=32), nullable=True),
|
sa.Column('bot_platform', sa.String(length=32), nullable=True),
|
||||||
sa.Column('bot_account', sa.VARCHAR(), nullable=True),
|
sa.Column('bot_account', sa.String(), nullable=True),
|
||||||
sa.Column('source_type', sa.VARCHAR(length=32), nullable=True),
|
sa.Column('source_type', sa.String(length=32), nullable=True),
|
||||||
sa.Column('source_account', sa.VARCHAR(), nullable=True),
|
sa.Column('source_account', sa.String(), nullable=True),
|
||||||
sa.Column('message', sa.BLOB(), nullable=True),
|
sa.Column('message', sa.PickleType(), nullable=True),
|
||||||
sa.Column('game_platform', sa.VARCHAR(length=32), nullable=False),
|
sa.Column('game_platform', sa.String(length=32), nullable=False),
|
||||||
sa.Column('command_type', sa.VARCHAR(length=16), nullable=False),
|
sa.Column('command_type', sa.String(length=16), nullable=False),
|
||||||
sa.Column('command_args', sqlite.JSON(), nullable=False),
|
sa.Column('command_args', sa.JSON(), nullable=False),
|
||||||
sa.Column('game_user', sqlite.JSON(), nullable=False),
|
sa.Column('game_user', sa.JSON(), nullable=False),
|
||||||
sa.Column('processed_data', sqlite.JSON(), nullable=False),
|
sa.Column('processed_data', sa.JSON(), nullable=False),
|
||||||
sa.Column('finish_time', sa.DATETIME(), nullable=False),
|
sa.Column('finish_time', sa.DateTime(), nullable=False),
|
||||||
sa.Column('user_unique_identifier', sa.VARCHAR(length=32), nullable=False),
|
sa.Column('user_unique_identifier', sa.String(length=32), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id', name='pk_nonebot_plugin_tetris_stats_historicaldata'),
|
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:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
"""migrate nonebot_plugin_tetris_stats_tetrioleaguestats
|
||||||
|
|
||||||
|
迁移 ID: 3d900bb0e8d4
|
||||||
|
父迁移: 405c6936a164
|
||||||
|
创建时间: 2025-07-18 02:22:03.771903
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from nonebot.log import logger
|
||||||
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.ext.automap import automap_base
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = '3d900bb0e8d4'
|
||||||
|
down_revision: str | Sequence[str] | None = '405c6936a164'
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def data_migrate() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
insp = inspect(conn)
|
||||||
|
table_names = insp.get_table_names()
|
||||||
|
if 'nonebot_plugin_tetris_stats_tetrioleaguestats' not in table_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
Base = automap_base() # noqa: N806
|
||||||
|
Base.prepare(autoload_with=conn)
|
||||||
|
Old = Base.classes.nonebot_plugin_tetris_stats_tetrioleaguestats # noqa: N806
|
||||||
|
New = Base.classes.nb_t_io_tl_stats # noqa: N806
|
||||||
|
|
||||||
|
with Session(conn) as db_session:
|
||||||
|
count = db_session.query(Old).count()
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
|
||||||
|
|
||||||
|
with Progress(
|
||||||
|
SpinnerColumn(),
|
||||||
|
TextColumn('[progress.description]{task.description}'),
|
||||||
|
BarColumn(),
|
||||||
|
TaskProgressColumn(),
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task('迁移数据...', total=count)
|
||||||
|
|
||||||
|
for i in db_session.query(Old).yield_per(1):
|
||||||
|
db_session.add(
|
||||||
|
New(
|
||||||
|
id=i.id,
|
||||||
|
update_time=i.update_time,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
|
||||||
|
if progress.tasks[task].completed % 100 == 0:
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
logger.success('tetris_stats: 数据迁移完成!')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
data_migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
"""migrate nonebot_plugin_tetris_stats_tetrioleaguehistorical
|
||||||
|
|
||||||
|
迁移 ID: 405c6936a164
|
||||||
|
父迁移: bbbdfd94e6fa
|
||||||
|
创建时间: 2025-07-18 01:55:27.406032
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from nonebot.log import logger
|
||||||
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.ext.automap import automap_base
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = '405c6936a164'
|
||||||
|
down_revision: str | Sequence[str] | None = 'bbbdfd94e6fa'
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def data_migrate() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
insp = inspect(conn)
|
||||||
|
table_names = insp.get_table_names()
|
||||||
|
if 'nonebot_plugin_tetris_stats_tetrioleaguehistorical' not in table_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
Base = automap_base() # noqa: N806
|
||||||
|
Base.prepare(autoload_with=conn)
|
||||||
|
Old = Base.classes.nonebot_plugin_tetris_stats_tetrioleaguehistorical # noqa: N806
|
||||||
|
New = Base.classes.nb_t_io_tl_hist # noqa: N806
|
||||||
|
|
||||||
|
with Session(conn) as db_session:
|
||||||
|
count = db_session.query(Old).count()
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
|
||||||
|
|
||||||
|
with Progress(
|
||||||
|
SpinnerColumn(),
|
||||||
|
TextColumn('[progress.description]{task.description}'),
|
||||||
|
BarColumn(),
|
||||||
|
TaskProgressColumn(),
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task('迁移数据...', total=count)
|
||||||
|
|
||||||
|
for i in db_session.query(Old).yield_per(1):
|
||||||
|
db_session.add(
|
||||||
|
New(
|
||||||
|
id=i.id,
|
||||||
|
request_id=i.request_id,
|
||||||
|
data=i.data,
|
||||||
|
update_time=i.update_time,
|
||||||
|
stats_id=i.stats_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
|
||||||
|
if progress.tasks[task].completed % 100 == 0:
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
logger.success('tetris_stats: 数据迁移完成!')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
data_migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'nonebot_plugin_tetris_stats_tetrioleaguestats',
|
'nonebot_plugin_tetris_stats_tetrioleaguestats',
|
||||||
@@ -102,6 +104,8 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguestatsfield', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetrioleaguestatsfield', schema=None) as batch_op:
|
||||||
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguestatsfield_rank'))
|
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_tetrioleaguestatsfield_rank'))
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
"""Create a new table
|
||||||
|
|
||||||
|
迁移 ID: 612d8b00d9ac
|
||||||
|
父迁移: 5a1b93948494
|
||||||
|
创建时间: 2025-05-26 04:49:29.664480
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = '612d8b00d9ac'
|
||||||
|
down_revision: str | Sequence[str] | None = '5a1b93948494'
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
|
op.create_table(
|
||||||
|
'nonebot_plugin_tetris_stats_triggerhistoricaldatav2',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('trigger_time', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('session_persist_id', sa.Integer(), nullable=False),
|
||||||
|
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('finish_time', sa.DateTime(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_nonebot_plugin_tetris_stats_triggerhistoricaldatav2')),
|
||||||
|
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_triggerhistoricaldatav2', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(
|
||||||
|
batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldatav2_command_type'),
|
||||||
|
['command_type'],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
batch_op.create_index(
|
||||||
|
batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldatav2_game_platform'),
|
||||||
|
['game_platform'],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_triggerhistoricaldatav2', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldatav2_game_platform'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_triggerhistoricaldatav2_command_type'))
|
||||||
|
|
||||||
|
op.drop_table('nonebot_plugin_tetris_stats_triggerhistoricaldatav2')
|
||||||
@@ -26,7 +26,9 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
from json import dumps, loads
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
|
from json import dumps, loads # noqa: PLC0415
|
||||||
|
|
||||||
Base = automap_base() # noqa: N806
|
Base = automap_base() # noqa: N806
|
||||||
connection = op.get_bind()
|
connection = op.get_bind()
|
||||||
@@ -50,7 +52,9 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
from json import dumps, loads
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
|
from json import dumps, loads # noqa: PLC0415
|
||||||
|
|
||||||
Base = automap_base() # noqa: N806
|
Base = automap_base() # noqa: N806
|
||||||
connection = op.get_bind()
|
connection = op.get_bind()
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
"""Migrate to uninfo
|
||||||
|
|
||||||
|
迁移 ID: 766cc7e75a62
|
||||||
|
父迁移: 612d8b00d9ac
|
||||||
|
创建时间: 2025-05-26 04:51:54.665200
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import math
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from nonebot.log import logger
|
||||||
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.ext.automap import automap_base
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = '766cc7e75a62'
|
||||||
|
down_revision: str | Sequence[str] | None = '612d8b00d9ac'
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def data_migrate() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
insp = inspect(conn)
|
||||||
|
table_names = insp.get_table_names()
|
||||||
|
if 'nonebot_plugin_tetris_stats_triggerhistoricaldata' not in table_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
Base = automap_base() # noqa: N806
|
||||||
|
Base.prepare(autoload_with=conn)
|
||||||
|
TriggerHistoricalData = Base.classes.nonebot_plugin_tetris_stats_triggerhistoricaldata # noqa: N806
|
||||||
|
TriggerHistoricalDataV2 = Base.classes.nonebot_plugin_tetris_stats_triggerhistoricaldatav2 # noqa: N806
|
||||||
|
|
||||||
|
with Session(conn) as db_session:
|
||||||
|
count = db_session.query(TriggerHistoricalData).count()
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
from nonebot_session_to_uninfo import ( # type: ignore[import-untyped] # noqa: PLC0415
|
||||||
|
check_tables,
|
||||||
|
get_id_map,
|
||||||
|
)
|
||||||
|
except ImportError as err:
|
||||||
|
msg = '请安装 `nonebot-session-to-uninfo` 以迁移数据'
|
||||||
|
raise ValueError(msg) from err
|
||||||
|
|
||||||
|
check_tables()
|
||||||
|
|
||||||
|
migration_limit = 10000 # 每次迁移的数据量为 10000 条
|
||||||
|
last_id = -1
|
||||||
|
id_map: dict[int, int] = {}
|
||||||
|
|
||||||
|
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
|
||||||
|
|
||||||
|
with Progress(
|
||||||
|
SpinnerColumn(),
|
||||||
|
TextColumn('[progress.description]{task.description}'),
|
||||||
|
BarColumn(),
|
||||||
|
TaskProgressColumn(),
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task('迁移数据...', total=count)
|
||||||
|
|
||||||
|
for _ in range(math.ceil(count / migration_limit)):
|
||||||
|
records = (
|
||||||
|
db_session.query(TriggerHistoricalData)
|
||||||
|
.order_by(TriggerHistoricalData.id)
|
||||||
|
.where(TriggerHistoricalData.id > last_id)
|
||||||
|
.limit(migration_limit)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
last_id = records[-1].id
|
||||||
|
|
||||||
|
session_ids = [
|
||||||
|
record.session_persist_id for record in records if record.session_persist_id not in id_map
|
||||||
|
]
|
||||||
|
if session_ids:
|
||||||
|
id_map.update(get_id_map(session_ids))
|
||||||
|
|
||||||
|
db_session.add_all(
|
||||||
|
TriggerHistoricalDataV2(
|
||||||
|
id=record.id,
|
||||||
|
session_persist_id=id_map[record.session_persist_id],
|
||||||
|
trigger_time=record.trigger_time,
|
||||||
|
game_platform=record.game_platform,
|
||||||
|
command_type=record.command_type,
|
||||||
|
command_args=record.command_args,
|
||||||
|
finish_time=record.finish_time,
|
||||||
|
)
|
||||||
|
for record in records
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.update(task, advance=len(records))
|
||||||
|
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
logger.success('tetris_stats: 数据迁移完成!')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
|
data_migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
"""migrate nonebot_plugin_tetris_stats_tetrioleaguestatsfield
|
||||||
|
|
||||||
|
迁移 ID: 8459b2a4b7a3
|
||||||
|
父迁移: 3d900bb0e8d4
|
||||||
|
创建时间: 2025-07-18 02:24:59.560252
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from nonebot.log import logger
|
||||||
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.ext.automap import automap_base
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = '8459b2a4b7a3'
|
||||||
|
down_revision: str | Sequence[str] | None = '3d900bb0e8d4'
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def data_migrate() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
insp = inspect(conn)
|
||||||
|
table_names = insp.get_table_names()
|
||||||
|
if 'nonebot_plugin_tetris_stats_tetrioleaguestatsfield' not in table_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
Base = automap_base() # noqa: N806
|
||||||
|
Base.prepare(autoload_with=conn)
|
||||||
|
Old = Base.classes.nonebot_plugin_tetris_stats_tetrioleaguestatsfield # noqa: N806
|
||||||
|
New = Base.classes.nb_t_io_tl_stats_field # noqa: N806
|
||||||
|
|
||||||
|
with Session(conn) as db_session:
|
||||||
|
count = db_session.query(Old).count()
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
|
||||||
|
|
||||||
|
with Progress(
|
||||||
|
SpinnerColumn(),
|
||||||
|
TextColumn('[progress.description]{task.description}'),
|
||||||
|
BarColumn(),
|
||||||
|
TaskProgressColumn(),
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task('迁移数据...', total=count)
|
||||||
|
|
||||||
|
for i in db_session.query(Old).yield_per(1):
|
||||||
|
db_session.add(
|
||||||
|
New(
|
||||||
|
id=i.id,
|
||||||
|
rank=i.rank,
|
||||||
|
tr_line=i.tr_line,
|
||||||
|
player_count=i.player_count,
|
||||||
|
low_pps=i.low_pps,
|
||||||
|
low_apm=i.low_apm,
|
||||||
|
low_vs=i.low_vs,
|
||||||
|
avg_pps=i.avg_pps,
|
||||||
|
avg_apm=i.avg_apm,
|
||||||
|
avg_vs=i.avg_vs,
|
||||||
|
high_pps=i.high_pps,
|
||||||
|
high_apm=i.high_apm,
|
||||||
|
high_vs=i.high_vs,
|
||||||
|
stats_id=i.stats_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
|
||||||
|
if progress.tasks[task].completed % 100 == 0:
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
logger.success('tetris_stats: 数据迁移完成!')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
data_migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
@@ -28,10 +28,12 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None: # noqa: C901
|
def upgrade(name: str = '') -> None: # noqa: C901
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
|
|
||||||
from nonebot.compat import PYDANTIC_V2, type_validate_json
|
from nonebot.compat import PYDANTIC_V2, type_validate_json # noqa: PLC0415
|
||||||
from pydantic import BaseModel, ValidationError
|
from pydantic import BaseModel, ValidationError # noqa: PLC0415
|
||||||
from rich.progress import (
|
from rich.progress import ( # noqa: PLC0415
|
||||||
BarColumn,
|
BarColumn,
|
||||||
MofNCompleteColumn,
|
MofNCompleteColumn,
|
||||||
Progress,
|
Progress,
|
||||||
@@ -58,14 +60,14 @@ def upgrade(name: str = '') -> None: # noqa: C901
|
|||||||
logger.info('空表, 跳过')
|
logger.info('空表, 跳过')
|
||||||
return
|
return
|
||||||
|
|
||||||
from nonebot_plugin_tetris_stats.version import __version__
|
from nonebot_plugin_tetris_stats.version import __version__ # noqa: PLC0415
|
||||||
|
|
||||||
if __version__ != '1.0.3':
|
if __version__ != '1.0.3':
|
||||||
msg = '本迁移需要1.0.3版本, 请先锁定版本至1.0.3版本再执行本迁移'
|
msg = '本迁移需要1.0.3版本, 请先锁定版本至1.0.3版本再执行本迁移'
|
||||||
logger.critical(msg)
|
logger.critical(msg)
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
|
|
||||||
from nonebot_plugin_tetris_stats.game_data_processor.schemas import ( # type: ignore[import-untyped]
|
from nonebot_plugin_tetris_stats.game_data_processor.schemas import ( # type: ignore[import-untyped] # noqa: PLC0415
|
||||||
BaseProcessedData,
|
BaseProcessedData,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -101,3 +103,5 @@ def upgrade(name: str = '') -> None: # noqa: C901
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'nonebot_plugin_tetris_stats_bind',
|
'nonebot_plugin_tetris_stats_bind',
|
||||||
@@ -122,6 +124,8 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
||||||
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_rank'))
|
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_iorank_rank'))
|
||||||
|
|||||||
@@ -63,6 +63,8 @@ def migrate_old_data(connection: Connection) -> None:
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
try:
|
try:
|
||||||
db_path = Path(config.db_url)
|
db_path = Path(config.db_url)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
@@ -91,3 +93,5 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ from typing import TYPE_CHECKING
|
|||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
from alembic import op
|
from alembic import op
|
||||||
from sqlalchemy.dialects import sqlite
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from collections.abc import Sequence
|
from collections.abc import Sequence
|
||||||
@@ -26,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
|
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_command_type')
|
||||||
@@ -71,6 +72,8 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
|
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_type'))
|
||||||
@@ -82,19 +85,19 @@ def downgrade(name: str = '') -> None:
|
|||||||
|
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'nonebot_plugin_tetris_stats_historicaldata',
|
'nonebot_plugin_tetris_stats_historicaldata',
|
||||||
sa.Column('id', sa.INTEGER(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('trigger_time', sa.DATETIME(), nullable=False),
|
sa.Column('trigger_time', sa.DateTime(), nullable=False),
|
||||||
sa.Column('bot_platform', sa.VARCHAR(length=32), nullable=True),
|
sa.Column('bot_platform', sa.String(length=32), nullable=True),
|
||||||
sa.Column('bot_account', sa.VARCHAR(), nullable=True),
|
sa.Column('bot_account', sa.String(), nullable=True),
|
||||||
sa.Column('source_type', sa.VARCHAR(length=32), nullable=True),
|
sa.Column('source_type', sa.String(length=32), nullable=True),
|
||||||
sa.Column('source_account', sa.VARCHAR(), nullable=True),
|
sa.Column('source_account', sa.String(), nullable=True),
|
||||||
sa.Column('message', sa.BLOB(), nullable=True),
|
sa.Column('message', sa.PickleType(), nullable=True),
|
||||||
sa.Column('game_platform', sa.VARCHAR(length=32), nullable=False),
|
sa.Column('game_platform', sa.String(length=32), nullable=False),
|
||||||
sa.Column('command_type', sa.VARCHAR(length=16), nullable=False),
|
sa.Column('command_type', sa.String(length=16), nullable=False),
|
||||||
sa.Column('command_args', sqlite.JSON(), nullable=False),
|
sa.Column('command_args', sa.JSON(), nullable=False),
|
||||||
sa.Column('game_user', sa.BLOB(), nullable=False),
|
sa.Column('game_user', sa.PickleType(), nullable=False),
|
||||||
sa.Column('processed_data', sa.BLOB(), nullable=False),
|
sa.Column('processed_data', sa.PickleType(), nullable=False),
|
||||||
sa.Column('finish_time', sa.DATETIME(), nullable=False),
|
sa.Column('finish_time', sa.DateTime(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id', name='pk_nonebot_plugin_tetris_stats_historicaldata'),
|
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:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'nonebot_plugin_tetris_stats_tetriouserconfig',
|
'nonebot_plugin_tetris_stats_tetriouserconfig',
|
||||||
@@ -39,6 +41,8 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
op.drop_table('nonebot_plugin_tetris_stats_tetriouserconfig')
|
op.drop_table('nonebot_plugin_tetris_stats_tetriouserconfig')
|
||||||
# ### end Alembic commands ###
|
# ### end Alembic commands ###
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_bind', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_bind', schema=None) as batch_op:
|
||||||
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_bind_chat_account')
|
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_bind_chat_account')
|
||||||
@@ -49,6 +51,8 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_bind', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_bind', schema=None) as batch_op:
|
||||||
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_bind_user_id'))
|
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_bind_user_id'))
|
||||||
@@ -57,11 +61,11 @@ def downgrade(name: str = '') -> None:
|
|||||||
|
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'nonebot_plugin_tetris_stats_bind',
|
'nonebot_plugin_tetris_stats_bind',
|
||||||
sa.Column('id', sa.INTEGER(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('chat_platform', sa.VARCHAR(length=32), nullable=False),
|
sa.Column('chat_platform', sa.String(length=32), nullable=False),
|
||||||
sa.Column('chat_account', sa.VARCHAR(), nullable=False),
|
sa.Column('chat_account', sa.String(), nullable=False),
|
||||||
sa.Column('game_platform', sa.VARCHAR(length=32), nullable=False),
|
sa.Column('game_platform', sa.String(length=32), nullable=False),
|
||||||
sa.Column('game_account', sa.VARCHAR(), nullable=False),
|
sa.Column('game_account', sa.String(), nullable=False),
|
||||||
sa.PrimaryKeyConstraint('id', name='pk_nonebot_plugin_tetris_stats_bind'),
|
sa.PrimaryKeyConstraint('id', name='pk_nonebot_plugin_tetris_stats_bind'),
|
||||||
)
|
)
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_bind', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_bind', schema=None) as batch_op:
|
||||||
|
|||||||
@@ -0,0 +1,215 @@
|
|||||||
|
"""create new tables
|
||||||
|
|
||||||
|
迁移 ID: b2075a5ce371
|
||||||
|
父迁移: 766cc7e75a62
|
||||||
|
创建时间: 2025-07-17 22:57:32.245327
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from alembic import op
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = 'b2075a5ce371'
|
||||||
|
down_revision: str | Sequence[str] | None = '766cc7e75a62'
|
||||||
|
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! ###
|
||||||
|
op.create_table(
|
||||||
|
'nb_t_bind',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('game_platform', sa.String(length=32), nullable=False),
|
||||||
|
sa.Column('game_account', sa.String(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_bind')),
|
||||||
|
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('nb_t_bind', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_bind_user_id'), ['user_id'], unique=False)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'nb_t_io_hist_data',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_unique_identifier', sa.String(length=24), nullable=False),
|
||||||
|
sa.Column('api_type', sa.String(length=32), nullable=False),
|
||||||
|
sa.Column('data', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('update_time', sa.DateTime(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_hist_data')),
|
||||||
|
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('nb_t_io_hist_data', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_io_hist_data_api_type'), ['api_type'], unique=False)
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_io_hist_data_update_time'), ['update_time'], unique=False)
|
||||||
|
batch_op.create_index(
|
||||||
|
batch_op.f('ix_nb_t_io_hist_data_user_unique_identifier'), ['user_unique_identifier'], unique=False
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'nb_t_io_tl_stats',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('update_time', sa.DateTime(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_tl_stats')),
|
||||||
|
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('nb_t_io_tl_stats', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_io_tl_stats_update_time'), ['update_time'], unique=False)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'nb_t_io_u_cfg',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('query_template', sa.String(length=2), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_u_cfg')),
|
||||||
|
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
'nb_t_top_hist_data',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_unique_identifier', sa.String(length=24), nullable=False),
|
||||||
|
sa.Column('api_type', sa.String(length=16), nullable=False),
|
||||||
|
sa.Column('data', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('update_time', sa.DateTime(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_top_hist_data')),
|
||||||
|
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('nb_t_top_hist_data', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_top_hist_data_api_type'), ['api_type'], unique=False)
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_top_hist_data_update_time'), ['update_time'], unique=False)
|
||||||
|
batch_op.create_index(
|
||||||
|
batch_op.f('ix_nb_t_top_hist_data_user_unique_identifier'), ['user_unique_identifier'], unique=False
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'nb_t_tos_hist_data',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user_unique_identifier', sa.String(length=24), nullable=False),
|
||||||
|
sa.Column('api_type', sa.String(length=16), nullable=False),
|
||||||
|
sa.Column('data', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('update_time', sa.DateTime(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_tos_hist_data')),
|
||||||
|
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('nb_t_tos_hist_data', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_tos_hist_data_api_type'), ['api_type'], unique=False)
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_tos_hist_data_update_time'), ['update_time'], unique=False)
|
||||||
|
batch_op.create_index(
|
||||||
|
batch_op.f('ix_nb_t_tos_hist_data_user_unique_identifier'), ['user_unique_identifier'], unique=False
|
||||||
|
)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'nb_t_trigger_hist_v2',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('trigger_time', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('session_persist_id', sa.Integer(), nullable=False),
|
||||||
|
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('finish_time', sa.DateTime(), nullable=False),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_trigger_hist_v2')),
|
||||||
|
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('nb_t_trigger_hist_v2', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_trigger_hist_v2_command_type'), ['command_type'], unique=False)
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_trigger_hist_v2_game_platform'), ['game_platform'], unique=False)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'nb_t_io_tl_hist',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('request_id', sa.Uuid(), nullable=False),
|
||||||
|
sa.Column('data', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('update_time', sa.DateTime(), nullable=False),
|
||||||
|
sa.Column('stats_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
['stats_id'], ['nb_t_io_tl_stats.id'], name=op.f('fk_nb_t_io_tl_hist_stats_id_nb_t_io_tl_stats')
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_tl_hist')),
|
||||||
|
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('nb_t_io_tl_hist', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_io_tl_hist_request_id'), ['request_id'], unique=False)
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_io_tl_hist_update_time'), ['update_time'], unique=False)
|
||||||
|
|
||||||
|
op.create_table(
|
||||||
|
'nb_t_io_tl_stats_field',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('rank', sa.String(length=2), nullable=False),
|
||||||
|
sa.Column('tr_line', sa.Float(), nullable=False),
|
||||||
|
sa.Column('player_count', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('low_pps', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('low_apm', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('low_vs', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('avg_pps', sa.Float(), nullable=False),
|
||||||
|
sa.Column('avg_apm', sa.Float(), nullable=False),
|
||||||
|
sa.Column('avg_vs', sa.Float(), nullable=False),
|
||||||
|
sa.Column('high_pps', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('high_apm', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('high_vs', sa.JSON(), nullable=False),
|
||||||
|
sa.Column('stats_id', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
['stats_id'], ['nb_t_io_tl_stats.id'], name=op.f('fk_nb_t_io_tl_stats_field_stats_id_nb_t_io_tl_stats')
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint('id', name=op.f('pk_nb_t_io_tl_stats_field')),
|
||||||
|
info={'bind_key': 'nonebot_plugin_tetris_stats'},
|
||||||
|
)
|
||||||
|
with op.batch_alter_table('nb_t_io_tl_stats_field', schema=None) as batch_op:
|
||||||
|
batch_op.create_index(batch_op.f('ix_nb_t_io_tl_stats_field_rank'), ['rank'], 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('nb_t_io_tl_stats_field', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_io_tl_stats_field_rank'))
|
||||||
|
|
||||||
|
op.drop_table('nb_t_io_tl_stats_field')
|
||||||
|
with op.batch_alter_table('nb_t_io_tl_hist', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_io_tl_hist_update_time'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_io_tl_hist_request_id'))
|
||||||
|
|
||||||
|
op.drop_table('nb_t_io_tl_hist')
|
||||||
|
with op.batch_alter_table('nb_t_trigger_hist_v2', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_trigger_hist_v2_game_platform'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_trigger_hist_v2_command_type'))
|
||||||
|
|
||||||
|
op.drop_table('nb_t_trigger_hist_v2')
|
||||||
|
with op.batch_alter_table('nb_t_tos_hist_data', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_tos_hist_data_user_unique_identifier'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_tos_hist_data_update_time'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_tos_hist_data_api_type'))
|
||||||
|
|
||||||
|
op.drop_table('nb_t_tos_hist_data')
|
||||||
|
with op.batch_alter_table('nb_t_top_hist_data', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_top_hist_data_user_unique_identifier'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_top_hist_data_update_time'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_top_hist_data_api_type'))
|
||||||
|
|
||||||
|
op.drop_table('nb_t_top_hist_data')
|
||||||
|
op.drop_table('nb_t_io_u_cfg')
|
||||||
|
with op.batch_alter_table('nb_t_io_tl_stats', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_io_tl_stats_update_time'))
|
||||||
|
|
||||||
|
op.drop_table('nb_t_io_tl_stats')
|
||||||
|
with op.batch_alter_table('nb_t_io_hist_data', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_io_hist_data_user_unique_identifier'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_io_hist_data_update_time'))
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_io_hist_data_api_type'))
|
||||||
|
|
||||||
|
op.drop_table('nb_t_io_hist_data')
|
||||||
|
with op.batch_alter_table('nb_t_bind', schema=None) as batch_op:
|
||||||
|
batch_op.drop_index(batch_op.f('ix_nb_t_bind_user_id'))
|
||||||
|
|
||||||
|
op.drop_table('nb_t_bind')
|
||||||
|
# ### end Alembic commands ###
|
||||||
@@ -23,13 +23,15 @@ branch_labels: str | Sequence[str] | None = None
|
|||||||
depends_on: str | Sequence[str] | None = None
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None: # noqa: C901
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
|
|
||||||
from nonebot.compat import type_validate_json
|
from nonebot.compat import type_validate_json # noqa: PLC0415
|
||||||
from pydantic import ValidationError
|
from pydantic import ValidationError # noqa: PLC0415
|
||||||
from rich.progress import (
|
from rich.progress import ( # noqa: PLC0415
|
||||||
BarColumn,
|
BarColumn,
|
||||||
MofNCompleteColumn,
|
MofNCompleteColumn,
|
||||||
Progress,
|
Progress,
|
||||||
@@ -37,9 +39,9 @@ def upgrade(name: str = '') -> None:
|
|||||||
TextColumn,
|
TextColumn,
|
||||||
TimeRemainingColumn,
|
TimeRemainingColumn,
|
||||||
)
|
)
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select # noqa: PLC0415
|
||||||
from sqlalchemy.ext.automap import automap_base
|
from sqlalchemy.ext.automap import automap_base # noqa: PLC0415
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session # noqa: PLC0415
|
||||||
|
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
|
||||||
batch_op.add_column(sa.Column('user_unique_identifier', sa.String(length=32), nullable=True))
|
batch_op.add_column(sa.Column('user_unique_identifier', sa.String(length=32), nullable=True))
|
||||||
@@ -58,13 +60,15 @@ def upgrade(name: str = '') -> None:
|
|||||||
if count == 0:
|
if count == 0:
|
||||||
logger.info('空表, 跳过')
|
logger.info('空表, 跳过')
|
||||||
else:
|
else:
|
||||||
from nonebot_plugin_tetris_stats.version import __version__
|
from nonebot_plugin_tetris_stats.version import __version__ # noqa: PLC0415
|
||||||
|
|
||||||
if __version__ != '1.0.4':
|
if __version__ != '1.0.4':
|
||||||
msg = '本迁移需要1.0.4版本, 请先锁定版本至1.0.4版本再执行本迁移'
|
msg = '本迁移需要1.0.4版本, 请先锁定版本至1.0.4版本再执行本迁移'
|
||||||
logger.critical(msg)
|
logger.critical(msg)
|
||||||
raise RuntimeError(msg)
|
raise RuntimeError(msg)
|
||||||
from nonebot_plugin_tetris_stats.game_data_processor.schemas import BaseUser # type: ignore[import-untyped]
|
from nonebot_plugin_tetris_stats.game_data_processor.schemas import ( # type: ignore[import-untyped] # noqa: PLC0415
|
||||||
|
BaseUser,
|
||||||
|
)
|
||||||
|
|
||||||
models: list[type[BaseUser]] = BaseUser.__subclasses__()
|
models: list[type[BaseUser]] = BaseUser.__subclasses__()
|
||||||
|
|
||||||
@@ -103,6 +107,8 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_historicaldata', schema=None) as batch_op:
|
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_user_unique_identifier'))
|
batch_op.drop_index(batch_op.f('ix_nonebot_plugin_tetris_stats_historicaldata_user_unique_identifier'))
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
"""migrate nonebot_plugin_tetris_stats_tetriouserconfig
|
||||||
|
|
||||||
|
迁移 ID: b96c8c18b79a
|
||||||
|
父迁移: 8459b2a4b7a3
|
||||||
|
创建时间: 2025-07-18 04:25:44.190319
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from nonebot.log import logger
|
||||||
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.ext.automap import automap_base
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = 'b96c8c18b79a'
|
||||||
|
down_revision: str | Sequence[str] | None = '8459b2a4b7a3'
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def data_migrate() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
insp = inspect(conn)
|
||||||
|
table_names = insp.get_table_names()
|
||||||
|
if 'nonebot_plugin_tetris_stats_tetriouserconfig' not in table_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
Base = automap_base() # noqa: N806
|
||||||
|
Base.prepare(autoload_with=conn)
|
||||||
|
Old = Base.classes.nonebot_plugin_tetris_stats_tetriouserconfig # noqa: N806
|
||||||
|
New = Base.classes.nb_t_io_u_cfg # noqa: N806
|
||||||
|
|
||||||
|
with Session(conn) as db_session:
|
||||||
|
count = db_session.query(Old).count()
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
|
||||||
|
|
||||||
|
with Progress(
|
||||||
|
SpinnerColumn(),
|
||||||
|
TextColumn('[progress.description]{task.description}'),
|
||||||
|
BarColumn(),
|
||||||
|
TaskProgressColumn(),
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task('迁移数据...', total=count)
|
||||||
|
|
||||||
|
for i in db_session.query(Old).yield_per(1):
|
||||||
|
db_session.add(
|
||||||
|
New(
|
||||||
|
id=i.id,
|
||||||
|
query_template=i.query_template,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
|
||||||
|
if progress.tasks[task].completed % 100 == 0:
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
logger.success('tetris_stats: 数据迁移完成!')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
data_migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
@@ -26,6 +26,8 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
|
|
||||||
Base = automap_base() # noqa: N806
|
Base = automap_base() # noqa: N806
|
||||||
connection = op.get_bind()
|
connection = op.get_bind()
|
||||||
@@ -40,3 +42,5 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
"""migrate nonebot_plugin_tetris_stats_tetriohistoricaldata
|
||||||
|
|
||||||
|
迁移 ID: bbbdfd94e6fa
|
||||||
|
父迁移: d61e6ae36586
|
||||||
|
创建时间: 2025-07-18 00:42:33.730885
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from nonebot.log import logger
|
||||||
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.ext.automap import automap_base
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = 'bbbdfd94e6fa'
|
||||||
|
down_revision: str | Sequence[str] | None = 'd61e6ae36586'
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def data_migrate() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
insp = inspect(conn)
|
||||||
|
table_names = insp.get_table_names()
|
||||||
|
if 'nonebot_plugin_tetris_stats_tetriohistoricaldata' not in table_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
Base = automap_base() # noqa: N806
|
||||||
|
Base.prepare(autoload_with=conn)
|
||||||
|
Old = Base.classes.nonebot_plugin_tetris_stats_tetriohistoricaldata # noqa: N806
|
||||||
|
New = Base.classes.nb_t_io_hist_data # noqa: N806
|
||||||
|
|
||||||
|
with Session(conn) as db_session:
|
||||||
|
count = db_session.query(Old).count()
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
|
||||||
|
|
||||||
|
with Progress(
|
||||||
|
SpinnerColumn(),
|
||||||
|
TextColumn('[progress.description]{task.description}'),
|
||||||
|
BarColumn(),
|
||||||
|
TaskProgressColumn(),
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task('迁移数据...', total=count)
|
||||||
|
|
||||||
|
for i in db_session.query(Old).yield_per(1):
|
||||||
|
db_session.add(
|
||||||
|
New(
|
||||||
|
id=i.id,
|
||||||
|
user_unique_identifier=i.user_unique_identifier,
|
||||||
|
api_type=i.api_type,
|
||||||
|
data=i.data,
|
||||||
|
update_time=i.update_time,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
|
||||||
|
if progress.tasks[task].completed % 100 == 0:
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
logger.success('tetris_stats: 数据迁移完成!')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
data_migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
"""migrate nonebot_plugin_tetris_stats_triggerhistoricaldatav2
|
||||||
|
|
||||||
|
迁移 ID: bc6abd57928f
|
||||||
|
父迁移: ee76ae37d70a
|
||||||
|
创建时间: 2025-07-18 04:33:04.222045
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from nonebot.log import logger
|
||||||
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.ext.automap import automap_base
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = 'bc6abd57928f'
|
||||||
|
down_revision: str | Sequence[str] | None = 'ee76ae37d70a'
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def data_migrate() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
insp = inspect(conn)
|
||||||
|
table_names = insp.get_table_names()
|
||||||
|
if 'nonebot_plugin_tetris_stats_triggerhistoricaldatav2' not in table_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
Base = automap_base() # noqa: N806
|
||||||
|
Base.prepare(autoload_with=conn)
|
||||||
|
Old = Base.classes.nonebot_plugin_tetris_stats_triggerhistoricaldatav2 # noqa: N806
|
||||||
|
New = Base.classes.nb_t_trigger_hist_v2 # noqa: N806
|
||||||
|
|
||||||
|
with Session(conn) as db_session:
|
||||||
|
count = db_session.query(Old).count()
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
|
||||||
|
|
||||||
|
with Progress(
|
||||||
|
SpinnerColumn(),
|
||||||
|
TextColumn('[progress.description]{task.description}'),
|
||||||
|
BarColumn(),
|
||||||
|
TaskProgressColumn(),
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task('迁移数据...', total=count)
|
||||||
|
|
||||||
|
for i in db_session.query(Old).yield_per(1):
|
||||||
|
db_session.add(
|
||||||
|
New(
|
||||||
|
id=i.id,
|
||||||
|
trigger_time=i.trigger_time,
|
||||||
|
session_persist_id=i.session_persist_id,
|
||||||
|
game_platform=i.game_platform,
|
||||||
|
command_type=i.command_type,
|
||||||
|
command_args=i.command_args,
|
||||||
|
finish_time=i.finish_time,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
|
||||||
|
if progress.tasks[task].completed % 100 == 0:
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
logger.success('tetris_stats: 数据迁移完成!')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
data_migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
"""migrate nonebot_plugin_tetris_stats_tophistoricaldata
|
||||||
|
|
||||||
|
迁移 ID: ce073d279d19
|
||||||
|
父迁移: b96c8c18b79a
|
||||||
|
创建时间: 2025-07-18 04:28:13.820635
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from nonebot.log import logger
|
||||||
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.ext.automap import automap_base
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = 'ce073d279d19'
|
||||||
|
down_revision: str | Sequence[str] | None = 'b96c8c18b79a'
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def data_migrate() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
insp = inspect(conn)
|
||||||
|
table_names = insp.get_table_names()
|
||||||
|
if 'nonebot_plugin_tetris_stats_tophistoricaldata' not in table_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
Base = automap_base() # noqa: N806
|
||||||
|
Base.prepare(autoload_with=conn)
|
||||||
|
Old = Base.classes.nonebot_plugin_tetris_stats_tophistoricaldata # noqa: N806
|
||||||
|
New = Base.classes.nb_t_top_hist_data # noqa: N806
|
||||||
|
|
||||||
|
with Session(conn) as db_session:
|
||||||
|
count = db_session.query(Old).count()
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
|
||||||
|
|
||||||
|
with Progress(
|
||||||
|
SpinnerColumn(),
|
||||||
|
TextColumn('[progress.description]{task.description}'),
|
||||||
|
BarColumn(),
|
||||||
|
TaskProgressColumn(),
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task('迁移数据...', total=count)
|
||||||
|
|
||||||
|
for i in db_session.query(Old).yield_per(1):
|
||||||
|
db_session.add(
|
||||||
|
New(
|
||||||
|
id=i.id,
|
||||||
|
user_unique_identifier=i.user_unique_identifier,
|
||||||
|
api_type=i.api_type,
|
||||||
|
data=i.data,
|
||||||
|
update_time=i.update_time,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
|
||||||
|
if progress.tasks[task].completed % 100 == 0:
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
logger.success('tetris_stats: 数据迁移完成!')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
data_migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
@@ -26,6 +26,8 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetriohistoricaldata', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_tetriohistoricaldata', schema=None) as batch_op:
|
||||||
batch_op.alter_column(
|
batch_op.alter_column(
|
||||||
@@ -38,6 +40,8 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
# ### commands auto generated by Alembic - please adjust! ###
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
logger.warning('新数据可能不支持降级!')
|
logger.warning('新数据可能不支持降级!')
|
||||||
logger.warning('请确认数据库内数据可以迁移到旧版本!')
|
logger.warning('请确认数据库内数据可以迁移到旧版本!')
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
"""migrate nonebot_plugin_tetris_stats_bind
|
||||||
|
|
||||||
|
迁移 ID: d61e6ae36586
|
||||||
|
父迁移: b2075a5ce371
|
||||||
|
创建时间: 2025-07-17 23:58:13.408384
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from nonebot.log import logger
|
||||||
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.ext.automap import automap_base
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = 'd61e6ae36586'
|
||||||
|
down_revision: str | Sequence[str] | None = 'b2075a5ce371'
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def data_migrate() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
insp = inspect(conn)
|
||||||
|
table_names = insp.get_table_names()
|
||||||
|
if 'nonebot_plugin_tetris_stats_bind' not in table_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
Base = automap_base() # noqa: N806
|
||||||
|
Base.prepare(autoload_with=conn)
|
||||||
|
Old = Base.classes.nonebot_plugin_tetris_stats_bind # noqa: N806
|
||||||
|
New = Base.classes.nb_t_bind # noqa: N806
|
||||||
|
|
||||||
|
with Session(conn) as db_session:
|
||||||
|
count = db_session.query(Old).count()
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
|
||||||
|
|
||||||
|
with Progress(
|
||||||
|
SpinnerColumn(),
|
||||||
|
TextColumn('[progress.description]{task.description}'),
|
||||||
|
BarColumn(),
|
||||||
|
TaskProgressColumn(),
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task('迁移数据...', total=count)
|
||||||
|
|
||||||
|
for i in db_session.query(Old).yield_per(100):
|
||||||
|
db_session.add(
|
||||||
|
New(
|
||||||
|
id=i.id,
|
||||||
|
user_id=i.user_id,
|
||||||
|
game_platform=i.game_platform,
|
||||||
|
game_account=i.game_account,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
|
||||||
|
if progress.tasks[task].completed % 100 == 0:
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
logger.success('tetris_stats: 数据迁移完成!')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
data_migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
"""migrate nonebot_plugin_tetris_stats_toshistoricaldata
|
||||||
|
|
||||||
|
迁移 ID: ee76ae37d70a
|
||||||
|
父迁移: ce073d279d19
|
||||||
|
创建时间: 2025-07-18 04:29:52.976624
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
from nonebot.log import logger
|
||||||
|
from rich.progress import BarColumn, Progress, SpinnerColumn, TaskProgressColumn, TextColumn
|
||||||
|
from sqlalchemy import inspect
|
||||||
|
from sqlalchemy.ext.automap import automap_base
|
||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
revision: str = 'ee76ae37d70a'
|
||||||
|
down_revision: str | Sequence[str] | None = 'ce073d279d19'
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def data_migrate() -> None:
|
||||||
|
conn = op.get_bind()
|
||||||
|
insp = inspect(conn)
|
||||||
|
table_names = insp.get_table_names()
|
||||||
|
if 'nonebot_plugin_tetris_stats_toshistoricaldata' not in table_names:
|
||||||
|
return
|
||||||
|
|
||||||
|
Base = automap_base() # noqa: N806
|
||||||
|
Base.prepare(autoload_with=conn)
|
||||||
|
Old = Base.classes.nonebot_plugin_tetris_stats_toshistoricaldata # noqa: N806
|
||||||
|
New = Base.classes.nb_t_tos_hist_data # noqa: N806
|
||||||
|
|
||||||
|
with Session(conn) as db_session:
|
||||||
|
count = db_session.query(Old).count()
|
||||||
|
if count == 0:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.warning('tetris_stats: 正在迁移数据, 请不要关闭程序...')
|
||||||
|
|
||||||
|
with Progress(
|
||||||
|
SpinnerColumn(),
|
||||||
|
TextColumn('[progress.description]{task.description}'),
|
||||||
|
BarColumn(),
|
||||||
|
TaskProgressColumn(),
|
||||||
|
) as progress:
|
||||||
|
task = progress.add_task('迁移数据...', total=count)
|
||||||
|
|
||||||
|
for i in db_session.query(Old).yield_per(1):
|
||||||
|
db_session.add(
|
||||||
|
New(
|
||||||
|
id=i.id,
|
||||||
|
user_unique_identifier=i.user_unique_identifier,
|
||||||
|
api_type=i.api_type,
|
||||||
|
data=i.data,
|
||||||
|
update_time=i.update_time,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
progress.update(task, advance=1)
|
||||||
|
|
||||||
|
if progress.tasks[task].completed % 100 == 0:
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
db_session.commit()
|
||||||
|
|
||||||
|
logger.success('tetris_stats: 数据迁移完成!')
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
|
data_migrate()
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade(name: str = '') -> None:
|
||||||
|
if name:
|
||||||
|
return
|
||||||
@@ -25,6 +25,8 @@ depends_on: str | Sequence[str] | None = None
|
|||||||
def upgrade(name: str = '') -> None:
|
def upgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
||||||
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_iorank_file_hash')
|
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_iorank_file_hash')
|
||||||
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_iorank_rank')
|
batch_op.drop_index('ix_nonebot_plugin_tetris_stats_iorank_rank')
|
||||||
@@ -66,23 +68,25 @@ def upgrade(name: str = '') -> None:
|
|||||||
def downgrade(name: str = '') -> None:
|
def downgrade(name: str = '') -> None:
|
||||||
if name:
|
if name:
|
||||||
return
|
return
|
||||||
|
if op.get_bind().dialect.name == 'postgresql':
|
||||||
|
return
|
||||||
op.create_table(
|
op.create_table(
|
||||||
'nonebot_plugin_tetris_stats_iorank',
|
'nonebot_plugin_tetris_stats_iorank',
|
||||||
sa.Column('id', sa.INTEGER(), nullable=False),
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
sa.Column('rank', sa.VARCHAR(length=2), nullable=False),
|
sa.Column('rank', sa.String(length=2), nullable=False),
|
||||||
sa.Column('tr_line', sa.FLOAT(), nullable=False),
|
sa.Column('tr_line', sa.Float(), nullable=False),
|
||||||
sa.Column('player_count', sa.INTEGER(), nullable=False),
|
sa.Column('player_count', sa.Integer(), nullable=False),
|
||||||
sa.Column('low_pps', sa.JSON(), nullable=False),
|
sa.Column('low_pps', sa.JSON(), nullable=False),
|
||||||
sa.Column('low_apm', sa.JSON(), nullable=False),
|
sa.Column('low_apm', sa.JSON(), nullable=False),
|
||||||
sa.Column('low_vs', sa.JSON(), nullable=False),
|
sa.Column('low_vs', sa.JSON(), nullable=False),
|
||||||
sa.Column('avg_pps', sa.FLOAT(), nullable=False),
|
sa.Column('avg_pps', sa.Float(), nullable=False),
|
||||||
sa.Column('avg_apm', sa.FLOAT(), nullable=False),
|
sa.Column('avg_apm', sa.Float(), nullable=False),
|
||||||
sa.Column('avg_vs', sa.FLOAT(), nullable=False),
|
sa.Column('avg_vs', sa.Float(), nullable=False),
|
||||||
sa.Column('high_pps', sa.JSON(), nullable=False),
|
sa.Column('high_pps', sa.JSON(), nullable=False),
|
||||||
sa.Column('high_apm', sa.JSON(), nullable=False),
|
sa.Column('high_apm', sa.JSON(), nullable=False),
|
||||||
sa.Column('high_vs', sa.JSON(), nullable=False),
|
sa.Column('high_vs', sa.JSON(), nullable=False),
|
||||||
sa.Column('update_time', sa.DATETIME(), nullable=False),
|
sa.Column('update_time', sa.DateTime(), nullable=False),
|
||||||
sa.Column('file_hash', sa.VARCHAR(length=128), nullable=True),
|
sa.Column('file_hash', sa.String(length=128), nullable=True),
|
||||||
sa.PrimaryKeyConstraint('id', name='pk_nonebot_plugin_tetris_stats_iorank'),
|
sa.PrimaryKeyConstraint('id', name='pk_nonebot_plugin_tetris_stats_iorank'),
|
||||||
)
|
)
|
||||||
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
with op.batch_alter_table('nonebot_plugin_tetris_stats_iorank', schema=None) as batch_op:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from nonebot_plugin_user import User
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from ..utils.typedefs import AllCommandType, BaseCommandType, GameType, TETRIOCommandType
|
from ..utils.typedefs import AllCommandType, BaseCommandType, GameType, TETRIOCommandType
|
||||||
from .models import Bind, TriggerHistoricalData
|
from .models import Bind, TriggerHistoricalDataV2
|
||||||
|
|
||||||
UTC = timezone.utc
|
UTC = timezone.utc
|
||||||
|
|
||||||
@@ -42,6 +42,8 @@ async def create_or_update_bind(
|
|||||||
user: User,
|
user: User,
|
||||||
game_platform: GameType,
|
game_platform: GameType,
|
||||||
game_account: str,
|
game_account: str,
|
||||||
|
*,
|
||||||
|
verify: bool = False,
|
||||||
) -> BindStatus:
|
) -> BindStatus:
|
||||||
bind = await query_bind_info(
|
bind = await query_bind_info(
|
||||||
session=session,
|
session=session,
|
||||||
@@ -53,11 +55,13 @@ async def create_or_update_bind(
|
|||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
game_platform=game_platform,
|
game_platform=game_platform,
|
||||||
game_account=game_account,
|
game_account=game_account,
|
||||||
|
verify=verify,
|
||||||
)
|
)
|
||||||
session.add(bind)
|
session.add(bind)
|
||||||
status = BindStatus.SUCCESS
|
status = BindStatus.SUCCESS
|
||||||
else:
|
else:
|
||||||
bind.game_account = game_account
|
bind.game_account = game_account
|
||||||
|
bind.verify = verify
|
||||||
status = BindStatus.UPDATE
|
status = BindStatus.UPDATE
|
||||||
await session.commit()
|
await session.commit()
|
||||||
return status
|
return status
|
||||||
@@ -139,7 +143,7 @@ async def trigger(
|
|||||||
except FinishedException:
|
except FinishedException:
|
||||||
async with get_session() as session:
|
async with get_session() as session:
|
||||||
session.add(
|
session.add(
|
||||||
TriggerHistoricalData(
|
TriggerHistoricalDataV2(
|
||||||
trigger_time=trigger_time,
|
trigger_time=trigger_time,
|
||||||
session_persist_id=session_persist_id,
|
session_persist_id=session_persist_id,
|
||||||
game_platform=game_platform,
|
game_platform=game_platform,
|
||||||
|
|||||||
@@ -65,13 +65,18 @@ class PydanticType(TypeDecorator):
|
|||||||
|
|
||||||
|
|
||||||
class Bind(MappedAsDataclass, Model):
|
class Bind(MappedAsDataclass, Model):
|
||||||
|
__tablename__ = 'nb_t_bind'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||||
user_id: Mapped[int] = mapped_column(index=True)
|
user_id: Mapped[int] = mapped_column(index=True)
|
||||||
game_platform: Mapped[GameType] = mapped_column(String(32))
|
game_platform: Mapped[GameType] = mapped_column(String(32))
|
||||||
game_account: Mapped[str]
|
game_account: Mapped[str]
|
||||||
|
verify: Mapped[bool]
|
||||||
|
|
||||||
|
|
||||||
class TriggerHistoricalData(MappedAsDataclass, Model):
|
class TriggerHistoricalDataV2(MappedAsDataclass, Model):
|
||||||
|
__tablename__ = 'nb_t_trigger_hist_v2'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||||
trigger_time: Mapped[datetime] = mapped_column(DateTime)
|
trigger_time: Mapped[datetime] = mapped_column(DateTime)
|
||||||
session_persist_id: Mapped[int]
|
session_persist_id: Mapped[int]
|
||||||
|
|||||||
@@ -22,3 +22,5 @@ class BaseUser(BaseModel, ABC, Generic[T]):
|
|||||||
@abstractmethod
|
@abstractmethod
|
||||||
def unique_identifier(self) -> str:
|
def unique_identifier(self) -> str:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
__hash__ = BaseModel.__hash__
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ command = Subcommand(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
from . import bind, config, list, query, rank, record, unbind # noqa: A004, E402
|
from . import bind, config, list, query, rank, record, unbind, verify # noqa: A004, E402
|
||||||
|
|
||||||
main_command.add(command)
|
main_command.add(command)
|
||||||
|
|
||||||
@@ -36,4 +36,5 @@ __all__ = [
|
|||||||
'rank',
|
'rank',
|
||||||
'record',
|
'record',
|
||||||
'unbind',
|
'unbind',
|
||||||
|
'verify',
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ from .typedefs import Records, Summaries
|
|||||||
|
|
||||||
|
|
||||||
class TETRIOHistoricalData(MappedAsDataclass, Model):
|
class TETRIOHistoricalData(MappedAsDataclass, Model):
|
||||||
|
__tablename__ = 'nb_t_io_hist_data'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||||
user_unique_identifier: Mapped[str] = mapped_column(String(24), index=True)
|
user_unique_identifier: Mapped[str] = mapped_column(String(24), index=True)
|
||||||
api_type: Mapped[Literal['User Info', Records, Summaries]] = mapped_column(String(32), index=True)
|
api_type: Mapped[Literal['User Info', Records, Summaries]] = mapped_column(String(32), index=True)
|
||||||
|
|||||||
@@ -1,25 +1,93 @@
|
|||||||
from typing import TypeAlias
|
from datetime import datetime
|
||||||
|
from enum import IntEnum
|
||||||
|
from typing import Literal, TypeAlias
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
from ..base import FailedModel, SuccessModel
|
from ..base import FailedModel, SuccessModel
|
||||||
|
|
||||||
|
|
||||||
|
class RankType(IntEnum):
|
||||||
|
PERCENTILE = 1
|
||||||
|
ISSUE = 2
|
||||||
|
ZENITH = 3
|
||||||
|
PERCENTILELAX = 4
|
||||||
|
PERCENTILEVLAX = 5
|
||||||
|
PERCENTILEMLAX = 6
|
||||||
|
|
||||||
|
|
||||||
|
class ValueType(IntEnum):
|
||||||
|
NONE = 0
|
||||||
|
NUMBER = 1
|
||||||
|
TIME = 2
|
||||||
|
TIME_INV = 3
|
||||||
|
FLOOR = 4
|
||||||
|
ISSUE = 5
|
||||||
|
NUMBER_INV = 6
|
||||||
|
|
||||||
|
|
||||||
|
class ArType(IntEnum):
|
||||||
|
UNRANKED = 0
|
||||||
|
RANKED = 1
|
||||||
|
COMPETITIVE = 2
|
||||||
|
|
||||||
|
|
||||||
|
class Rank(IntEnum):
|
||||||
|
NONE = 0
|
||||||
|
BRONZE = 1
|
||||||
|
SILVER = 2
|
||||||
|
GOLD = 3
|
||||||
|
PLATINUM = 4
|
||||||
|
DIAMOND = 5
|
||||||
|
ISSUED = 100
|
||||||
|
|
||||||
|
|
||||||
|
class Ally(BaseModel):
|
||||||
|
id: str = Field(alias='_id')
|
||||||
|
username: str
|
||||||
|
role: Literal['anon', 'user', 'bot', 'halfmod', 'mod', 'admin', 'sysop', 'hidden', 'banned']
|
||||||
|
country: str | None = None
|
||||||
|
supporter: bool
|
||||||
|
avatar_revision: int | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class X(BaseModel):
|
||||||
|
ally: Ally | None = None
|
||||||
|
|
||||||
|
|
||||||
class Achievement(BaseModel):
|
class Achievement(BaseModel):
|
||||||
# 这**都是些啥
|
# 这**都是些啥
|
||||||
k: int
|
achievement_id: int = Field(alias='k')
|
||||||
o: int
|
category: str
|
||||||
rt: int
|
primary_name: str = Field(alias='name')
|
||||||
vt: int
|
objective: str = Field(alias='object')
|
||||||
|
flavor_text: str = Field(alias='desc')
|
||||||
|
order: int = Field(alias='o')
|
||||||
|
rank_type: RankType = Field(alias='rt')
|
||||||
|
value_type: ValueType = Field(alias='vt')
|
||||||
|
ar_type: ArType = Field(alias='art')
|
||||||
min: int
|
min: int
|
||||||
deci: int
|
deci: int
|
||||||
name: str
|
|
||||||
object: str
|
|
||||||
category: str
|
|
||||||
hidden: bool
|
hidden: bool
|
||||||
desc: str
|
nolb: bool
|
||||||
|
event: str | None = None
|
||||||
|
event_past: bool | None = None
|
||||||
|
disabled: bool | None = None
|
||||||
|
pair: bool | None = None
|
||||||
|
achieved_score: float | None = Field(None, alias='v')
|
||||||
|
a: float | None = None
|
||||||
|
t: datetime | None = None
|
||||||
|
pos: int | None = None
|
||||||
|
total: int | None = None
|
||||||
|
rank: Rank | None = None
|
||||||
|
x: X | None = None
|
||||||
n: str
|
n: str
|
||||||
stub: bool
|
|
||||||
|
tiebreak: int
|
||||||
|
notifypb: bool
|
||||||
|
id: str | None = Field(None, alias='_id')
|
||||||
|
progress: float | None = None
|
||||||
|
stub: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
class AchievementsSuccessModel(SuccessModel):
|
class AchievementsSuccessModel(SuccessModel):
|
||||||
|
|||||||
@@ -1,22 +1,25 @@
|
|||||||
|
from asyncio import gather
|
||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
|
from secrets import choice
|
||||||
|
|
||||||
from arclet.alconna import Arg, ArgFlag
|
from arclet.alconna import Arg, ArgFlag
|
||||||
from nonebot_plugin_alconna import Args, Subcommand
|
from nonebot_plugin_alconna import Args, Subcommand
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import QryItrface, Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo import User as UninfoUser
|
||||||
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import User
|
from nonebot_plugin_user import User
|
||||||
from nonebot_plugin_userinfo import BotUserInfo, UserInfo
|
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
|
from ...config.config import global_config
|
||||||
from ...db import BindStatus, create_or_update_bind, trigger
|
from ...db import BindStatus, create_or_update_bind, trigger
|
||||||
from ...utils.host import HostPage, get_self_netloc
|
from ...utils.host import get_self_netloc
|
||||||
from ...utils.image import get_avatar
|
from ...utils.image import get_avatar
|
||||||
from ...utils.lang import get_lang
|
from ...utils.lang import get_lang
|
||||||
from ...utils.render import Bind, render
|
from ...utils.render import render_image
|
||||||
from ...utils.render.schemas.base import Avatar, People
|
from ...utils.render.schemas.base import Avatar, People
|
||||||
from ...utils.screenshot import screenshot
|
from ...utils.render.schemas.bind import Bind
|
||||||
from . import alc, command, get_player
|
from . import alc, command, get_player
|
||||||
from .api import Player
|
from .api import Player
|
||||||
from .constant import GAME_TYPE
|
from .constant import GAME_TYPE
|
||||||
@@ -42,9 +45,45 @@ alc.shortcut(
|
|||||||
humanized='io绑定',
|
humanized='io绑定',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from nonebot.adapters.discord import MessageCreateEvent
|
||||||
|
|
||||||
|
@alc.assign('TETRIO.bind')
|
||||||
|
async def _(_: MessageCreateEvent, nb_user: User, account: Player, event_session: Uninfo, interface: QryItrface):
|
||||||
|
async with trigger(
|
||||||
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
|
game_platform=GAME_TYPE,
|
||||||
|
command_type='bind',
|
||||||
|
command_args=[],
|
||||||
|
):
|
||||||
|
user, user_info = await gather(account.user, account.get_info())
|
||||||
|
verify = (
|
||||||
|
user_info.data.connections.discord is not None
|
||||||
|
and user_info.data.connections.discord.id == event_session.user.id
|
||||||
|
)
|
||||||
|
async with get_session() as session:
|
||||||
|
bind_status = await create_or_update_bind(
|
||||||
|
session=session,
|
||||||
|
user=nb_user,
|
||||||
|
game_platform=GAME_TYPE,
|
||||||
|
game_account=user.unique_identifier,
|
||||||
|
verify=verify,
|
||||||
|
)
|
||||||
|
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
|
||||||
|
await UniMessage.image(
|
||||||
|
raw=await make_bind_image(
|
||||||
|
player=account,
|
||||||
|
event_session=event_session,
|
||||||
|
interface=interface,
|
||||||
|
verify=verify,
|
||||||
|
)
|
||||||
|
).finish()
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@alc.assign('TETRIO.bind')
|
@alc.assign('TETRIO.bind')
|
||||||
async def _(nb_user: User, account: Player, event_session: EventSession, bot_info: UserInfo = BotUserInfo()): # noqa: B008
|
async def _(nb_user: User, account: Player, event_session: Uninfo, interface: QryItrface):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
@@ -60,29 +99,45 @@ async def _(nb_user: User, account: Player, event_session: EventSession, bot_inf
|
|||||||
game_account=user.unique_identifier,
|
game_account=user.unique_identifier,
|
||||||
)
|
)
|
||||||
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
|
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
|
||||||
netloc = get_self_netloc()
|
await UniMessage.image(
|
||||||
async with HostPage(
|
raw=await make_bind_image(
|
||||||
await render(
|
player=account,
|
||||||
'v1/binding',
|
event_session=event_session,
|
||||||
Bind(
|
interface=interface,
|
||||||
platform='TETR.IO',
|
verify=None,
|
||||||
type='unknown',
|
|
||||||
user=People(
|
|
||||||
avatar=str(
|
|
||||||
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}')
|
|
||||||
% {'revision': avatar_revision}
|
|
||||||
)
|
|
||||||
if (avatar_revision := (await account.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,
|
|
||||||
),
|
|
||||||
prompt='io查我',
|
|
||||||
lang=get_lang(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
) as page_hash:
|
).finish()
|
||||||
await UniMessage.image(raw=await screenshot(f'http://{netloc}/host/{page_hash}.html')).finish()
|
|
||||||
|
|
||||||
|
async def make_bind_image(
|
||||||
|
player: Player, event_session: Uninfo, interface: QryItrface, *, verify: bool | None = None
|
||||||
|
) -> bytes:
|
||||||
|
(user, avatar_revision) = await gather(player.user, player.avatar_revision)
|
||||||
|
return await render_image(
|
||||||
|
Bind(
|
||||||
|
platform='TETR.IO',
|
||||||
|
type='unknown' if verify is None else 'success' if verify else 'unverified',
|
||||||
|
user=People(
|
||||||
|
avatar=str(
|
||||||
|
URL(f'http://{get_self_netloc()}/host/resource/tetrio/avatars/{user.ID}')
|
||||||
|
% {'revision': avatar_revision}
|
||||||
|
)
|
||||||
|
if 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_user := await interface.get_user(event_session.self_id)
|
||||||
|
or UninfoUser(id=event_session.self_id)
|
||||||
|
),
|
||||||
|
'Data URI',
|
||||||
|
'../../static/logo/logo.svg',
|
||||||
|
),
|
||||||
|
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
|
||||||
|
),
|
||||||
|
prompt='io查我',
|
||||||
|
lang=get_lang(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ from arclet.alconna import Arg
|
|||||||
from nonebot_plugin_alconna import Option, Subcommand
|
from nonebot_plugin_alconna import Option, Subcommand
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import async_scoped_session
|
from nonebot_plugin_orm import async_scoped_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import User
|
from nonebot_plugin_user import User
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ alc.shortcut(
|
|||||||
|
|
||||||
|
|
||||||
@alc.assign('TETRIO.config')
|
@alc.assign('TETRIO.config')
|
||||||
async def _(user: User, session: async_scoped_session, event_session: EventSession, template: Template):
|
async def _(user: User, session: async_scoped_session, event_session: Uninfo, template: Template):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
|
|||||||
@@ -1,15 +1,13 @@
|
|||||||
from nonebot_plugin_alconna import Args, Option, Subcommand
|
from nonebot_plugin_alconna import Args, Option, Subcommand
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
|
|
||||||
from ...db import trigger
|
from ...db import trigger
|
||||||
from ...utils.host import HostPage, get_self_netloc
|
|
||||||
from ...utils.lang import get_lang
|
from ...utils.lang import get_lang
|
||||||
from ...utils.metrics import get_metrics
|
from ...utils.metrics import get_metrics
|
||||||
from ...utils.render import render
|
from ...utils.render import render_image
|
||||||
from ...utils.render.schemas.v2.tetrio.user.list import Data, List, TetraLeague, User
|
from ...utils.render.schemas.v2.tetrio.user.list import Data, List, TetraLeague, User
|
||||||
from ...utils.screenshot import screenshot
|
|
||||||
from .. import alc
|
from .. import alc
|
||||||
from . import command
|
from . import command
|
||||||
from .api.leaderboards import by
|
from .api.leaderboards import by
|
||||||
@@ -32,7 +30,7 @@ command.add(
|
|||||||
|
|
||||||
@alc.assign('TETRIO.list')
|
@alc.assign('TETRIO.list')
|
||||||
async def _(
|
async def _(
|
||||||
event_session: EventSession,
|
event_session: Uninfo,
|
||||||
max_tr: float | None = None,
|
max_tr: float | None = None,
|
||||||
min_tr: float | None = None,
|
min_tr: float | None = None,
|
||||||
limit: int | None = None,
|
limit: int | None = None,
|
||||||
@@ -59,9 +57,8 @@ async def _(
|
|||||||
country=country,
|
country=country,
|
||||||
)
|
)
|
||||||
league = await by('league', parameter)
|
league = await by('league', parameter)
|
||||||
async with HostPage(
|
await UniMessage.image(
|
||||||
await render(
|
raw=await render_image(
|
||||||
'v2/tetrio/user/list',
|
|
||||||
List(
|
List(
|
||||||
show_index=True,
|
show_index=True,
|
||||||
data=[
|
data=[
|
||||||
@@ -92,5 +89,4 @@ async def _(
|
|||||||
lang=get_lang(),
|
lang=get_lang(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
) as page_hash:
|
).finish()
|
||||||
await UniMessage.image(raw=await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')).finish()
|
|
||||||
|
|||||||
@@ -12,11 +12,15 @@ from .typedefs import Template
|
|||||||
|
|
||||||
|
|
||||||
class TETRIOUserConfig(MappedAsDataclass, Model):
|
class TETRIOUserConfig(MappedAsDataclass, Model):
|
||||||
|
__tablename__ = 'nb_t_io_u_cfg'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(primary_key=True)
|
id: Mapped[int] = mapped_column(primary_key=True)
|
||||||
query_template: Mapped[Template] = mapped_column(String(2))
|
query_template: Mapped[Template] = mapped_column(String(2))
|
||||||
|
|
||||||
|
|
||||||
class TETRIOLeagueStats(MappedAsDataclass, Model):
|
class TETRIOLeagueStats(MappedAsDataclass, Model):
|
||||||
|
__tablename__ = 'nb_t_io_tl_stats'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||||
raw: Mapped[list['TETRIOLeagueHistorical']] = relationship(back_populates='stats', lazy='noload')
|
raw: Mapped[list['TETRIOLeagueHistorical']] = relationship(back_populates='stats', lazy='noload')
|
||||||
fields: Mapped[list['TETRIOLeagueStatsField']] = relationship(back_populates='stats')
|
fields: Mapped[list['TETRIOLeagueStatsField']] = relationship(back_populates='stats')
|
||||||
@@ -24,11 +28,13 @@ class TETRIOLeagueStats(MappedAsDataclass, Model):
|
|||||||
|
|
||||||
|
|
||||||
class TETRIOLeagueHistorical(MappedAsDataclass, Model):
|
class TETRIOLeagueHistorical(MappedAsDataclass, Model):
|
||||||
|
__tablename__ = 'nb_t_io_tl_hist'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||||
request_id: Mapped[UUID] = mapped_column(index=True)
|
request_id: Mapped[UUID] = mapped_column(index=True)
|
||||||
data: Mapped[BySuccessModel] = mapped_column(PydanticType([], {BySuccessModel}))
|
data: Mapped[BySuccessModel] = mapped_column(PydanticType([], {BySuccessModel}))
|
||||||
update_time: Mapped[datetime] = mapped_column(DateTime, index=True)
|
update_time: Mapped[datetime] = mapped_column(DateTime, index=True)
|
||||||
stats_id: Mapped[int] = mapped_column(ForeignKey('nonebot_plugin_tetris_stats_tetrioleaguestats.id'), init=False)
|
stats_id: Mapped[int] = mapped_column(ForeignKey('nb_t_io_tl_stats.id'), init=False)
|
||||||
stats: Mapped['TETRIOLeagueStats'] = relationship(back_populates='raw')
|
stats: Mapped['TETRIOLeagueStats'] = relationship(back_populates='raw')
|
||||||
|
|
||||||
|
|
||||||
@@ -36,6 +42,8 @@ entry_type = PydanticType([], {Entry})
|
|||||||
|
|
||||||
|
|
||||||
class TETRIOLeagueStatsField(MappedAsDataclass, Model):
|
class TETRIOLeagueStatsField(MappedAsDataclass, Model):
|
||||||
|
__tablename__ = 'nb_t_io_tl_stats_field'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||||
rank: Mapped[ValidRank] = mapped_column(String(2), index=True)
|
rank: Mapped[ValidRank] = mapped_column(String(2), index=True)
|
||||||
tr_line: Mapped[float]
|
tr_line: Mapped[float]
|
||||||
@@ -49,5 +57,5 @@ class TETRIOLeagueStatsField(MappedAsDataclass, Model):
|
|||||||
high_pps: Mapped[Entry] = mapped_column(entry_type)
|
high_pps: Mapped[Entry] = mapped_column(entry_type)
|
||||||
high_apm: Mapped[Entry] = mapped_column(entry_type)
|
high_apm: Mapped[Entry] = mapped_column(entry_type)
|
||||||
high_vs: Mapped[Entry] = mapped_column(entry_type)
|
high_vs: Mapped[Entry] = mapped_column(entry_type)
|
||||||
stats_id: Mapped[int] = mapped_column(ForeignKey('nonebot_plugin_tetris_stats_tetrioleaguestats.id'), init=False)
|
stats_id: Mapped[int] = mapped_column(ForeignKey('nb_t_io_tl_stats.id'), init=False)
|
||||||
stats: Mapped['TETRIOLeagueStats'] = relationship(back_populates='fields')
|
stats: Mapped['TETRIOLeagueStats'] = relationship(back_populates='fields')
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ from nonebot.matcher import Matcher
|
|||||||
from nonebot_plugin_alconna import Args, At, Option, Subcommand
|
from nonebot_plugin_alconna import Args, At, Option, Subcommand
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import User as NBUser
|
from nonebot_plugin_user import User as NBUser
|
||||||
from nonebot_plugin_user import get_user
|
from nonebot_plugin_user import get_user
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
@@ -90,7 +90,7 @@ async def _( # noqa: PLR0913
|
|||||||
event: Event,
|
event: Event,
|
||||||
matcher: Matcher,
|
matcher: Matcher,
|
||||||
target: At | Me,
|
target: At | Me,
|
||||||
event_session: EventSession,
|
event_session: Uninfo,
|
||||||
template: Template | None = None,
|
template: Template | None = None,
|
||||||
):
|
):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
@@ -103,7 +103,7 @@ async def _( # noqa: PLR0913
|
|||||||
bind = await query_bind_info(
|
bind = await query_bind_info(
|
||||||
session=session,
|
session=session,
|
||||||
user=await get_user(
|
user=await get_user(
|
||||||
event_session.platform, target.target if isinstance(target, At) else event.get_user_id()
|
event_session.scope, target.target if isinstance(target, At) else event.get_user_id()
|
||||||
),
|
),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
)
|
)
|
||||||
@@ -120,7 +120,7 @@ async def _( # noqa: PLR0913
|
|||||||
|
|
||||||
|
|
||||||
@alc.assign('TETRIO.query')
|
@alc.assign('TETRIO.query')
|
||||||
async def _(user: NBUser, account: Player, event_session: EventSession, template: Template | None = None):
|
async def _(user: NBUser, account: Player, event_session: Uninfo, template: Template | None = None):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
|
|||||||
@@ -6,14 +6,13 @@ from yarl import URL
|
|||||||
|
|
||||||
from ....utils.chart import get_split, get_value_bounds, handle_history_data
|
from ....utils.chart import get_split, get_value_bounds, handle_history_data
|
||||||
from ....utils.exception import FallbackError
|
from ....utils.exception import FallbackError
|
||||||
from ....utils.host import HostPage, get_self_netloc
|
from ....utils.host import get_self_netloc
|
||||||
from ....utils.lang import get_lang
|
from ....utils.lang import get_lang
|
||||||
from ....utils.metrics import get_metrics
|
from ....utils.metrics import get_metrics
|
||||||
from ....utils.render import render
|
from ....utils.render import render_image
|
||||||
from ....utils.render.schemas.base import Avatar, Trending
|
from ....utils.render.schemas.base import Avatar, Trending
|
||||||
from ....utils.render.schemas.v1.base import History
|
from ....utils.render.schemas.v1.base import History
|
||||||
from ....utils.render.schemas.v1.tetrio.user.info import Info, Multiplayer, Singleplayer, User
|
from ....utils.render.schemas.v1.tetrio.info import Info, Multiplayer, Singleplayer, User
|
||||||
from ....utils.screenshot import screenshot
|
|
||||||
from ..api import Player
|
from ..api import Player
|
||||||
from ..api.schemas.summaries.league import RatedData
|
from ..api.schemas.summaries.league import RatedData
|
||||||
from ..constant import TR_MAX, TR_MIN
|
from ..constant import TR_MAX, TR_MIN
|
||||||
@@ -40,61 +39,57 @@ async def make_query_image_v1(player: Player) -> bytes:
|
|||||||
else:
|
else:
|
||||||
sprint_value = 'N/A'
|
sprint_value = 'N/A'
|
||||||
blitz_value = f'{blitz.data.record.results.stats.score:,}' if blitz.data.record is not None else 'N/A'
|
blitz_value = f'{blitz.data.record.results.stats.score:,}' if blitz.data.record is not None else 'N/A'
|
||||||
netloc = get_self_netloc()
|
|
||||||
dsps: float
|
dsps: float
|
||||||
dspp: float
|
dspp: float
|
||||||
# make mypy happy
|
# make mypy happy
|
||||||
async with HostPage(
|
return await render_image(
|
||||||
page=await render(
|
Info(
|
||||||
'v1/tetrio/info',
|
user=User(
|
||||||
Info(
|
avatar=str(
|
||||||
user=User(
|
URL(f'http://{get_self_netloc()}/host/resource/tetrio/avatars/{user.ID}')
|
||||||
avatar=str(
|
% {'revision': avatar_revision}
|
||||||
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}') % {'revision': avatar_revision}
|
)
|
||||||
)
|
if avatar_revision is not None and avatar_revision != 0
|
||||||
if avatar_revision is not None and avatar_revision != 0
|
else Avatar(
|
||||||
else Avatar(
|
type='identicon',
|
||||||
type='identicon',
|
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
|
||||||
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
|
|
||||||
),
|
|
||||||
name=user.name.upper(),
|
|
||||||
bio=user_info.data.bio,
|
|
||||||
),
|
),
|
||||||
multiplayer=Multiplayer(
|
name=user.name.upper(),
|
||||||
glicko=f'{round(league_data.glicko, 2):,}',
|
bio=user_info.data.bio,
|
||||||
rd=round(league_data.rd, 2),
|
|
||||||
rank=league_data.rank,
|
|
||||||
tr=f'{round(league_data.tr, 2):,}',
|
|
||||||
global_rank=league_data.standing,
|
|
||||||
history=History(
|
|
||||||
data=histories,
|
|
||||||
split_interval=split_value,
|
|
||||||
min_value=values.value_min,
|
|
||||||
max_value=values.value_max,
|
|
||||||
offset=offset,
|
|
||||||
),
|
|
||||||
lpm=(metrics := get_metrics(pps=league_data.pps, apm=league_data.apm, vs=league_data.vs)).lpm,
|
|
||||||
pps=metrics.pps,
|
|
||||||
lpm_trending=Trending.KEEP,
|
|
||||||
apm=metrics.apm,
|
|
||||||
apl=metrics.apl,
|
|
||||||
apm_trending=Trending.KEEP,
|
|
||||||
adpm=metrics.adpm,
|
|
||||||
vs=metrics.vs,
|
|
||||||
adpl=metrics.adpl,
|
|
||||||
adpm_trending=Trending.KEEP,
|
|
||||||
app=(app := (league_data.apm / (60 * league_data.pps))),
|
|
||||||
dsps=(dsps := ((league_data.vs / 100) - (league_data.apm / 60))),
|
|
||||||
dspp=(dspp := (dsps / league_data.pps)),
|
|
||||||
ci=150 * dspp - 125 * app + 50 * (league_data.vs / league_data.apm) - 25,
|
|
||||||
ge=2 * ((app * dsps) / league_data.pps),
|
|
||||||
),
|
|
||||||
singleplayer=Singleplayer(
|
|
||||||
sprint=sprint_value,
|
|
||||||
blitz=blitz_value,
|
|
||||||
),
|
|
||||||
lang=get_lang(),
|
|
||||||
),
|
),
|
||||||
)
|
multiplayer=Multiplayer(
|
||||||
) as page_hash:
|
glicko=f'{round(league_data.glicko, 2):,}',
|
||||||
return await screenshot(f'http://{netloc}/host/{page_hash}.html')
|
rd=round(league_data.rd, 2),
|
||||||
|
rank=league_data.rank,
|
||||||
|
tr=f'{round(league_data.tr, 2):,}',
|
||||||
|
global_rank=league_data.standing,
|
||||||
|
history=History(
|
||||||
|
data=histories,
|
||||||
|
split_interval=split_value,
|
||||||
|
min_value=values.value_min,
|
||||||
|
max_value=values.value_max,
|
||||||
|
offset=offset,
|
||||||
|
),
|
||||||
|
lpm=(metrics := get_metrics(pps=league_data.pps, apm=league_data.apm, vs=league_data.vs)).lpm,
|
||||||
|
pps=metrics.pps,
|
||||||
|
lpm_trending=Trending.KEEP,
|
||||||
|
apm=metrics.apm,
|
||||||
|
apl=metrics.apl,
|
||||||
|
apm_trending=Trending.KEEP,
|
||||||
|
adpm=metrics.adpm,
|
||||||
|
vs=metrics.vs,
|
||||||
|
adpl=metrics.adpl,
|
||||||
|
adpm_trending=Trending.KEEP,
|
||||||
|
app=(app := (league_data.apm / (60 * league_data.pps))),
|
||||||
|
dsps=(dsps := ((league_data.vs / 100) - (league_data.apm / 60))),
|
||||||
|
dspp=(dspp := (dsps / league_data.pps)),
|
||||||
|
ci=150 * dspp - 125 * app + 50 * (league_data.vs / league_data.apm) - 25,
|
||||||
|
ge=2 * ((app * dsps) / league_data.pps),
|
||||||
|
),
|
||||||
|
singleplayer=Singleplayer(
|
||||||
|
sprint=sprint_value,
|
||||||
|
blitz=blitz_value,
|
||||||
|
),
|
||||||
|
lang=get_lang(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ from hashlib import md5
|
|||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
from ....utils.exception import FallbackError
|
from ....utils.exception import FallbackError
|
||||||
from ....utils.host import HostPage, get_self_netloc
|
from ....utils.host import get_self_netloc
|
||||||
from ....utils.lang import get_lang
|
from ....utils.lang import get_lang
|
||||||
from ....utils.metrics import get_metrics
|
from ....utils.metrics import get_metrics
|
||||||
from ....utils.render import render
|
from ....utils.render import render_image
|
||||||
from ....utils.render.schemas.base import Avatar
|
from ....utils.render.schemas.base import Avatar
|
||||||
from ....utils.render.schemas.v2.tetrio.user.info import (
|
from ....utils.render.schemas.v2.tetrio.user.info import (
|
||||||
|
Achievement,
|
||||||
Badge,
|
Badge,
|
||||||
Best,
|
Best,
|
||||||
Blitz,
|
Blitz,
|
||||||
@@ -24,7 +25,6 @@ from ....utils.render.schemas.v2.tetrio.user.info import (
|
|||||||
Zen,
|
Zen,
|
||||||
Zenith,
|
Zenith,
|
||||||
)
|
)
|
||||||
from ....utils.screenshot import screenshot
|
|
||||||
from ..api import Player
|
from ..api import Player
|
||||||
from ..api.schemas.summaries.league import InvalidData, NeverPlayedData, NeverRatedData
|
from ..api.schemas.summaries.league import InvalidData, NeverPlayedData, NeverRatedData
|
||||||
from .tools import flow_to_history, handling_special_value
|
from .tools import flow_to_history, handling_special_value
|
||||||
@@ -33,7 +33,7 @@ from .tools import flow_to_history, handling_special_value
|
|||||||
async def make_query_image_v2(player: Player) -> bytes:
|
async def make_query_image_v2(player: Player) -> bytes:
|
||||||
(
|
(
|
||||||
(user, user_info, league, sprint, blitz, zen),
|
(user, user_info, league, sprint, blitz, zen),
|
||||||
(avatar_revision, banner_revision, leagueflow, zenith, zenithex),
|
(avatar_revision, banner_revision, leagueflow, zenith, zenithex, achievements),
|
||||||
) = await gather(
|
) = await gather(
|
||||||
gather(
|
gather(
|
||||||
player.user,
|
player.user,
|
||||||
@@ -49,6 +49,7 @@ async def make_query_image_v2(player: Player) -> bytes:
|
|||||||
player.get_leagueflow(),
|
player.get_leagueflow(),
|
||||||
player.get_summaries('zenith'),
|
player.get_summaries('zenith'),
|
||||||
player.get_summaries('zenithex'),
|
player.get_summaries('zenithex'),
|
||||||
|
player.get_summaries('achievements'),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
if sprint.data.record is not None:
|
if sprint.data.record is not None:
|
||||||
@@ -72,124 +73,133 @@ async def make_query_image_v2(player: Player) -> bytes:
|
|||||||
except FallbackError:
|
except FallbackError:
|
||||||
history = None
|
history = None
|
||||||
netloc = get_self_netloc()
|
netloc = get_self_netloc()
|
||||||
async with HostPage(
|
return await render_image(
|
||||||
await render(
|
Info(
|
||||||
'v2/tetrio/user/info',
|
user=User(
|
||||||
Info(
|
id=user.ID,
|
||||||
user=User(
|
name=user.name.upper(),
|
||||||
id=user.ID,
|
country=user_info.data.country,
|
||||||
name=user.name.upper(),
|
role=user_info.data.role,
|
||||||
country=user_info.data.country,
|
botmaster=user_info.data.botmaster,
|
||||||
role=user_info.data.role,
|
avatar=str(
|
||||||
botmaster=user_info.data.botmaster,
|
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}') % {'revision': avatar_revision}
|
||||||
avatar=str(
|
|
||||||
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}') % {'revision': avatar_revision}
|
|
||||||
)
|
|
||||||
if avatar_revision is not None and avatar_revision != 0
|
|
||||||
else Avatar(
|
|
||||||
type='identicon',
|
|
||||||
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
|
|
||||||
),
|
|
||||||
banner=str(
|
|
||||||
URL(f'http://{netloc}/host/resource/tetrio/banners/{user.ID}') % {'revision': banner_revision}
|
|
||||||
)
|
|
||||||
if banner_revision is not None and banner_revision != 0
|
|
||||||
else None,
|
|
||||||
bio=user_info.data.bio,
|
|
||||||
friend_count=user_info.data.friend_count,
|
|
||||||
supporter_tier=user_info.data.supporter_tier,
|
|
||||||
bad_standing=user_info.data.badstanding or False,
|
|
||||||
badges=[
|
|
||||||
Badge(
|
|
||||||
id=i.id,
|
|
||||||
description=i.label,
|
|
||||||
group=i.group,
|
|
||||||
receive_at=i.ts if isinstance(i.ts, datetime) else None,
|
|
||||||
)
|
|
||||||
for i in user_info.data.badges
|
|
||||||
],
|
|
||||||
xp=user_info.data.xp,
|
|
||||||
ar=user_info.data.ar,
|
|
||||||
achievements=user_info.data.achievements,
|
|
||||||
playtime=play_time,
|
|
||||||
join_at=user_info.data.ts,
|
|
||||||
),
|
|
||||||
tetra_league=TetraLeague(
|
|
||||||
rank=league.data.rank,
|
|
||||||
highest_rank='z' if isinstance(league.data, NeverRatedData) else league.data.bestrank,
|
|
||||||
tr=round(league.data.tr, 2),
|
|
||||||
glicko=round(league.data.glicko, 2),
|
|
||||||
rd=round(league.data.rd, 2),
|
|
||||||
global_rank=league.data.standing,
|
|
||||||
country_rank=league.data.standing_local,
|
|
||||||
pps=(metrics := get_metrics(pps=league.data.pps, apm=league.data.apm, vs=league.data.vs)).pps,
|
|
||||||
apm=metrics.apm,
|
|
||||||
apl=metrics.apl,
|
|
||||||
vs=metrics.vs,
|
|
||||||
adpl=metrics.adpl,
|
|
||||||
statistic=TetraLeagueStatistic(total=league.data.gamesplayed, wins=league.data.gameswon),
|
|
||||||
decaying=league.data.decaying,
|
|
||||||
history=history,
|
|
||||||
)
|
)
|
||||||
if not isinstance(league.data, NeverPlayedData | InvalidData)
|
if avatar_revision is not None and avatar_revision != 0
|
||||||
else None,
|
else Avatar(
|
||||||
zenith=Zenith(
|
type='identicon',
|
||||||
week=Week(
|
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
|
||||||
altitude=zenith.data.record.results.stats.zenith.altitude,
|
|
||||||
global_rank=zenith.data.rank,
|
|
||||||
country_rank=zenith.data.rank_local,
|
|
||||||
play_at=zenith.data.record.ts,
|
|
||||||
)
|
|
||||||
if zenith.data.record is not None
|
|
||||||
else None,
|
|
||||||
best=Best(
|
|
||||||
altitude=zenith.data.best.record.results.stats.zenith.altitude,
|
|
||||||
global_rank=zenith.data.best.rank,
|
|
||||||
play_at=zenith.data.best.record.ts,
|
|
||||||
)
|
|
||||||
if zenith.data.best.record is not None
|
|
||||||
else None,
|
|
||||||
),
|
),
|
||||||
zenithex=Zenith(
|
banner=str(
|
||||||
week=Week(
|
URL(f'http://{netloc}/host/resource/tetrio/banners/{user.ID}') % {'revision': banner_revision}
|
||||||
altitude=zenithex.data.record.results.stats.zenith.altitude,
|
|
||||||
global_rank=zenithex.data.rank,
|
|
||||||
country_rank=zenithex.data.rank_local,
|
|
||||||
play_at=zenithex.data.record.ts,
|
|
||||||
)
|
|
||||||
if zenithex.data.record is not None
|
|
||||||
else None,
|
|
||||||
best=Best(
|
|
||||||
altitude=zenithex.data.best.record.results.stats.zenith.altitude,
|
|
||||||
global_rank=zenithex.data.best.rank,
|
|
||||||
play_at=zenithex.data.best.record.ts,
|
|
||||||
)
|
|
||||||
if zenithex.data.best.record is not None
|
|
||||||
else None,
|
|
||||||
),
|
|
||||||
statistic=Statistic(
|
|
||||||
total=handling_special_value(user_info.data.gamesplayed),
|
|
||||||
wins=handling_special_value(user_info.data.gameswon),
|
|
||||||
),
|
|
||||||
sprint=Sprint(
|
|
||||||
time=sprint_value,
|
|
||||||
global_rank=sprint.data.rank,
|
|
||||||
country_rank=sprint.data.rank_local,
|
|
||||||
play_at=sprint.data.record.ts,
|
|
||||||
)
|
)
|
||||||
if sprint.data.record is not None
|
if banner_revision is not None and banner_revision != 0
|
||||||
else None,
|
else None,
|
||||||
blitz=Blitz(
|
bio=user_info.data.bio,
|
||||||
score=blitz.data.record.results.stats.score,
|
friend_count=user_info.data.friend_count,
|
||||||
global_rank=blitz.data.rank,
|
supporter_tier=user_info.data.supporter_tier,
|
||||||
country_rank=blitz.data.rank_local,
|
bad_standing=user_info.data.badstanding or False,
|
||||||
play_at=blitz.data.record.ts,
|
badges=[
|
||||||
)
|
Badge(
|
||||||
if blitz.data.record is not None
|
id=i.id,
|
||||||
else None,
|
description=i.label,
|
||||||
zen=Zen(level=zen.data.level, score=zen.data.score),
|
group=i.group,
|
||||||
lang=get_lang(),
|
receive_at=i.ts if isinstance(i.ts, datetime) else None,
|
||||||
|
)
|
||||||
|
for i in user_info.data.badges
|
||||||
|
],
|
||||||
|
xp=user_info.data.xp,
|
||||||
|
ar=user_info.data.ar,
|
||||||
|
achievements=[
|
||||||
|
Achievement(
|
||||||
|
key=i.achievement_id,
|
||||||
|
rank_type=i.rank_type,
|
||||||
|
ar_type=i.ar_type,
|
||||||
|
stub=i.stub,
|
||||||
|
rank=i.rank,
|
||||||
|
achieved_score=i.achieved_score,
|
||||||
|
pos=i.pos,
|
||||||
|
progress=i.progress,
|
||||||
|
total=i.total,
|
||||||
|
)
|
||||||
|
for i in achievements.data
|
||||||
|
],
|
||||||
|
playtime=play_time,
|
||||||
|
join_at=user_info.data.ts,
|
||||||
),
|
),
|
||||||
|
tetra_league=TetraLeague(
|
||||||
|
rank=league.data.rank,
|
||||||
|
highest_rank='z' if isinstance(league.data, NeverRatedData) else league.data.bestrank,
|
||||||
|
tr=round(league.data.tr, 2),
|
||||||
|
glicko=round(league.data.glicko, 2),
|
||||||
|
rd=round(league.data.rd, 2),
|
||||||
|
global_rank=league.data.standing,
|
||||||
|
country_rank=league.data.standing_local,
|
||||||
|
pps=(metrics := get_metrics(pps=league.data.pps, apm=league.data.apm, vs=league.data.vs)).pps,
|
||||||
|
apm=metrics.apm,
|
||||||
|
apl=metrics.apl,
|
||||||
|
vs=metrics.vs,
|
||||||
|
adpl=metrics.adpl,
|
||||||
|
statistic=TetraLeagueStatistic(total=league.data.gamesplayed, wins=league.data.gameswon),
|
||||||
|
decaying=league.data.decaying,
|
||||||
|
history=history,
|
||||||
|
)
|
||||||
|
if not isinstance(league.data, NeverPlayedData | InvalidData)
|
||||||
|
else None,
|
||||||
|
zenith=Zenith(
|
||||||
|
week=Week(
|
||||||
|
altitude=zenith.data.record.results.stats.zenith.altitude,
|
||||||
|
global_rank=zenith.data.rank,
|
||||||
|
country_rank=zenith.data.rank_local,
|
||||||
|
play_at=zenith.data.record.ts,
|
||||||
|
)
|
||||||
|
if zenith.data.record is not None
|
||||||
|
else None,
|
||||||
|
best=Best(
|
||||||
|
altitude=zenith.data.best.record.results.stats.zenith.altitude,
|
||||||
|
global_rank=zenith.data.best.rank,
|
||||||
|
play_at=zenith.data.best.record.ts,
|
||||||
|
)
|
||||||
|
if zenith.data.best.record is not None
|
||||||
|
else None,
|
||||||
|
),
|
||||||
|
zenithex=Zenith(
|
||||||
|
week=Week(
|
||||||
|
altitude=zenithex.data.record.results.stats.zenith.altitude,
|
||||||
|
global_rank=zenithex.data.rank,
|
||||||
|
country_rank=zenithex.data.rank_local,
|
||||||
|
play_at=zenithex.data.record.ts,
|
||||||
|
)
|
||||||
|
if zenithex.data.record is not None
|
||||||
|
else None,
|
||||||
|
best=Best(
|
||||||
|
altitude=zenithex.data.best.record.results.stats.zenith.altitude,
|
||||||
|
global_rank=zenithex.data.best.rank,
|
||||||
|
play_at=zenithex.data.best.record.ts,
|
||||||
|
)
|
||||||
|
if zenithex.data.best.record is not None
|
||||||
|
else None,
|
||||||
|
),
|
||||||
|
statistic=Statistic(
|
||||||
|
total=handling_special_value(user_info.data.gamesplayed),
|
||||||
|
wins=handling_special_value(user_info.data.gameswon),
|
||||||
|
),
|
||||||
|
sprint=Sprint(
|
||||||
|
time=sprint_value,
|
||||||
|
global_rank=sprint.data.rank,
|
||||||
|
country_rank=sprint.data.rank_local,
|
||||||
|
play_at=sprint.data.record.ts,
|
||||||
|
)
|
||||||
|
if sprint.data.record is not None
|
||||||
|
else None,
|
||||||
|
blitz=Blitz(
|
||||||
|
score=blitz.data.record.results.stats.score,
|
||||||
|
global_rank=blitz.data.rank,
|
||||||
|
country_rank=blitz.data.rank_local,
|
||||||
|
play_at=blitz.data.record.ts,
|
||||||
|
)
|
||||||
|
if blitz.data.record is not None
|
||||||
|
else None,
|
||||||
|
zen=Zen(level=zen.data.level, score=zen.data.score),
|
||||||
|
lang=get_lang(),
|
||||||
),
|
),
|
||||||
) as page_hash:
|
)
|
||||||
return await screenshot(f'http://{netloc}/host/{page_hash}.html')
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ from nonebot_plugin_apscheduler import scheduler
|
|||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
|
from ....config.config import config
|
||||||
from ....utils.exception import RequestError
|
from ....utils.exception import RequestError
|
||||||
from ....utils.retry import retry
|
from ....utils.retry import retry
|
||||||
from .. import alc
|
from .. import alc
|
||||||
@@ -136,14 +137,16 @@ async def get_tetra_league_data() -> None:
|
|||||||
await session.commit()
|
await session.commit()
|
||||||
|
|
||||||
|
|
||||||
@driver.on_startup
|
if not config.tetris.dev.enabled:
|
||||||
async def _() -> None:
|
|
||||||
async with get_session() as session:
|
@driver.on_startup
|
||||||
latest_time = await session.scalar(
|
async def _() -> None:
|
||||||
select(TETRIOLeagueStats.update_time).order_by(TETRIOLeagueStats.id.desc()).limit(1)
|
async with get_session() as session:
|
||||||
)
|
latest_time = await session.scalar(
|
||||||
if latest_time is None or datetime.now(tz=UTC) - latest_time.replace(tzinfo=UTC) > timedelta(hours=6):
|
select(TETRIOLeagueStats.update_time).order_by(TETRIOLeagueStats.id.desc()).limit(1)
|
||||||
await get_tetra_league_data()
|
)
|
||||||
|
if latest_time is None or datetime.now(tz=UTC) - latest_time.replace(tzinfo=UTC) > timedelta(hours=6):
|
||||||
|
await get_tetra_league_data()
|
||||||
|
|
||||||
|
|
||||||
from . import all, detail # noqa: A004, E402
|
from . import all, detail # noqa: A004, E402
|
||||||
|
|||||||
@@ -3,22 +3,20 @@ from datetime import timedelta
|
|||||||
from arclet.alconna import Arg
|
from arclet.alconna import Arg
|
||||||
from nonebot_plugin_alconna import Option, Subcommand, UniMessage
|
from nonebot_plugin_alconna import Option, Subcommand, UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from sqlalchemy import func, select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from ....db import trigger
|
from ....db import trigger
|
||||||
from ....utils.host import HostPage, get_self_netloc
|
|
||||||
from ....utils.lang import get_lang
|
from ....utils.lang import get_lang
|
||||||
from ....utils.metrics import get_metrics
|
from ....utils.metrics import get_metrics
|
||||||
from ....utils.render import render
|
from ....utils.render import render_image
|
||||||
from ....utils.render.schemas.v1.tetrio.rank import Data as DataV1
|
from ....utils.render.schemas.v1.tetrio.rank import Data as DataV1
|
||||||
from ....utils.render.schemas.v1.tetrio.rank import ItemData as ItemDataV1
|
from ....utils.render.schemas.v1.tetrio.rank import ItemData as ItemDataV1
|
||||||
from ....utils.render.schemas.v2.tetrio.rank import AverageData as AverageDataV2
|
from ....utils.render.schemas.v2.tetrio.rank import AverageData as AverageDataV2
|
||||||
from ....utils.render.schemas.v2.tetrio.rank import Data as DataV2
|
from ....utils.render.schemas.v2.tetrio.rank import Data as DataV2
|
||||||
from ....utils.render.schemas.v2.tetrio.rank import ItemData as ItemDataV2
|
from ....utils.render.schemas.v2.tetrio.rank import ItemData as ItemDataV2
|
||||||
from ....utils.screenshot import screenshot
|
|
||||||
from .. import alc
|
from .. import alc
|
||||||
from ..constant import GAME_TYPE
|
from ..constant import GAME_TYPE
|
||||||
from ..models import TETRIOLeagueStats
|
from ..models import TETRIOLeagueStats
|
||||||
@@ -33,7 +31,7 @@ command.add(
|
|||||||
|
|
||||||
|
|
||||||
@alc.assign('TETRIO.rank.all')
|
@alc.assign('TETRIO.rank.all')
|
||||||
async def _(event_session: EventSession, template: Template | None = None):
|
async def _(event_session: Uninfo, template: Template | None = None):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
@@ -41,6 +39,7 @@ async def _(event_session: EventSession, template: Template | None = None):
|
|||||||
command_args=['--all'] + ([f'--template {template}'] if template is not None else []),
|
command_args=['--all'] + ([f'--template {template}'] if template is not None else []),
|
||||||
):
|
):
|
||||||
async with get_session() as session:
|
async with get_session() as session:
|
||||||
|
# 获取最新记录
|
||||||
latest_data = (
|
latest_data = (
|
||||||
await session.scalars(
|
await session.scalars(
|
||||||
select(TETRIOLeagueStats)
|
select(TETRIOLeagueStats)
|
||||||
@@ -49,19 +48,42 @@ async def _(event_session: EventSession, template: Template | None = None):
|
|||||||
.options(selectinload(TETRIOLeagueStats.fields))
|
.options(selectinload(TETRIOLeagueStats.fields))
|
||||||
)
|
)
|
||||||
).one()
|
).one()
|
||||||
compare_data = (
|
|
||||||
await session.scalars(
|
# 计算目标时间点 (24小时前)
|
||||||
|
target_time = latest_data.update_time - timedelta(hours=24)
|
||||||
|
|
||||||
|
# 查询目标时间点之前的最近记录
|
||||||
|
before = (
|
||||||
|
await session.scalar(
|
||||||
select(TETRIOLeagueStats)
|
select(TETRIOLeagueStats)
|
||||||
.order_by(
|
.where(TETRIOLeagueStats.update_time <= target_time)
|
||||||
func.abs(
|
.order_by(TETRIOLeagueStats.update_time.desc())
|
||||||
func.julianday(TETRIOLeagueStats.update_time)
|
|
||||||
- func.julianday(latest_data.update_time - timedelta(hours=24))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.options(selectinload(TETRIOLeagueStats.fields))
|
.options(selectinload(TETRIOLeagueStats.fields))
|
||||||
)
|
)
|
||||||
).one()
|
or latest_data
|
||||||
|
)
|
||||||
|
|
||||||
|
# 查询目标时间点之后的最近记录
|
||||||
|
after = (
|
||||||
|
await session.scalar(
|
||||||
|
select(TETRIOLeagueStats)
|
||||||
|
.where(TETRIOLeagueStats.update_time >= target_time) # 使用 >= 避免间隙
|
||||||
|
.order_by(TETRIOLeagueStats.update_time.asc())
|
||||||
|
.limit(1)
|
||||||
|
.options(selectinload(TETRIOLeagueStats.fields))
|
||||||
|
)
|
||||||
|
or latest_data
|
||||||
|
)
|
||||||
|
|
||||||
|
# 确定最接近的记录
|
||||||
|
compare_data = (
|
||||||
|
before
|
||||||
|
if abs((target_time - before.update_time).total_seconds())
|
||||||
|
< abs((target_time - after.update_time).total_seconds())
|
||||||
|
else after
|
||||||
|
)
|
||||||
|
|
||||||
match template:
|
match template:
|
||||||
case 'v1' | None:
|
case 'v1' | None:
|
||||||
await UniMessage.image(raw=await make_image_v1(latest_data, compare_data)).finish()
|
await UniMessage.image(raw=await make_image_v1(latest_data, compare_data)).finish()
|
||||||
@@ -70,49 +92,41 @@ async def _(event_session: EventSession, template: Template | None = None):
|
|||||||
|
|
||||||
|
|
||||||
async def make_image_v1(latest_data: TETRIOLeagueStats, compare_data: TETRIOLeagueStats) -> bytes:
|
async def make_image_v1(latest_data: TETRIOLeagueStats, compare_data: TETRIOLeagueStats) -> bytes:
|
||||||
async with HostPage(
|
return await render_image(
|
||||||
await render(
|
DataV1(
|
||||||
'v1/tetrio/rank',
|
items={
|
||||||
DataV1(
|
i[0].rank: ItemDataV1(
|
||||||
items={
|
trending=round(i[0].tr_line - i[1].tr_line, 2),
|
||||||
i[0].rank: ItemDataV1(
|
require_tr=round(i[0].tr_line, 2),
|
||||||
trending=round(i[0].tr_line - i[1].tr_line, 2),
|
players=i[0].player_count,
|
||||||
require_tr=round(i[0].tr_line, 2),
|
)
|
||||||
players=i[0].player_count,
|
for i in zip(latest_data.fields, compare_data.fields, strict=True)
|
||||||
)
|
},
|
||||||
for i in zip(latest_data.fields, compare_data.fields, strict=True)
|
updated_at=latest_data.update_time,
|
||||||
},
|
lang=get_lang(),
|
||||||
updated_at=latest_data.update_time,
|
),
|
||||||
lang=get_lang(),
|
)
|
||||||
),
|
|
||||||
)
|
|
||||||
) as page_hash:
|
|
||||||
return await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
|
|
||||||
|
|
||||||
|
|
||||||
async def make_image_v2(latest_data: TETRIOLeagueStats, compare_data: TETRIOLeagueStats) -> bytes:
|
async def make_image_v2(latest_data: TETRIOLeagueStats, compare_data: TETRIOLeagueStats) -> bytes:
|
||||||
async with HostPage(
|
return await render_image(
|
||||||
await render(
|
DataV2(
|
||||||
'v2/tetrio/rank',
|
items={
|
||||||
DataV2(
|
i[0].rank: ItemDataV2(
|
||||||
items={
|
require_tr=round(i[0].tr_line, 2),
|
||||||
i[0].rank: ItemDataV2(
|
trending=round(i[0].tr_line - i[1].tr_line, 2),
|
||||||
require_tr=round(i[0].tr_line, 2),
|
average_data=AverageDataV2(
|
||||||
trending=round(i[0].tr_line - i[1].tr_line, 2),
|
pps=(metrics := get_metrics(pps=i[0].avg_pps, apm=i[0].avg_apm, vs=i[0].avg_vs)).pps,
|
||||||
average_data=AverageDataV2(
|
apm=metrics.apm,
|
||||||
pps=(metrics := get_metrics(pps=i[0].avg_pps, apm=i[0].avg_apm, vs=i[0].avg_vs)).pps,
|
apl=metrics.apl,
|
||||||
apm=metrics.apm,
|
vs=metrics.vs,
|
||||||
apl=metrics.apl,
|
adpl=metrics.adpl,
|
||||||
vs=metrics.vs,
|
),
|
||||||
adpl=metrics.adpl,
|
players=i[0].player_count,
|
||||||
),
|
)
|
||||||
players=i[0].player_count,
|
for i in zip(latest_data.fields, compare_data.fields, strict=True)
|
||||||
)
|
},
|
||||||
for i in zip(latest_data.fields, compare_data.fields, strict=True)
|
updated_at=latest_data.update_time,
|
||||||
},
|
lang=get_lang(),
|
||||||
updated_at=latest_data.update_time,
|
),
|
||||||
lang=get_lang(),
|
)
|
||||||
),
|
|
||||||
)
|
|
||||||
) as page_hash:
|
|
||||||
return await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
|
|
||||||
|
|||||||
@@ -5,18 +5,16 @@ from arclet.alconna import Arg
|
|||||||
from nonebot import get_driver
|
from nonebot import get_driver
|
||||||
from nonebot_plugin_alconna import Option, UniMessage
|
from nonebot_plugin_alconna import Option, UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from sqlalchemy import func, select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import selectinload
|
from sqlalchemy.orm import selectinload
|
||||||
|
|
||||||
from ....db import trigger
|
from ....db import trigger
|
||||||
from ....utils.host import HostPage, get_self_netloc
|
|
||||||
from ....utils.lang import get_lang
|
from ....utils.lang import get_lang
|
||||||
from ....utils.metrics import get_metrics
|
from ....utils.metrics import get_metrics
|
||||||
from ....utils.render import render
|
from ....utils.render import render_image
|
||||||
from ....utils.render.schemas.v2.tetrio.rank.detail import Data, SpecialData
|
from ....utils.render.schemas.v2.tetrio.rank.detail import Data, SpecialData
|
||||||
from ....utils.screenshot import screenshot
|
|
||||||
from .. import alc
|
from .. import alc
|
||||||
from ..api.typedefs import ValidRank
|
from ..api.typedefs import ValidRank
|
||||||
from ..constant import GAME_TYPE
|
from ..constant import GAME_TYPE
|
||||||
@@ -31,7 +29,7 @@ command.add(Option('--detail', Arg('rank', ValidRank), alias=['-D']))
|
|||||||
|
|
||||||
|
|
||||||
@alc.assign('TETRIO.rank')
|
@alc.assign('TETRIO.rank')
|
||||||
async def _(rank: ValidRank, event_session: EventSession):
|
async def _(rank: ValidRank, event_session: Uninfo):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
@@ -39,6 +37,7 @@ async def _(rank: ValidRank, event_session: EventSession):
|
|||||||
command_args=[f'--detail {rank}'],
|
command_args=[f'--detail {rank}'],
|
||||||
):
|
):
|
||||||
async with get_session() as session:
|
async with get_session() as session:
|
||||||
|
# 获取最新记录
|
||||||
latest_data = (
|
latest_data = (
|
||||||
await session.scalars(
|
await session.scalars(
|
||||||
select(TETRIOLeagueStats)
|
select(TETRIOLeagueStats)
|
||||||
@@ -47,19 +46,41 @@ async def _(rank: ValidRank, event_session: EventSession):
|
|||||||
.options(selectinload(TETRIOLeagueStats.fields))
|
.options(selectinload(TETRIOLeagueStats.fields))
|
||||||
)
|
)
|
||||||
).one()
|
).one()
|
||||||
compare_data = (
|
|
||||||
await session.scalars(
|
# 计算目标时间点 (24小时前)
|
||||||
|
target_time = latest_data.update_time - timedelta(hours=24)
|
||||||
|
|
||||||
|
# 查询目标时间点之前的最近记录
|
||||||
|
before = (
|
||||||
|
await session.scalar(
|
||||||
select(TETRIOLeagueStats)
|
select(TETRIOLeagueStats)
|
||||||
.order_by(
|
.where(TETRIOLeagueStats.update_time <= target_time)
|
||||||
func.abs(
|
.order_by(TETRIOLeagueStats.update_time.desc())
|
||||||
func.julianday(TETRIOLeagueStats.update_time)
|
|
||||||
- func.julianday(latest_data.update_time - timedelta(hours=24))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.options(selectinload(TETRIOLeagueStats.fields))
|
.options(selectinload(TETRIOLeagueStats.fields))
|
||||||
)
|
)
|
||||||
).one()
|
or latest_data # 回退到最新记录
|
||||||
|
)
|
||||||
|
|
||||||
|
# 查询目标时间点之后的最近记录
|
||||||
|
after = (
|
||||||
|
await session.scalar(
|
||||||
|
select(TETRIOLeagueStats)
|
||||||
|
.where(TETRIOLeagueStats.update_time >= target_time)
|
||||||
|
.order_by(TETRIOLeagueStats.update_time.asc())
|
||||||
|
.limit(1)
|
||||||
|
.options(selectinload(TETRIOLeagueStats.fields))
|
||||||
|
)
|
||||||
|
or latest_data # 回退到最新记录
|
||||||
|
)
|
||||||
|
|
||||||
|
# 确定最接近的记录
|
||||||
|
compare_data = (
|
||||||
|
before
|
||||||
|
if abs((target_time - before.update_time).total_seconds())
|
||||||
|
< abs((target_time - after.update_time).total_seconds())
|
||||||
|
else after
|
||||||
|
)
|
||||||
await UniMessage.image(
|
await UniMessage.image(
|
||||||
raw=await make_image(
|
raw=await make_image(
|
||||||
rank,
|
rank,
|
||||||
@@ -91,40 +112,36 @@ async def make_image(rank: ValidRank, latest: TETRIOLeagueStats, compare: TETRIO
|
|||||||
max_vs = get_metrics(
|
max_vs = get_metrics(
|
||||||
pps=latest_data.high_vs.league.pps, apm=latest_data.high_vs.league.apm, vs=latest_data.high_vs.league.vs
|
pps=latest_data.high_vs.league.pps, apm=latest_data.high_vs.league.apm, vs=latest_data.high_vs.league.vs
|
||||||
)
|
)
|
||||||
async with HostPage(
|
return await render_image(
|
||||||
await render(
|
Data(
|
||||||
'v2/tetrio/rank/detail',
|
name=latest_data.rank,
|
||||||
Data(
|
trending=round(latest_data.tr_line - compare_data.tr_line, 2),
|
||||||
name=latest_data.rank,
|
require_tr=round(latest_data.tr_line, 2),
|
||||||
trending=round(latest_data.tr_line - compare_data.tr_line, 2),
|
players=latest_data.player_count,
|
||||||
require_tr=round(latest_data.tr_line, 2),
|
minimum_data=SpecialData(
|
||||||
players=latest_data.player_count,
|
apm=low_apm.apm,
|
||||||
minimum_data=SpecialData(
|
pps=low_pps.pps,
|
||||||
apm=low_apm.apm,
|
lpm=low_pps.lpm,
|
||||||
pps=low_pps.pps,
|
vs=low_vs.vs,
|
||||||
lpm=low_pps.lpm,
|
adpm=low_vs.adpm,
|
||||||
vs=low_vs.vs,
|
apm_holder=latest_data.low_apm.username.upper(),
|
||||||
adpm=low_vs.adpm,
|
pps_holder=latest_data.low_pps.username.upper(),
|
||||||
apm_holder=latest_data.low_apm.username.upper(),
|
vs_holder=latest_data.low_vs.username.upper(),
|
||||||
pps_holder=latest_data.low_pps.username.upper(),
|
|
||||||
vs_holder=latest_data.low_vs.username.upper(),
|
|
||||||
),
|
|
||||||
average_data=SpecialData(
|
|
||||||
apm=avg.apm, pps=avg.pps, lpm=avg.lpm, vs=avg.vs, adpm=avg.adpm, apl=avg.apl, adpl=avg.adpl
|
|
||||||
),
|
|
||||||
maximum_data=SpecialData(
|
|
||||||
apm=max_apm.apm,
|
|
||||||
pps=max_pps.pps,
|
|
||||||
lpm=max_pps.lpm,
|
|
||||||
vs=max_vs.vs,
|
|
||||||
adpm=max_vs.adpm,
|
|
||||||
apm_holder=latest_data.high_apm.username.upper(),
|
|
||||||
pps_holder=latest_data.high_pps.username.upper(),
|
|
||||||
vs_holder=latest_data.high_vs.username.upper(),
|
|
||||||
),
|
|
||||||
updated_at=latest.update_time.replace(tzinfo=UTC).astimezone(ZoneInfo('Asia/Shanghai')),
|
|
||||||
lang=get_lang(),
|
|
||||||
),
|
),
|
||||||
)
|
average_data=SpecialData(
|
||||||
) as page_hash:
|
apm=avg.apm, pps=avg.pps, lpm=avg.lpm, vs=avg.vs, adpm=avg.adpm, apl=avg.apl, adpl=avg.adpl
|
||||||
return await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
|
),
|
||||||
|
maximum_data=SpecialData(
|
||||||
|
apm=max_apm.apm,
|
||||||
|
pps=max_pps.pps,
|
||||||
|
lpm=max_pps.lpm,
|
||||||
|
vs=max_vs.vs,
|
||||||
|
adpm=max_vs.adpm,
|
||||||
|
apm_holder=latest_data.high_apm.username.upper(),
|
||||||
|
pps_holder=latest_data.high_pps.username.upper(),
|
||||||
|
vs_holder=latest_data.high_vs.username.upper(),
|
||||||
|
),
|
||||||
|
updated_at=latest.update_time.replace(tzinfo=UTC).astimezone(ZoneInfo('Asia/Shanghai')),
|
||||||
|
lang=get_lang(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|||||||
@@ -7,22 +7,21 @@ from nonebot.matcher import Matcher
|
|||||||
from nonebot_plugin_alconna import At, Option
|
from nonebot_plugin_alconna import At, Option
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import get_user
|
from nonebot_plugin_user import get_user
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
from ....db import query_bind_info, trigger
|
from ....db import query_bind_info, trigger
|
||||||
from ....i18n import Lang
|
from ....i18n import Lang
|
||||||
from ....utils.exception import RecordNotFoundError
|
from ....utils.exception import RecordNotFoundError
|
||||||
from ....utils.host import HostPage, get_self_netloc
|
from ....utils.host import get_self_netloc
|
||||||
from ....utils.lang import get_lang
|
from ....utils.lang import get_lang
|
||||||
from ....utils.metrics import get_metrics
|
from ....utils.metrics import get_metrics
|
||||||
from ....utils.render import render
|
from ....utils.render import render_image
|
||||||
from ....utils.render.schemas.base import Avatar
|
from ....utils.render.schemas.base import Avatar
|
||||||
from ....utils.render.schemas.v2.tetrio.record.base import Finesse, Max, Mini, Tspins, User
|
from ....utils.render.schemas.v2.tetrio.record.base import Finesse, Max, Mini, Tspins, User
|
||||||
from ....utils.render.schemas.v2.tetrio.record.blitz import Record, Statistic
|
from ....utils.render.schemas.v2.tetrio.record.blitz import Record, Statistic
|
||||||
from ....utils.screenshot import screenshot
|
|
||||||
from ....utils.typedefs import Me
|
from ....utils.typedefs import Me
|
||||||
from .. import alc
|
from .. import alc
|
||||||
from ..api.player import Player
|
from ..api.player import Player
|
||||||
@@ -43,7 +42,7 @@ async def _(
|
|||||||
event: Event,
|
event: Event,
|
||||||
matcher: Matcher,
|
matcher: Matcher,
|
||||||
target: At | Me,
|
target: At | Me,
|
||||||
event_session: EventSession,
|
event_session: Uninfo,
|
||||||
):
|
):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
@@ -55,7 +54,7 @@ async def _(
|
|||||||
bind = await query_bind_info(
|
bind = await query_bind_info(
|
||||||
session=session,
|
session=session,
|
||||||
user=await get_user(
|
user=await get_user(
|
||||||
event_session.platform, target.target if isinstance(target, At) else event.get_user_id()
|
event_session.scope, target.target if isinstance(target, At) else event.get_user_id()
|
||||||
),
|
),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
)
|
)
|
||||||
@@ -68,7 +67,7 @@ async def _(
|
|||||||
|
|
||||||
|
|
||||||
@alc.assign('TETRIO.record.blitz')
|
@alc.assign('TETRIO.record.blitz')
|
||||||
async def _(account: Player, event_session: EventSession):
|
async def _(account: Player, event_session: Uninfo):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
@@ -88,66 +87,62 @@ async def make_blitz_image(player: Player) -> bytes:
|
|||||||
duration = timedelta(milliseconds=stats.finaltime).total_seconds()
|
duration = timedelta(milliseconds=stats.finaltime).total_seconds()
|
||||||
metrics = get_metrics(pps=stats.piecesplaced / duration)
|
metrics = get_metrics(pps=stats.piecesplaced / duration)
|
||||||
netloc = get_self_netloc()
|
netloc = get_self_netloc()
|
||||||
async with HostPage(
|
return await render_image(
|
||||||
page=await render(
|
Record(
|
||||||
'v2/tetrio/record/blitz',
|
type='best',
|
||||||
Record(
|
user=User(
|
||||||
type='best',
|
id=user.ID,
|
||||||
user=User(
|
name=user.name.upper(),
|
||||||
id=user.ID,
|
avatar=str(
|
||||||
name=user.name.upper(),
|
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}') % {'revision': avatar_revision}
|
||||||
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(
|
||||||
if (avatar_revision := (await player.avatar_revision)) is not None and avatar_revision != 0
|
type='identicon',
|
||||||
else Avatar(
|
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
|
||||||
type='identicon',
|
|
||||||
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
replay_id=blitz.data.record.replayid,
|
|
||||||
rank=blitz.data.rank,
|
|
||||||
personal_rank=1,
|
|
||||||
statistic=Statistic(
|
|
||||||
keys=stats.inputs,
|
|
||||||
kpp=round(stats.inputs / stats.piecesplaced, 2),
|
|
||||||
kps=round(stats.inputs / duration, 2),
|
|
||||||
max=Max(
|
|
||||||
combo=max((0, stats.topcombo - 1)),
|
|
||||||
btb=max((0, stats.topbtb - 1)),
|
|
||||||
),
|
|
||||||
pieces=stats.piecesplaced,
|
|
||||||
pps=metrics.pps,
|
|
||||||
lines=stats.lines,
|
|
||||||
lpm=metrics.lpm,
|
|
||||||
holds=stats.holds,
|
|
||||||
score=stats.score,
|
|
||||||
spp=round(stats.score / stats.piecesplaced, 2),
|
|
||||||
single=clears.singles,
|
|
||||||
double=clears.doubles,
|
|
||||||
triple=clears.triples,
|
|
||||||
quad=clears.quads,
|
|
||||||
tspins=Tspins(
|
|
||||||
total=clears.realtspins,
|
|
||||||
single=clears.tspinsingles,
|
|
||||||
double=clears.tspindoubles,
|
|
||||||
triple=clears.tspintriples,
|
|
||||||
mini=Mini(
|
|
||||||
total=clears.minitspins,
|
|
||||||
single=clears.minitspinsingles,
|
|
||||||
double=clears.minitspindoubles,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
all_clear=clears.allclear,
|
|
||||||
finesse=Finesse(
|
|
||||||
faults=stats.finesse.faults,
|
|
||||||
accuracy=round(stats.finesse.perfectpieces / stats.piecesplaced * 100, 2),
|
|
||||||
),
|
|
||||||
level=stats.level,
|
|
||||||
),
|
|
||||||
play_at=blitz.data.record.ts,
|
|
||||||
lang=get_lang(),
|
|
||||||
),
|
),
|
||||||
)
|
replay_id=blitz.data.record.replayid,
|
||||||
) as page_hash:
|
rank=blitz.data.rank,
|
||||||
return await screenshot(f'http://{netloc}/host/{page_hash}.html')
|
personal_rank=1,
|
||||||
|
statistic=Statistic(
|
||||||
|
keys=stats.inputs,
|
||||||
|
kpp=round(stats.inputs / stats.piecesplaced, 2),
|
||||||
|
kps=round(stats.inputs / duration, 2),
|
||||||
|
max=Max(
|
||||||
|
combo=max((0, stats.topcombo - 1)),
|
||||||
|
btb=max((0, stats.topbtb - 1)),
|
||||||
|
),
|
||||||
|
pieces=stats.piecesplaced,
|
||||||
|
pps=metrics.pps,
|
||||||
|
lines=stats.lines,
|
||||||
|
lpm=metrics.lpm,
|
||||||
|
holds=stats.holds,
|
||||||
|
score=stats.score,
|
||||||
|
spp=round(stats.score / stats.piecesplaced, 2),
|
||||||
|
single=clears.singles,
|
||||||
|
double=clears.doubles,
|
||||||
|
triple=clears.triples,
|
||||||
|
quad=clears.quads,
|
||||||
|
tspins=Tspins(
|
||||||
|
total=clears.realtspins,
|
||||||
|
single=clears.tspinsingles,
|
||||||
|
double=clears.tspindoubles,
|
||||||
|
triple=clears.tspintriples,
|
||||||
|
mini=Mini(
|
||||||
|
total=clears.minitspins,
|
||||||
|
single=clears.minitspinsingles,
|
||||||
|
double=clears.minitspindoubles,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
all_clear=clears.allclear,
|
||||||
|
finesse=Finesse(
|
||||||
|
faults=stats.finesse.faults,
|
||||||
|
accuracy=round(stats.finesse.perfectpieces / stats.piecesplaced * 100, 2),
|
||||||
|
),
|
||||||
|
level=stats.level,
|
||||||
|
),
|
||||||
|
play_at=blitz.data.record.ts,
|
||||||
|
lang=get_lang(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|||||||
@@ -7,22 +7,21 @@ from nonebot.matcher import Matcher
|
|||||||
from nonebot_plugin_alconna import At, Option
|
from nonebot_plugin_alconna import At, Option
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import get_user
|
from nonebot_plugin_user import get_user
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
from ....db import query_bind_info, trigger
|
from ....db import query_bind_info, trigger
|
||||||
from ....i18n import Lang
|
from ....i18n import Lang
|
||||||
from ....utils.exception import RecordNotFoundError
|
from ....utils.exception import RecordNotFoundError
|
||||||
from ....utils.host import HostPage, get_self_netloc
|
from ....utils.host import get_self_netloc
|
||||||
from ....utils.lang import get_lang
|
from ....utils.lang import get_lang
|
||||||
from ....utils.metrics import get_metrics
|
from ....utils.metrics import get_metrics
|
||||||
from ....utils.render import render
|
from ....utils.render import render_image
|
||||||
from ....utils.render.schemas.base import Avatar
|
from ....utils.render.schemas.base import Avatar
|
||||||
from ....utils.render.schemas.v2.tetrio.record.base import Finesse, Max, Mini, Statistic, Tspins, User
|
from ....utils.render.schemas.v2.tetrio.record.base import Finesse, Max, Mini, Statistic, Tspins, User
|
||||||
from ....utils.render.schemas.v2.tetrio.record.sprint import Record
|
from ....utils.render.schemas.v2.tetrio.record.sprint import Record
|
||||||
from ....utils.screenshot import screenshot
|
|
||||||
from ....utils.typedefs import Me
|
from ....utils.typedefs import Me
|
||||||
from .. import alc
|
from .. import alc
|
||||||
from ..api.player import Player
|
from ..api.player import Player
|
||||||
@@ -43,7 +42,7 @@ async def _(
|
|||||||
event: Event,
|
event: Event,
|
||||||
matcher: Matcher,
|
matcher: Matcher,
|
||||||
target: At | Me,
|
target: At | Me,
|
||||||
event_session: EventSession,
|
event_session: Uninfo,
|
||||||
):
|
):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
@@ -55,7 +54,7 @@ async def _(
|
|||||||
bind = await query_bind_info(
|
bind = await query_bind_info(
|
||||||
session=session,
|
session=session,
|
||||||
user=await get_user(
|
user=await get_user(
|
||||||
event_session.platform, target.target if isinstance(target, At) else event.get_user_id()
|
event_session.scope, target.target if isinstance(target, At) else event.get_user_id()
|
||||||
),
|
),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
)
|
)
|
||||||
@@ -68,7 +67,7 @@ async def _(
|
|||||||
|
|
||||||
|
|
||||||
@alc.assign('TETRIO.record.sprint')
|
@alc.assign('TETRIO.record.sprint')
|
||||||
async def _(account: Player, event_session: EventSession):
|
async def _(account: Player, event_session: Uninfo):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
@@ -89,65 +88,61 @@ async def make_sprint_image(player: Player) -> bytes:
|
|||||||
sprint_value = f'{duration:.3f}s' if duration < 60 else f'{duration // 60:.0f}m {duration % 60:.3f}s' # noqa: PLR2004
|
sprint_value = f'{duration:.3f}s' if duration < 60 else f'{duration // 60:.0f}m {duration % 60:.3f}s' # noqa: PLR2004
|
||||||
metrics = get_metrics(pps=stats.piecesplaced / duration)
|
metrics = get_metrics(pps=stats.piecesplaced / duration)
|
||||||
netloc = get_self_netloc()
|
netloc = get_self_netloc()
|
||||||
async with HostPage(
|
return await render_image(
|
||||||
page=await render(
|
Record(
|
||||||
'v2/tetrio/record/sprint',
|
type='best',
|
||||||
Record(
|
user=User(
|
||||||
type='best',
|
id=user.ID,
|
||||||
user=User(
|
name=user.name.upper(),
|
||||||
id=user.ID,
|
avatar=str(
|
||||||
name=user.name.upper(),
|
URL(f'http://{netloc}/host/resource/tetrio/avatars/{user.ID}') % {'revision': avatar_revision}
|
||||||
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(
|
||||||
if (avatar_revision := (await player.avatar_revision)) is not None and avatar_revision != 0
|
type='identicon',
|
||||||
else Avatar(
|
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
|
||||||
type='identicon',
|
|
||||||
hash=md5(user.ID.encode()).hexdigest(), # noqa: S324
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
time=sprint_value,
|
|
||||||
replay_id=sprint.data.record.replayid,
|
|
||||||
rank=sprint.data.rank,
|
|
||||||
personal_rank=1,
|
|
||||||
statistic=Statistic(
|
|
||||||
keys=stats.inputs,
|
|
||||||
kpp=round(stats.inputs / stats.piecesplaced, 2),
|
|
||||||
kps=round(stats.inputs / duration, 2),
|
|
||||||
max=Max(
|
|
||||||
combo=max((0, stats.topcombo - 1)),
|
|
||||||
btb=max((0, stats.topbtb - 1)),
|
|
||||||
),
|
|
||||||
pieces=stats.piecesplaced,
|
|
||||||
pps=metrics.pps,
|
|
||||||
lines=stats.lines,
|
|
||||||
lpm=metrics.lpm,
|
|
||||||
holds=stats.holds,
|
|
||||||
score=stats.score,
|
|
||||||
single=clears.singles,
|
|
||||||
double=clears.doubles,
|
|
||||||
triple=clears.triples,
|
|
||||||
quad=clears.quads,
|
|
||||||
tspins=Tspins(
|
|
||||||
total=clears.realtspins,
|
|
||||||
single=clears.tspinsingles,
|
|
||||||
double=clears.tspindoubles,
|
|
||||||
triple=clears.tspintriples,
|
|
||||||
mini=Mini(
|
|
||||||
total=clears.minitspins,
|
|
||||||
single=clears.minitspinsingles,
|
|
||||||
double=clears.minitspindoubles,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
all_clear=clears.allclear,
|
|
||||||
finesse=Finesse(
|
|
||||||
faults=stats.finesse.faults,
|
|
||||||
accuracy=round(stats.finesse.perfectpieces / stats.piecesplaced * 100, 2),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
play_at=sprint.data.record.ts,
|
|
||||||
lang=get_lang(),
|
|
||||||
),
|
),
|
||||||
)
|
time=sprint_value,
|
||||||
) as page_hash:
|
replay_id=sprint.data.record.replayid,
|
||||||
return await screenshot(f'http://{netloc}/host/{page_hash}.html')
|
rank=sprint.data.rank,
|
||||||
|
personal_rank=1,
|
||||||
|
statistic=Statistic(
|
||||||
|
keys=stats.inputs,
|
||||||
|
kpp=round(stats.inputs / stats.piecesplaced, 2),
|
||||||
|
kps=round(stats.inputs / duration, 2),
|
||||||
|
max=Max(
|
||||||
|
combo=max((0, stats.topcombo - 1)),
|
||||||
|
btb=max((0, stats.topbtb - 1)),
|
||||||
|
),
|
||||||
|
pieces=stats.piecesplaced,
|
||||||
|
pps=metrics.pps,
|
||||||
|
lines=stats.lines,
|
||||||
|
lpm=metrics.lpm,
|
||||||
|
holds=stats.holds,
|
||||||
|
score=stats.score,
|
||||||
|
single=clears.singles,
|
||||||
|
double=clears.doubles,
|
||||||
|
triple=clears.triples,
|
||||||
|
quad=clears.quads,
|
||||||
|
tspins=Tspins(
|
||||||
|
total=clears.realtspins,
|
||||||
|
single=clears.tspinsingles,
|
||||||
|
double=clears.tspindoubles,
|
||||||
|
triple=clears.tspintriples,
|
||||||
|
mini=Mini(
|
||||||
|
total=clears.minitspins,
|
||||||
|
single=clears.minitspinsingles,
|
||||||
|
double=clears.minitspindoubles,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
all_clear=clears.allclear,
|
||||||
|
finesse=Finesse(
|
||||||
|
faults=stats.finesse.faults,
|
||||||
|
accuracy=round(stats.finesse.perfectpieces / stats.piecesplaced * 100, 2),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
play_at=sprint.data.record.ts,
|
||||||
|
lang=get_lang(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,22 +1,24 @@
|
|||||||
from hashlib import md5
|
from hashlib import md5
|
||||||
|
from secrets import choice
|
||||||
|
|
||||||
from nonebot_plugin_alconna import Subcommand
|
from nonebot_plugin_alconna import Subcommand
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import QryItrface, Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo import User as UninfoUser
|
||||||
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import User
|
from nonebot_plugin_user import User
|
||||||
from nonebot_plugin_userinfo import BotUserInfo, UserInfo
|
|
||||||
from nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
|
from nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
|
from ...config.config import global_config
|
||||||
from ...db import query_bind_info, remove_bind, trigger
|
from ...db import query_bind_info, remove_bind, trigger
|
||||||
from ...utils.host import HostPage, get_self_netloc
|
from ...utils.host import get_self_netloc
|
||||||
from ...utils.image import get_avatar
|
from ...utils.image import get_avatar
|
||||||
from ...utils.lang import get_lang
|
from ...utils.lang import get_lang
|
||||||
from ...utils.render import Bind, render
|
from ...utils.render import render_image
|
||||||
from ...utils.render.schemas.base import Avatar, People
|
from ...utils.render.schemas.base import Avatar, People
|
||||||
from ...utils.screenshot import screenshot
|
from ...utils.render.schemas.bind import Bind
|
||||||
from . import alc, command
|
from . import alc, command
|
||||||
from .api import Player
|
from .api import Player
|
||||||
from .constant import GAME_TYPE
|
from .constant import GAME_TYPE
|
||||||
@@ -31,7 +33,7 @@ alc.shortcut(
|
|||||||
|
|
||||||
|
|
||||||
@alc.assign('TETRIO.unbind')
|
@alc.assign('TETRIO.unbind')
|
||||||
async def _(nb_user: User, event_session: EventSession, bot_info: UserInfo = BotUserInfo()): # noqa: B008
|
async def _(nb_user: User, event_session: Uninfo, interface: QryItrface):
|
||||||
async with (
|
async with (
|
||||||
trigger(
|
trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
@@ -49,9 +51,8 @@ async def _(nb_user: User, event_session: EventSession, bot_info: UserInfo = Bot
|
|||||||
player = Player(user_id=bind.game_account, trust=True)
|
player = Player(user_id=bind.game_account, trust=True)
|
||||||
user = await player.user
|
user = await player.user
|
||||||
netloc = get_self_netloc()
|
netloc = get_self_netloc()
|
||||||
async with HostPage(
|
await UniMessage.image(
|
||||||
await render(
|
raw=await render_image(
|
||||||
'v1/binding',
|
|
||||||
Bind(
|
Bind(
|
||||||
platform='TETR.IO',
|
platform='TETR.IO',
|
||||||
type='unlink',
|
type='unlink',
|
||||||
@@ -65,13 +66,19 @@ async def _(nb_user: User, event_session: EventSession, bot_info: UserInfo = Bot
|
|||||||
name=user.name.upper(),
|
name=user.name.upper(),
|
||||||
),
|
),
|
||||||
bot=People(
|
bot=People(
|
||||||
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
avatar=await get_avatar(
|
||||||
name=bot_info.user_name,
|
(
|
||||||
|
bot_user := await interface.get_user(event_session.self_id)
|
||||||
|
or UninfoUser(id=event_session.self_id)
|
||||||
|
),
|
||||||
|
'Data URI',
|
||||||
|
'../../static/logo/logo.svg',
|
||||||
|
),
|
||||||
|
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
|
||||||
),
|
),
|
||||||
prompt='io绑定{游戏ID}',
|
prompt='io绑定{游戏ID}',
|
||||||
lang=get_lang(),
|
lang=get_lang(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
) as page_hash:
|
).send()
|
||||||
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)
|
await remove_bind(session=session, user=nb_user, game_platform=GAME_TYPE)
|
||||||
|
|||||||
110
nonebot_plugin_tetris_stats/games/tetrio/verify.py
Normal file
110
nonebot_plugin_tetris_stats/games/tetrio/verify.py
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
from asyncio import gather
|
||||||
|
from hashlib import md5
|
||||||
|
from secrets import choice
|
||||||
|
|
||||||
|
from nonebot_plugin_alconna import Subcommand
|
||||||
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
|
from nonebot_plugin_orm import get_session
|
||||||
|
from nonebot_plugin_uninfo import QryItrface, Uninfo
|
||||||
|
from nonebot_plugin_uninfo import User as UninfoUser
|
||||||
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
|
from nonebot_plugin_user import User
|
||||||
|
from yarl import URL
|
||||||
|
|
||||||
|
from ...config.config import global_config
|
||||||
|
from ...db import create_or_update_bind, query_bind_info, trigger
|
||||||
|
from ...utils.host import get_self_netloc
|
||||||
|
from ...utils.image import get_avatar
|
||||||
|
from ...utils.lang import get_lang
|
||||||
|
from ...utils.render import render_image
|
||||||
|
from ...utils.render.schemas.base import Avatar, People
|
||||||
|
from ...utils.render.schemas.bind import Bind
|
||||||
|
from . import alc, command
|
||||||
|
from .api import Player
|
||||||
|
from .constant import GAME_TYPE
|
||||||
|
|
||||||
|
command.add(Subcommand('verify', help_text='验证 TETR.IO 账号'))
|
||||||
|
|
||||||
|
alc.shortcut(
|
||||||
|
'(?i:io)(?i:验证|verify)',
|
||||||
|
command='tstats TETR.IO verify',
|
||||||
|
humanized='io验证',
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from nonebot.adapters.discord import MessageCreateEvent
|
||||||
|
|
||||||
|
@alc.assign('TETRIO.verify')
|
||||||
|
async def _(_: MessageCreateEvent, nb_user: User, event_session: Uninfo, interface: QryItrface):
|
||||||
|
async with (
|
||||||
|
trigger(
|
||||||
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
|
game_platform=GAME_TYPE,
|
||||||
|
command_type='verify',
|
||||||
|
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()
|
||||||
|
if bind.verify is True:
|
||||||
|
await UniMessage('您已经完成了验证.').finish()
|
||||||
|
player = Player(user_id=bind.game_account, trust=True)
|
||||||
|
user_info = await player.get_info()
|
||||||
|
verify = (
|
||||||
|
user_info.data.connections.discord is not None
|
||||||
|
and user_info.data.connections.discord.id == event_session.user.id
|
||||||
|
)
|
||||||
|
if verify is False:
|
||||||
|
await UniMessage('您未通过验证, 请确认目标 TETR.IO 账号绑定了当前 Discord 账号').finish()
|
||||||
|
await create_or_update_bind(
|
||||||
|
session=session,
|
||||||
|
user=nb_user,
|
||||||
|
game_platform=GAME_TYPE,
|
||||||
|
game_account=bind.game_account,
|
||||||
|
verify=verify,
|
||||||
|
)
|
||||||
|
user, avatar_revision = await gather(player.user, player.avatar_revision)
|
||||||
|
await UniMessage.image(
|
||||||
|
raw=await render_image(
|
||||||
|
Bind(
|
||||||
|
platform='TETR.IO',
|
||||||
|
type='success',
|
||||||
|
user=People(
|
||||||
|
avatar=str(
|
||||||
|
URL(f'http://{get_self_netloc()}/host/resource/tetrio/avatars/{user.ID}')
|
||||||
|
% {'revision': avatar_revision}
|
||||||
|
)
|
||||||
|
if 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_user := await interface.get_user(event_session.self_id)
|
||||||
|
or UninfoUser(id=event_session.self_id)
|
||||||
|
),
|
||||||
|
'Data URI',
|
||||||
|
'../../static/logo/logo.svg',
|
||||||
|
),
|
||||||
|
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
|
||||||
|
),
|
||||||
|
prompt='io查我',
|
||||||
|
lang=get_lang(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
).finish()
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@alc.assign('TETRIO.verify')
|
||||||
|
async def _(event_session: Uninfo):
|
||||||
|
async with trigger(
|
||||||
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
|
game_platform=GAME_TYPE,
|
||||||
|
command_type='verify',
|
||||||
|
command_args=[],
|
||||||
|
):
|
||||||
|
await UniMessage('目前仅支持 Discord 账号验证').finish()
|
||||||
@@ -10,6 +10,8 @@ from .schemas.user_profile import UserProfile
|
|||||||
|
|
||||||
|
|
||||||
class TOPHistoricalData(MappedAsDataclass, Model):
|
class TOPHistoricalData(MappedAsDataclass, Model):
|
||||||
|
__tablename__ = 'nb_t_top_hist_data'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||||
user_unique_identifier: Mapped[str] = mapped_column(String(24), index=True)
|
user_unique_identifier: Mapped[str] = mapped_column(String(24), index=True)
|
||||||
api_type: Mapped[Literal['User Profile']] = mapped_column(String(16), index=True)
|
api_type: Mapped[Literal['User Profile']] = mapped_column(String(16), index=True)
|
||||||
|
|||||||
@@ -1,30 +1,26 @@
|
|||||||
|
from secrets import choice
|
||||||
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import QryItrface, Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo import User as UninfoUser
|
||||||
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import User
|
from nonebot_plugin_user import User
|
||||||
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo
|
|
||||||
|
|
||||||
|
from ...config.config import global_config
|
||||||
from ...db import BindStatus, create_or_update_bind, trigger
|
from ...db import BindStatus, create_or_update_bind, trigger
|
||||||
from ...utils.host import HostPage, get_self_netloc
|
|
||||||
from ...utils.image import get_avatar
|
from ...utils.image import get_avatar
|
||||||
from ...utils.lang import get_lang
|
from ...utils.lang import get_lang
|
||||||
from ...utils.render import Bind, render
|
from ...utils.render import render_image
|
||||||
from ...utils.render.schemas.base import People
|
from ...utils.render.schemas.base import People
|
||||||
from ...utils.screenshot import screenshot
|
from ...utils.render.schemas.bind import Bind
|
||||||
from . import alc
|
from . import alc
|
||||||
from .api import Player
|
from .api import Player
|
||||||
from .constant import GAME_TYPE
|
from .constant import GAME_TYPE
|
||||||
|
|
||||||
|
|
||||||
@alc.assign('TOP.bind')
|
@alc.assign('TOP.bind')
|
||||||
async def _(
|
async def _(nb_user: User, account: Player, event_session: Uninfo, interface: QryItrface):
|
||||||
nb_user: User,
|
|
||||||
account: Player,
|
|
||||||
event_session: EventSession,
|
|
||||||
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
|
|
||||||
bot_info: UserInfo = BotUserInfo(), # noqa: B008
|
|
||||||
):
|
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
@@ -40,25 +36,32 @@ async def _(
|
|||||||
game_account=user.unique_identifier,
|
game_account=user.unique_identifier,
|
||||||
)
|
)
|
||||||
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
|
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
|
||||||
async with HostPage(
|
await UniMessage.image(
|
||||||
await render(
|
raw=await render_image(
|
||||||
'v1/binding',
|
|
||||||
Bind(
|
Bind(
|
||||||
platform=GAME_TYPE,
|
platform=GAME_TYPE,
|
||||||
type='unknown',
|
type='unknown',
|
||||||
user=People(
|
user=People(
|
||||||
avatar=await get_avatar(event_user_info, 'Data URI', None),
|
avatar=await get_avatar(
|
||||||
|
event_session.user,
|
||||||
|
'Data URI',
|
||||||
|
None,
|
||||||
|
),
|
||||||
name=user.user_name,
|
name=user.user_name,
|
||||||
),
|
),
|
||||||
bot=People(
|
bot=People(
|
||||||
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
avatar=await get_avatar(
|
||||||
name=bot_info.user_name,
|
(
|
||||||
|
bot_user := await interface.get_user(event_session.self_id)
|
||||||
|
or UninfoUser(id=event_session.self_id)
|
||||||
|
),
|
||||||
|
'Data URI',
|
||||||
|
'../../static/logo/logo.svg',
|
||||||
|
),
|
||||||
|
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
|
||||||
),
|
),
|
||||||
prompt='top查我',
|
prompt='top查我',
|
||||||
lang=get_lang(),
|
lang=get_lang(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
) as page_hash:
|
).finish()
|
||||||
await UniMessage.image(
|
|
||||||
raw=await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
|
|
||||||
).finish()
|
|
||||||
|
|||||||
@@ -3,22 +3,20 @@ from nonebot.matcher import Matcher
|
|||||||
from nonebot_plugin_alconna import At
|
from nonebot_plugin_alconna import At
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import get_user
|
from nonebot_plugin_user import get_user
|
||||||
|
|
||||||
from ...db import query_bind_info, trigger
|
from ...db import query_bind_info, trigger
|
||||||
from ...i18n import Lang
|
from ...i18n import Lang
|
||||||
from ...utils.exception import FallbackError
|
from ...utils.exception import FallbackError
|
||||||
from ...utils.host import HostPage, get_self_netloc
|
|
||||||
from ...utils.lang import get_lang
|
from ...utils.lang import get_lang
|
||||||
from ...utils.metrics import TetrisMetricsBasicWithLPM, get_metrics
|
from ...utils.metrics import TetrisMetricsBasicWithLPM, get_metrics
|
||||||
from ...utils.render import render
|
from ...utils.render import render_image
|
||||||
from ...utils.render.avatar import get_avatar
|
from ...utils.render.avatar import get_avatar
|
||||||
from ...utils.render.schemas.base import People, Trending
|
from ...utils.render.schemas.base import People, Trending
|
||||||
from ...utils.render.schemas.v1.top.info import Data as InfoData
|
from ...utils.render.schemas.v1.top.info import Data as InfoData
|
||||||
from ...utils.render.schemas.v1.top.info import Info
|
from ...utils.render.schemas.v1.top.info import Info
|
||||||
from ...utils.screenshot import screenshot
|
|
||||||
from ...utils.typedefs import Me
|
from ...utils.typedefs import Me
|
||||||
from . import alc
|
from . import alc
|
||||||
from .api import Player
|
from .api import Player
|
||||||
@@ -27,7 +25,7 @@ from .constant import GAME_TYPE
|
|||||||
|
|
||||||
|
|
||||||
@alc.assign('TOP.query')
|
@alc.assign('TOP.query')
|
||||||
async def _(event: Event, matcher: Matcher, target: At | Me, event_session: EventSession):
|
async def _(event: Event, matcher: Matcher, target: At | Me, event_session: Uninfo):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
@@ -38,7 +36,7 @@ async def _(event: Event, matcher: Matcher, target: At | Me, event_session: Even
|
|||||||
bind = await query_bind_info(
|
bind = await query_bind_info(
|
||||||
session=session,
|
session=session,
|
||||||
user=await get_user(
|
user=await get_user(
|
||||||
event_session.platform, target.target if isinstance(target, At) else event.get_user_id()
|
event_session.scope, target.target if isinstance(target, At) else event.get_user_id()
|
||||||
),
|
),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
)
|
)
|
||||||
@@ -51,7 +49,7 @@ async def _(event: Event, matcher: Matcher, target: At | Me, event_session: Even
|
|||||||
|
|
||||||
|
|
||||||
@alc.assign('TOP.query')
|
@alc.assign('TOP.query')
|
||||||
async def _(account: Player, event_session: EventSession):
|
async def _(account: Player, event_session: Uninfo):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
@@ -75,32 +73,28 @@ async def make_query_image(profile: UserProfile) -> bytes:
|
|||||||
raise FallbackError
|
raise FallbackError
|
||||||
today = get_metrics(lpm=profile.today.lpm, apm=profile.today.apm)
|
today = get_metrics(lpm=profile.today.lpm, apm=profile.today.apm)
|
||||||
history = get_avg_metrics(profile.total)
|
history = get_avg_metrics(profile.total)
|
||||||
async with HostPage(
|
return await render_image(
|
||||||
await render(
|
Info(
|
||||||
'v1/top/info',
|
user=People(avatar=get_avatar(profile.user_name), name=profile.user_name),
|
||||||
Info(
|
today=InfoData(
|
||||||
user=People(avatar=get_avatar(profile.user_name), name=profile.user_name),
|
pps=today.pps,
|
||||||
today=InfoData(
|
lpm=today.lpm,
|
||||||
pps=today.pps,
|
lpm_trending=Trending.KEEP,
|
||||||
lpm=today.lpm,
|
apm=today.apm,
|
||||||
lpm_trending=Trending.KEEP,
|
apl=today.apl,
|
||||||
apm=today.apm,
|
apm_trending=Trending.KEEP,
|
||||||
apl=today.apl,
|
|
||||||
apm_trending=Trending.KEEP,
|
|
||||||
),
|
|
||||||
historical=InfoData(
|
|
||||||
pps=history.pps,
|
|
||||||
lpm=history.lpm,
|
|
||||||
lpm_trending=Trending.KEEP,
|
|
||||||
apm=history.apm,
|
|
||||||
apl=history.apl,
|
|
||||||
apm_trending=Trending.KEEP,
|
|
||||||
),
|
|
||||||
lang=get_lang(),
|
|
||||||
),
|
),
|
||||||
)
|
historical=InfoData(
|
||||||
) as page_hash:
|
pps=history.pps,
|
||||||
return await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
|
lpm=history.lpm,
|
||||||
|
lpm_trending=Trending.KEEP,
|
||||||
|
apm=history.apm,
|
||||||
|
apl=history.apl,
|
||||||
|
apm_trending=Trending.KEEP,
|
||||||
|
),
|
||||||
|
lang=get_lang(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def make_query_text(profile: UserProfile) -> UniMessage:
|
def make_query_text(profile: UserProfile) -> UniMessage:
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
|
from secrets import choice
|
||||||
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import QryItrface, Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo import User as UninfoUser
|
||||||
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import User
|
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 nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
|
||||||
|
|
||||||
|
from ...config.config import global_config
|
||||||
from ...db import query_bind_info, remove_bind, trigger
|
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.image import get_avatar
|
||||||
from ...utils.lang import get_lang
|
from ...utils.lang import get_lang
|
||||||
from ...utils.render import Bind, render
|
from ...utils.render import render_image
|
||||||
from ...utils.render.schemas.base import People
|
from ...utils.render.schemas.base import People
|
||||||
from ...utils.screenshot import screenshot
|
from ...utils.render.schemas.bind import Bind
|
||||||
from . import alc
|
from . import alc
|
||||||
from .api import Player
|
from .api import Player
|
||||||
from .constant import GAME_TYPE
|
from .constant import GAME_TYPE
|
||||||
@@ -21,9 +23,8 @@ from .constant import GAME_TYPE
|
|||||||
@alc.assign('TOP.unbind')
|
@alc.assign('TOP.unbind')
|
||||||
async def _(
|
async def _(
|
||||||
nb_user: User,
|
nb_user: User,
|
||||||
event_session: EventSession,
|
event_session: Uninfo,
|
||||||
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
|
interface: QryItrface,
|
||||||
bot_info: UserInfo = BotUserInfo(), # noqa: B008
|
|
||||||
):
|
):
|
||||||
async with (
|
async with (
|
||||||
trigger(
|
trigger(
|
||||||
@@ -41,25 +42,33 @@ async def _(
|
|||||||
return
|
return
|
||||||
player = Player(user_name=bind.game_account, trust=True)
|
player = Player(user_name=bind.game_account, trust=True)
|
||||||
user = await player.user
|
user = await player.user
|
||||||
netloc = get_self_netloc()
|
await UniMessage.image(
|
||||||
async with HostPage(
|
raw=await render_image(
|
||||||
await render(
|
|
||||||
'v1/binding',
|
|
||||||
Bind(
|
Bind(
|
||||||
platform='TOP',
|
platform='TOP',
|
||||||
type='unlink',
|
type='unlink',
|
||||||
user=People(
|
user=People(
|
||||||
avatar=await get_avatar(event_user_info, 'Data URI', None),
|
avatar=await get_avatar(
|
||||||
|
event_session.user,
|
||||||
|
'Data URI',
|
||||||
|
None,
|
||||||
|
),
|
||||||
name=user.user_name,
|
name=user.user_name,
|
||||||
),
|
),
|
||||||
bot=People(
|
bot=People(
|
||||||
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
avatar=await get_avatar(
|
||||||
name=bot_info.user_name,
|
(
|
||||||
|
bot_user := await interface.get_user(event_session.self_id)
|
||||||
|
or UninfoUser(id=event_session.self_id)
|
||||||
|
),
|
||||||
|
'Data URI',
|
||||||
|
'../../static/logo/logo.svg',
|
||||||
|
),
|
||||||
|
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
|
||||||
),
|
),
|
||||||
prompt='top绑定{游戏ID}',
|
prompt='top绑定{游戏ID}',
|
||||||
lang=get_lang(),
|
lang=get_lang(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
) as page_hash:
|
).send()
|
||||||
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)
|
await remove_bind(session=session, user=nb_user, game_platform=GAME_TYPE)
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ from .schemas.user_profile import UserProfile
|
|||||||
|
|
||||||
|
|
||||||
class TOSHistoricalData(MappedAsDataclass, Model):
|
class TOSHistoricalData(MappedAsDataclass, Model):
|
||||||
|
__tablename__ = 'nb_t_tos_hist_data'
|
||||||
|
|
||||||
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
id: Mapped[int] = mapped_column(init=False, primary_key=True)
|
||||||
user_unique_identifier: Mapped[str] = mapped_column(String(24), index=True)
|
user_unique_identifier: Mapped[str] = mapped_column(String(256), index=True)
|
||||||
api_type: Mapped[Literal['User Info', 'User Profile']] = mapped_column(String(16), index=True)
|
api_type: Mapped[Literal['User Info', 'User Profile']] = mapped_column(String(16), index=True)
|
||||||
data: Mapped[UserInfoSuccess | UserProfile] = mapped_column(
|
data: Mapped[UserInfoSuccess | UserProfile] = mapped_column(
|
||||||
PydanticType(get_model=[], models={UserInfoSuccess, UserProfile})
|
PydanticType(get_model=[], models={UserInfoSuccess, UserProfile})
|
||||||
|
|||||||
@@ -1,17 +1,19 @@
|
|||||||
|
from secrets import choice
|
||||||
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import QryItrface, Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo import User as UninfoUser
|
||||||
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import User
|
from nonebot_plugin_user import User
|
||||||
from nonebot_plugin_userinfo import BotUserInfo, EventUserInfo, UserInfo
|
|
||||||
|
|
||||||
|
from ...config.config import global_config
|
||||||
from ...db import BindStatus, create_or_update_bind, trigger
|
from ...db import BindStatus, create_or_update_bind, trigger
|
||||||
from ...utils.host import HostPage, get_self_netloc
|
|
||||||
from ...utils.image import get_avatar
|
from ...utils.image import get_avatar
|
||||||
from ...utils.lang import get_lang
|
from ...utils.lang import get_lang
|
||||||
from ...utils.render import Bind, render
|
from ...utils.render import render_image
|
||||||
from ...utils.render.schemas.base import People
|
from ...utils.render.schemas.base import People
|
||||||
from ...utils.screenshot import screenshot
|
from ...utils.render.schemas.bind import Bind
|
||||||
from . import alc
|
from . import alc
|
||||||
from .api import Player
|
from .api import Player
|
||||||
from .constant import GAME_TYPE
|
from .constant import GAME_TYPE
|
||||||
@@ -21,9 +23,8 @@ from .constant import GAME_TYPE
|
|||||||
async def _(
|
async def _(
|
||||||
nb_user: User,
|
nb_user: User,
|
||||||
account: Player,
|
account: Player,
|
||||||
event_session: EventSession,
|
event_session: Uninfo,
|
||||||
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
|
interface: QryItrface,
|
||||||
bot_info: UserInfo = BotUserInfo(), # noqa: B008
|
|
||||||
):
|
):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
@@ -41,24 +42,32 @@ async def _(
|
|||||||
)
|
)
|
||||||
user_info = await account.get_info()
|
user_info = await account.get_info()
|
||||||
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
|
if bind_status in (BindStatus.SUCCESS, BindStatus.UPDATE):
|
||||||
async with HostPage(
|
await UniMessage.image(
|
||||||
await render(
|
raw=await render_image(
|
||||||
'v1/binding',
|
|
||||||
Bind(
|
Bind(
|
||||||
platform=GAME_TYPE,
|
platform=GAME_TYPE,
|
||||||
type='unknown',
|
type='unknown',
|
||||||
user=People(
|
user=People(
|
||||||
avatar=await get_avatar(event_user_info, 'Data URI', None), name=user_info.data.name
|
avatar=await get_avatar(
|
||||||
|
event_session.user,
|
||||||
|
'Data URI',
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
name=user_info.data.name,
|
||||||
),
|
),
|
||||||
bot=People(
|
bot=People(
|
||||||
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
avatar=await get_avatar(
|
||||||
name=bot_info.user_remark or bot_info.user_displayname or bot_info.user_name,
|
(
|
||||||
|
bot_user := await interface.get_user(event_session.self_id)
|
||||||
|
or UninfoUser(id=event_session.self_id)
|
||||||
|
),
|
||||||
|
'Data URI',
|
||||||
|
'../../static/logo/logo.svg',
|
||||||
|
),
|
||||||
|
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
|
||||||
),
|
),
|
||||||
prompt='茶服查我',
|
prompt='茶服查我',
|
||||||
lang=get_lang(),
|
lang=get_lang(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
) as page_hash:
|
).finish()
|
||||||
await UniMessage.image(
|
|
||||||
raw=await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
|
|
||||||
).finish()
|
|
||||||
|
|||||||
@@ -9,26 +9,23 @@ from nonebot.matcher import Matcher
|
|||||||
from nonebot_plugin_alconna import At
|
from nonebot_plugin_alconna import At
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import Uninfo, User
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import get_user
|
from nonebot_plugin_user import get_user
|
||||||
from nonebot_plugin_userinfo import EventUserInfo, UserInfo
|
|
||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
|
|
||||||
from ...db import query_bind_info, trigger
|
from ...db import query_bind_info, trigger
|
||||||
from ...i18n import Lang
|
from ...i18n import Lang
|
||||||
from ...utils.chart import get_split, get_value_bounds, handle_history_data
|
from ...utils.chart import get_split, get_value_bounds, handle_history_data
|
||||||
from ...utils.exception import RequestError
|
from ...utils.exception import RequestError
|
||||||
from ...utils.host import HostPage, get_self_netloc
|
|
||||||
from ...utils.image import get_avatar
|
from ...utils.image import get_avatar
|
||||||
from ...utils.lang import get_lang
|
from ...utils.lang import get_lang
|
||||||
from ...utils.metrics import TetrisMetricsProWithLPMADPM, get_metrics
|
from ...utils.metrics import TetrisMetricsProWithLPMADPM, get_metrics
|
||||||
from ...utils.render import render
|
from ...utils.render import render_image
|
||||||
from ...utils.render.avatar import get_avatar as get_random_avatar
|
from ...utils.render.avatar import get_avatar as get_random_avatar
|
||||||
from ...utils.render.schemas.base import HistoryData, People, Trending
|
from ...utils.render.schemas.base import HistoryData, People, Trending
|
||||||
from ...utils.render.schemas.v1.base import History
|
from ...utils.render.schemas.v1.base import History
|
||||||
from ...utils.render.schemas.v1.tos.info import Info, Multiplayer, Singleplayer
|
from ...utils.render.schemas.v1.tos.info import Info, Multiplayer, Singleplayer
|
||||||
from ...utils.screenshot import screenshot
|
|
||||||
from ...utils.time_it import time_it
|
from ...utils.time_it import time_it
|
||||||
from ...utils.typedefs import Me, Number
|
from ...utils.typedefs import Me, Number
|
||||||
from . import alc
|
from . import alc
|
||||||
@@ -45,8 +42,7 @@ def add_special_handlers(
|
|||||||
async def _(
|
async def _(
|
||||||
event: Event,
|
event: Event,
|
||||||
target: At | Me,
|
target: At | Me,
|
||||||
event_session: EventSession,
|
event_session: Uninfo,
|
||||||
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
|
|
||||||
):
|
):
|
||||||
if isinstance(event, match_event):
|
if isinstance(event, match_event):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
@@ -66,7 +62,9 @@ def add_special_handlers(
|
|||||||
if game_data is not None:
|
if game_data is not None:
|
||||||
await UniMessage.image(
|
await UniMessage.image(
|
||||||
raw=await make_query_image(
|
raw=await make_query_image(
|
||||||
user_info, game_data, None if isinstance(target, At) else event_user_info
|
user_info,
|
||||||
|
game_data,
|
||||||
|
None if isinstance(target, At) else event_session.user,
|
||||||
)
|
)
|
||||||
).finish()
|
).finish()
|
||||||
await make_query_text(user_info, game_data).finish()
|
await make_query_text(user_info, game_data).finish()
|
||||||
@@ -112,8 +110,7 @@ async def _(
|
|||||||
event: Event,
|
event: Event,
|
||||||
matcher: Matcher,
|
matcher: Matcher,
|
||||||
target: At | Me,
|
target: At | Me,
|
||||||
event_session: EventSession,
|
event_session: Uninfo,
|
||||||
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
|
|
||||||
):
|
):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
@@ -125,7 +122,7 @@ async def _(
|
|||||||
bind = await query_bind_info(
|
bind = await query_bind_info(
|
||||||
session=session,
|
session=session,
|
||||||
user=await get_user(
|
user=await get_user(
|
||||||
event_session.platform, target.target if isinstance(target, At) else event.get_user_id()
|
event_session.scope, target.target if isinstance(target, At) else event.get_user_id()
|
||||||
),
|
),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
)
|
)
|
||||||
@@ -139,7 +136,9 @@ async def _(
|
|||||||
message
|
message
|
||||||
+ UniMessage.image(
|
+ UniMessage.image(
|
||||||
raw=await make_query_image(
|
raw=await make_query_image(
|
||||||
user_info, game_data, None if isinstance(target, At) else event_user_info
|
user_info,
|
||||||
|
game_data,
|
||||||
|
None if isinstance(target, At) else event_session.user,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).finish()
|
).finish()
|
||||||
@@ -147,7 +146,7 @@ async def _(
|
|||||||
|
|
||||||
|
|
||||||
@alc.assign('TOS.query')
|
@alc.assign('TOS.query')
|
||||||
async def _(account: Player, event_session: EventSession):
|
async def _(account: Player, event_session: Uninfo):
|
||||||
async with trigger(
|
async with trigger(
|
||||||
session_persist_id=await get_session_persist_id(event_session),
|
session_persist_id=await get_session_persist_id(event_session),
|
||||||
game_platform=GAME_TYPE,
|
game_platform=GAME_TYPE,
|
||||||
@@ -250,7 +249,7 @@ async def get_historical_data(unique_identifier: str) -> list[HistoryData]:
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
async def make_query_image(user_info: UserInfoSuccess, game_data: GameData, event_user_info: UserInfo | None) -> bytes:
|
async def make_query_image(user_info: UserInfoSuccess, game_data: GameData, event_user_info: User | None) -> bytes:
|
||||||
metrics = game_data.metrics
|
metrics = game_data.metrics
|
||||||
sprint_value = (
|
sprint_value = (
|
||||||
(
|
(
|
||||||
@@ -263,52 +262,48 @@ async def make_query_image(user_info: UserInfoSuccess, game_data: GameData, even
|
|||||||
)
|
)
|
||||||
data = handle_history_data(await get_historical_data(user_info.data.teaid))
|
data = handle_history_data(await get_historical_data(user_info.data.teaid))
|
||||||
values = get_value_bounds([i.score for i in data])
|
values = get_value_bounds([i.score for i in data])
|
||||||
async with HostPage(
|
return await render_image(
|
||||||
await render(
|
Info(
|
||||||
'v1/tos/info',
|
user=People(
|
||||||
Info(
|
avatar=await get_avatar(event_user_info, 'Data URI', None)
|
||||||
user=People(
|
if event_user_info is not None
|
||||||
avatar=await get_avatar(event_user_info, 'Data URI', None)
|
else get_random_avatar(user_info.data.teaid),
|
||||||
if event_user_info is not None
|
name=user_info.data.name,
|
||||||
else get_random_avatar(user_info.data.teaid),
|
|
||||||
name=user_info.data.name,
|
|
||||||
),
|
|
||||||
multiplayer=Multiplayer(
|
|
||||||
history=History(
|
|
||||||
data=data,
|
|
||||||
max_value=values.value_max,
|
|
||||||
min_value=values.value_min,
|
|
||||||
split_interval=(split := get_split(value_bound=values, min_value=0)).split_value,
|
|
||||||
offset=split.offset,
|
|
||||||
),
|
|
||||||
rating=round(float(user_info.data.rating_now), 2),
|
|
||||||
rd=round(float(user_info.data.rd_now), 2),
|
|
||||||
lpm=metrics.lpm,
|
|
||||||
pps=metrics.pps,
|
|
||||||
lpm_trending=Trending.KEEP,
|
|
||||||
apm=metrics.apm,
|
|
||||||
apl=metrics.apl,
|
|
||||||
apm_trending=Trending.KEEP,
|
|
||||||
adpm=metrics.adpm,
|
|
||||||
vs=metrics.vs,
|
|
||||||
adpl=metrics.adpl,
|
|
||||||
adpm_trending=Trending.KEEP,
|
|
||||||
app=(app := (metrics.apm / (60 * metrics.pps))),
|
|
||||||
or_=game_data.or_,
|
|
||||||
dspp=game_data.dspp,
|
|
||||||
ci=150 * game_data.dspp - 125 * app + 50 * (metrics.vs / metrics.apm) - 25,
|
|
||||||
ge=game_data.ge,
|
|
||||||
),
|
|
||||||
singleplayer=Singleplayer(
|
|
||||||
sprint=sprint_value,
|
|
||||||
challenge=f'{int(user_info.data.pb_challenge):,}' if user_info.data.pb_challenge != '0' else 'N/A',
|
|
||||||
marathon=f'{int(user_info.data.pb_marathon):,}' if user_info.data.pb_marathon != '0' else 'N/A',
|
|
||||||
),
|
|
||||||
lang=get_lang(),
|
|
||||||
),
|
),
|
||||||
)
|
multiplayer=Multiplayer(
|
||||||
) as page_hash:
|
history=History(
|
||||||
return await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html')
|
data=data,
|
||||||
|
max_value=values.value_max,
|
||||||
|
min_value=values.value_min,
|
||||||
|
split_interval=(split := get_split(value_bound=values, min_value=0)).split_value,
|
||||||
|
offset=split.offset,
|
||||||
|
),
|
||||||
|
rating=round(float(user_info.data.rating_now), 2),
|
||||||
|
rd=round(float(user_info.data.rd_now), 2),
|
||||||
|
lpm=metrics.lpm,
|
||||||
|
pps=metrics.pps,
|
||||||
|
lpm_trending=Trending.KEEP,
|
||||||
|
apm=metrics.apm,
|
||||||
|
apl=metrics.apl,
|
||||||
|
apm_trending=Trending.KEEP,
|
||||||
|
adpm=metrics.adpm,
|
||||||
|
vs=metrics.vs,
|
||||||
|
adpl=metrics.adpl,
|
||||||
|
adpm_trending=Trending.KEEP,
|
||||||
|
app=(app := (metrics.apm / (60 * metrics.pps))),
|
||||||
|
or_=game_data.or_,
|
||||||
|
dspp=game_data.dspp,
|
||||||
|
ci=150 * game_data.dspp - 125 * app + 50 * (metrics.vs / metrics.apm) - 25,
|
||||||
|
ge=game_data.ge,
|
||||||
|
),
|
||||||
|
singleplayer=Singleplayer(
|
||||||
|
sprint=sprint_value,
|
||||||
|
challenge=f'{int(user_info.data.pb_challenge):,}' if user_info.data.pb_challenge != '0' else 'N/A',
|
||||||
|
marathon=f'{int(user_info.data.pb_marathon):,}' if user_info.data.pb_marathon != '0' else 'N/A',
|
||||||
|
),
|
||||||
|
lang=get_lang(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def make_query_text(user_info: UserInfoSuccess, game_data: GameData | None) -> UniMessage:
|
def make_query_text(user_info: UserInfoSuccess, game_data: GameData | None) -> UniMessage:
|
||||||
|
|||||||
@@ -1,19 +1,20 @@
|
|||||||
|
from secrets import choice
|
||||||
|
|
||||||
from nonebot_plugin_alconna.uniseg import UniMessage
|
from nonebot_plugin_alconna.uniseg import UniMessage
|
||||||
from nonebot_plugin_orm import get_session
|
from nonebot_plugin_orm import get_session
|
||||||
from nonebot_plugin_session import EventSession
|
from nonebot_plugin_uninfo import QryItrface, Uninfo
|
||||||
from nonebot_plugin_session_orm import get_session_persist_id # type: ignore[import-untyped]
|
from nonebot_plugin_uninfo import User as UninfoUser
|
||||||
|
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||||
from nonebot_plugin_user import User
|
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 nonebot_plugin_waiter import suggest # type: ignore[import-untyped]
|
||||||
|
|
||||||
|
from ...config.config import global_config
|
||||||
from ...db import query_bind_info, remove_bind, trigger
|
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.image import get_avatar
|
||||||
from ...utils.lang import get_lang
|
from ...utils.lang import get_lang
|
||||||
from ...utils.render import Bind, render
|
from ...utils.render import render_image
|
||||||
from ...utils.render.avatar import get_avatar as get_random_avatar
|
|
||||||
from ...utils.render.schemas.base import People
|
from ...utils.render.schemas.base import People
|
||||||
from ...utils.screenshot import screenshot
|
from ...utils.render.schemas.bind import Bind
|
||||||
from . import alc
|
from . import alc
|
||||||
from .api import Player
|
from .api import Player
|
||||||
from .constant import GAME_TYPE
|
from .constant import GAME_TYPE
|
||||||
@@ -22,9 +23,8 @@ from .constant import GAME_TYPE
|
|||||||
@alc.assign('TOP.unbind')
|
@alc.assign('TOP.unbind')
|
||||||
async def _(
|
async def _(
|
||||||
nb_user: User,
|
nb_user: User,
|
||||||
event_session: EventSession,
|
event_session: Uninfo,
|
||||||
event_user_info: UserInfo = EventUserInfo(), # noqa: B008
|
interface: QryItrface,
|
||||||
bot_info: UserInfo = BotUserInfo(), # noqa: B008
|
|
||||||
):
|
):
|
||||||
async with (
|
async with (
|
||||||
trigger(
|
trigger(
|
||||||
@@ -42,27 +42,29 @@ async def _(
|
|||||||
return
|
return
|
||||||
player = Player(user_name=bind.game_account, trust=True)
|
player = Player(user_name=bind.game_account, trust=True)
|
||||||
user = await player.user
|
user = await player.user
|
||||||
netloc = get_self_netloc()
|
await UniMessage.image(
|
||||||
async with HostPage(
|
raw=await render_image(
|
||||||
await render(
|
|
||||||
'v1/binding',
|
|
||||||
Bind(
|
Bind(
|
||||||
platform='TOS',
|
platform='TOS',
|
||||||
type='unlink',
|
type='unlink',
|
||||||
user=People(
|
user=People(
|
||||||
avatar=await get_avatar(event_user_info, 'Data URI', None)
|
avatar=await get_avatar(event_session.user, 'Data URI', None),
|
||||||
if event_user_info is not None
|
|
||||||
else get_random_avatar(user.teaid),
|
|
||||||
name=user.name,
|
name=user.name,
|
||||||
),
|
),
|
||||||
bot=People(
|
bot=People(
|
||||||
avatar=await get_avatar(bot_info, 'Data URI', '../../static/logo/logo.svg'),
|
avatar=await get_avatar(
|
||||||
name=bot_info.user_name,
|
(
|
||||||
|
bot_user := await interface.get_user(event_session.self_id)
|
||||||
|
or UninfoUser(id=event_session.self_id)
|
||||||
|
),
|
||||||
|
'Data URI',
|
||||||
|
'../../static/logo/logo.svg',
|
||||||
|
),
|
||||||
|
name=bot_user.nick or bot_user.name or choice(list(global_config.nickname) or ['bot']),
|
||||||
),
|
),
|
||||||
prompt='茶服绑定{游戏ID}',
|
prompt='茶服绑定{游戏ID}',
|
||||||
lang=get_lang(),
|
lang=get_lang(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
) as page_hash:
|
).send()
|
||||||
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)
|
await remove_bind(session=session, user=nb_user, game_platform=GAME_TYPE)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# This file is @generated by tarina.lang CLI tool
|
# This file is @generated by tarina.lang CLI tool
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
|
||||||
# ruff: noqa: E402, F401, PLC0414
|
# ruff: noqa: E402
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ from nonebot.log import logger
|
|||||||
from playwright.__main__ import main
|
from playwright.__main__ import main
|
||||||
from playwright.async_api import Browser, BrowserContext, async_playwright
|
from playwright.async_api import Browser, BrowserContext, async_playwright
|
||||||
|
|
||||||
|
from ..config.config import config
|
||||||
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
|
|
||||||
global_config = driver.config
|
global_config = driver.config
|
||||||
@@ -76,6 +78,7 @@ class BrowserManager:
|
|||||||
"""启动浏览器实例"""
|
"""启动浏览器实例"""
|
||||||
playwright = await async_playwright().start()
|
playwright = await async_playwright().start()
|
||||||
cls._browser = await playwright.firefox.launch(
|
cls._browser = await playwright.firefox.launch(
|
||||||
|
headless=not config.tetris.dev.enabled,
|
||||||
firefox_user_prefs={
|
firefox_user_prefs={
|
||||||
'network.http.max-persistent-connections-per-server': 64,
|
'network.http.max-persistent-connections-per-server': 64,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from nonebot import get_app, get_driver
|
|||||||
from nonebot.log import logger
|
from nonebot.log import logger
|
||||||
from yarl import URL
|
from yarl import URL
|
||||||
|
|
||||||
from ..config.config import CACHE_PATH
|
from ..config.config import CACHE_PATH, config
|
||||||
from ..games.tetrio.api.cache import request
|
from ..games.tetrio.api.cache import request
|
||||||
from .image import img_to_png
|
from .image import img_to_png
|
||||||
from .templates import TEMPLATES_DIR
|
from .templates import TEMPLATES_DIR
|
||||||
@@ -45,8 +45,14 @@ class HostPage:
|
|||||||
async def __aenter__(self) -> str:
|
async def __aenter__(self) -> str:
|
||||||
return self.page_hash
|
return self.page_hash
|
||||||
|
|
||||||
async def __aexit__(self, exc_type, exc, tb) -> None: # noqa: ANN001
|
if not config.tetris.dev.enabled:
|
||||||
self.pages.pop(self.page_hash, None)
|
|
||||||
|
async def __aexit__(self, exc_type, exc, tb) -> None: # noqa: ANN001
|
||||||
|
self.pages.pop(self.page_hash, None)
|
||||||
|
else:
|
||||||
|
|
||||||
|
async def __aexit__(self, exc_type, exc, tb) -> None: # noqa: ANN001
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
@driver.on_startup
|
@driver.on_startup
|
||||||
|
|||||||
@@ -2,16 +2,22 @@ from base64 import b64encode
|
|||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Literal, overload
|
from typing import Literal, overload
|
||||||
|
|
||||||
from nonebot_plugin_userinfo import UserInfo
|
from nonebot_plugin_uninfo import User
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
|
from yarl import URL
|
||||||
|
|
||||||
|
from ..config.config import config
|
||||||
|
from .request import Request
|
||||||
|
|
||||||
|
request = Request(config.tetris.proxy.main)
|
||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
async def get_avatar(user: UserInfo, scheme: Literal['Data URI'], default: str | None) -> str:
|
async def get_avatar(user: User, scheme: Literal['Data URI'], default: str | None) -> str:
|
||||||
"""获取用户头像的指定格式
|
"""获取用户头像的指定格式
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user (UserInfo): 要获取的用户
|
user (User): 要获取的用户
|
||||||
scheme (Literal['Data URI']): 格式
|
scheme (Literal['Data URI']): 格式
|
||||||
default (str | None): 获取不到时的默认值
|
default (str | None): 获取不到时的默认值
|
||||||
|
|
||||||
@@ -25,11 +31,11 @@ async def get_avatar(user: UserInfo, scheme: Literal['Data URI'], default: str |
|
|||||||
|
|
||||||
|
|
||||||
@overload
|
@overload
|
||||||
async def get_avatar(user: UserInfo, scheme: Literal['bytes'], default: str | None) -> bytes:
|
async def get_avatar(user: User, scheme: Literal['bytes'], default: str | None) -> bytes:
|
||||||
"""获取用户头像的指定格式
|
"""获取用户头像的指定格式
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
user (UserInfo): 要获取的用户
|
user (User): 要获取的用户
|
||||||
scheme (Literal['bytes']): 格式
|
scheme (Literal['bytes']): 格式
|
||||||
default (str | None): 获取不到时的默认值
|
default (str | None): 获取不到时的默认值
|
||||||
|
|
||||||
@@ -38,20 +44,20 @@ async def get_avatar(user: UserInfo, scheme: Literal['bytes'], default: str | No
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
async def get_avatar(user: UserInfo, scheme: Literal['Data URI', 'bytes'], default: str | None) -> str | bytes:
|
async def get_avatar(user: User, scheme: Literal['Data URI', 'bytes'], default: str | None) -> str | bytes:
|
||||||
if user.user_avatar is None:
|
if user.avatar is None:
|
||||||
if default is None:
|
if default is None:
|
||||||
msg = "Can't get avatar"
|
msg = "Can't get avatar"
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
return default
|
return default
|
||||||
bot_avatar = await user.user_avatar.get_image()
|
avatar = await request.request(URL(user.avatar), is_json=False)
|
||||||
if scheme == 'Data URI':
|
if scheme == 'Data URI':
|
||||||
avatar_format = Image.open(BytesIO(bot_avatar)).format
|
avatar_format = Image.open(BytesIO(avatar)).format
|
||||||
if avatar_format is None:
|
if avatar_format is None:
|
||||||
msg = "Can't get avatar format"
|
msg = "Can't get avatar format"
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
return f'data:{Image.MIME[avatar_format]};base64,{b64encode(bot_avatar).decode()}'
|
return f'data:{Image.MIME[avatar_format]};base64,{b64encode(avatar).decode()}'
|
||||||
return bot_avatar
|
return avatar
|
||||||
|
|
||||||
|
|
||||||
def img_to_png(image: bytes) -> bytes:
|
def img_to_png(image: bytes) -> bytes:
|
||||||
|
|||||||
@@ -1,22 +1,10 @@
|
|||||||
from typing import Literal, overload
|
|
||||||
|
|
||||||
from jinja2 import Environment, FileSystemLoader
|
from jinja2 import Environment, FileSystemLoader
|
||||||
from nonebot.compat import PYDANTIC_V2
|
from nonebot.compat import PYDANTIC_V2
|
||||||
|
|
||||||
|
from ..host import HostPage, get_self_netloc
|
||||||
|
from ..screenshot import screenshot
|
||||||
from ..templates import TEMPLATES_DIR
|
from ..templates import TEMPLATES_DIR
|
||||||
from .schemas.base import Base
|
from .schemas.base import Base
|
||||||
from .schemas.bind import Bind
|
|
||||||
from .schemas.v1.tetrio.rank import Data as TETRIORankDataV1
|
|
||||||
from .schemas.v1.tetrio.user.info import Info as TETRIOUserInfoV1
|
|
||||||
from .schemas.v1.top.info import Info as TOPInfoV1
|
|
||||||
from .schemas.v1.tos.info import Info as TOSInfoV1
|
|
||||||
from .schemas.v2.tetrio.rank import Data as TETRIORankDataV2
|
|
||||||
from .schemas.v2.tetrio.rank.detail import Data as TETRIORankDetailDataV2
|
|
||||||
from .schemas.v2.tetrio.record.blitz import Record as TETRIORecordBlitzV2
|
|
||||||
from .schemas.v2.tetrio.record.sprint import Record as TETRIORecordSprintV2
|
|
||||||
from .schemas.v2.tetrio.tetra_league import Data as TETRIOTetraLeagueDataV2
|
|
||||||
from .schemas.v2.tetrio.user.info import Info as TETRIOUserInfoV2
|
|
||||||
from .schemas.v2.tetrio.user.list import List as TETRIOUserListV2
|
|
||||||
|
|
||||||
env = Environment(
|
env = Environment(
|
||||||
loader=FileSystemLoader(TEMPLATES_DIR),
|
loader=FileSystemLoader(TEMPLATES_DIR),
|
||||||
@@ -27,39 +15,19 @@ env = Environment(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v1/binding'], data: Bind) -> str: ...
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v1/tetrio/info'], data: TETRIOUserInfoV1) -> str: ...
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v1/tetrio/rank'], data: TETRIORankDataV1) -> str: ...
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v1/top/info'], data: TOPInfoV1) -> str: ...
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v1/tos/info'], data: TOSInfoV1) -> str: ...
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v2/tetrio/rank'], data: TETRIORankDataV2) -> str: ...
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v2/tetrio/rank/detail'], data: TETRIORankDetailDataV2) -> str: ...
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v2/tetrio/record/blitz'], data: TETRIORecordBlitzV2) -> str: ...
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v2/tetrio/record/sprint'], data: TETRIORecordSprintV2) -> str: ...
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v2/tetrio/tetra-league'], data: TETRIOTetraLeagueDataV2) -> str: ...
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v2/tetrio/user/info'], data: TETRIOUserInfoV2) -> str: ...
|
|
||||||
@overload
|
|
||||||
async def render(render_type: Literal['v2/tetrio/user/list'], data: TETRIOUserListV2) -> str: ...
|
|
||||||
async def render(
|
async def render(
|
||||||
render_type: str,
|
|
||||||
data: Base,
|
data: Base,
|
||||||
) -> str:
|
) -> str:
|
||||||
if PYDANTIC_V2:
|
if PYDANTIC_V2:
|
||||||
return await env.get_template('index.html').render_async(
|
return await env.get_template('index.html').render_async(data=data.model_dump_json(by_alias=True))
|
||||||
path=render_type, data=data.model_dump_json(by_alias=True)
|
return await env.get_template('index.html').render_async(data=data.json(by_alias=True))
|
||||||
)
|
|
||||||
return await env.get_template('index.html').render_async(path=render_type, data=data.json(by_alias=True))
|
|
||||||
|
async def render_image(
|
||||||
|
data: Base,
|
||||||
|
) -> bytes:
|
||||||
|
async with HostPage(page=await render(data)) as page_hash:
|
||||||
|
return await screenshot(f'http://{get_self_netloc()}/host/{page_hash}.html#/{data.path}')
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['render']
|
__all__ = ['render']
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ class Piece(Enum):
|
|||||||
)
|
)
|
||||||
|
|
||||||
I5 = (
|
I5 = (
|
||||||
(True, True, True, True, True), # fmt: skip
|
(True, True, True, True, True),
|
||||||
)
|
) # fmt: skip
|
||||||
|
|
||||||
V = (
|
V = (
|
||||||
(True, False, False),
|
(True, False, False),
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
from abc import abstractmethod
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
@@ -8,6 +9,11 @@ from ...typedefs import Lang, Number
|
|||||||
|
|
||||||
|
|
||||||
class Base(BaseModel):
|
class Base(BaseModel):
|
||||||
|
@property
|
||||||
|
@abstractmethod
|
||||||
|
def path(self) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
lang: Lang
|
lang: Lang
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,16 @@
|
|||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from .base import Base, People
|
from .base import Base, People
|
||||||
|
|
||||||
|
|
||||||
class Bind(Base):
|
class Bind(Base):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v1/binding'
|
||||||
|
|
||||||
platform: Literal['TETR.IO', 'TOP', 'TOS']
|
platform: Literal['TETR.IO', 'TOP', 'TOS']
|
||||||
type: Literal['success', 'unknown', 'unlink', 'unverified', 'error']
|
type: Literal['success', 'unknown', 'unlink', 'unverified', 'error']
|
||||||
user: People
|
user: People
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from .......games.tetrio.api.typedefs import Rank
|
from ......games.tetrio.api.typedefs import Rank
|
||||||
from ......typedefs import Number
|
from .....typedefs import Number
|
||||||
from ....base import Base, People, Trending
|
from ...base import Base, People, Trending
|
||||||
from ...base import History
|
from ..base import History
|
||||||
|
|
||||||
|
|
||||||
class User(People):
|
class User(People):
|
||||||
@@ -45,6 +46,11 @@ class Singleplayer(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Info(Base):
|
class Info(Base):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v1/tetrio/info'
|
||||||
|
|
||||||
user: User
|
user: User
|
||||||
multiplayer: Multiplayer
|
multiplayer: Multiplayer
|
||||||
singleplayer: Singleplayer
|
singleplayer: Singleplayer
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from ......games.tetrio.api.typedefs import ValidRank
|
from ......games.tetrio.api.typedefs import ValidRank
|
||||||
from ...base import Base
|
from ...base import Base
|
||||||
@@ -13,5 +14,10 @@ class ItemData(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Data(Base):
|
class Data(Base):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v1/tetrio/rank'
|
||||||
|
|
||||||
items: dict[ValidRank, ItemData]
|
items: dict[ValidRank, ItemData]
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from .....typedefs import Number
|
from .....typedefs import Number
|
||||||
from ...base import Base, People, Trending
|
from ...base import Base, People, Trending
|
||||||
@@ -14,6 +15,11 @@ class Data(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Info(Base):
|
class Info(Base):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v1/top/info'
|
||||||
|
|
||||||
user: People
|
user: People
|
||||||
today: Data
|
today: Data
|
||||||
historical: Data
|
historical: Data
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from .....typedefs import Number
|
from .....typedefs import Number
|
||||||
from ...base import Base, People, Trending
|
from ...base import Base, People, Trending
|
||||||
@@ -37,6 +38,11 @@ class Singleplayer(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Info(Base):
|
class Info(Base):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v1/tos/info'
|
||||||
|
|
||||||
user: People
|
user: People
|
||||||
multiplayer: Multiplayer
|
multiplayer: Multiplayer
|
||||||
singleplayer: Singleplayer
|
singleplayer: Singleplayer
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from .......games.tetrio.api.typedefs import ValidRank
|
from .......games.tetrio.api.typedefs import ValidRank
|
||||||
from ......typedefs import Number
|
from ......typedefs import Number
|
||||||
@@ -23,5 +24,10 @@ class ItemData(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Data(Base):
|
class Data(Base):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v2/tetrio/rank'
|
||||||
|
|
||||||
items: dict[ValidRank, ItemData]
|
items: dict[ValidRank, ItemData]
|
||||||
updated_at: datetime
|
updated_at: datetime
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from .......games.tetrio.api.typedefs import ValidRank
|
from .......games.tetrio.api.typedefs import ValidRank
|
||||||
from ......typedefs import Number
|
from ......typedefs import Number
|
||||||
@@ -21,6 +22,11 @@ class SpecialData(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Data(Base):
|
class Data(Base):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v2/tetrio/rank/detail'
|
||||||
|
|
||||||
name: ValidRank
|
name: ValidRank
|
||||||
trending: Number
|
trending: Number
|
||||||
require_tr: Number
|
require_tr: Number
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from .base import Record as BaseRecord
|
from .base import Record as BaseRecord
|
||||||
from .base import Statistic as BaseStatistic
|
from .base import Statistic as BaseStatistic
|
||||||
|
|
||||||
@@ -9,4 +11,9 @@ class Statistic(BaseStatistic):
|
|||||||
|
|
||||||
|
|
||||||
class Record(BaseRecord):
|
class Record(BaseRecord):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v2/tetrio/record/blitz'
|
||||||
|
|
||||||
statistic: Statistic
|
statistic: Statistic
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from .base import Record as BaseRecord
|
from .base import Record as BaseRecord
|
||||||
from .base import Statistic
|
from .base import Statistic
|
||||||
|
|
||||||
|
|
||||||
class Record(BaseRecord):
|
class Record(BaseRecord):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v2/tetrio/record/sprint'
|
||||||
|
|
||||||
statistic: Statistic
|
statistic: Statistic
|
||||||
time: str
|
time: str
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from .....typedefs import Number
|
from .....typedefs import Number
|
||||||
from ...base import Base
|
from ...base import Base
|
||||||
@@ -34,6 +35,11 @@ class Game(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Data(Base):
|
class Data(Base):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v2/tetrio/tetra-league'
|
||||||
|
|
||||||
replay_id: str
|
replay_id: str
|
||||||
games: list[Game]
|
games: list[Game]
|
||||||
play_at: datetime
|
play_at: datetime
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ from datetime import datetime
|
|||||||
from typing import Literal
|
from typing import Literal
|
||||||
|
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
|
from .......games.tetrio.api.schemas.summaries.achievements import ArType, RankType
|
||||||
|
from .......games.tetrio.api.schemas.summaries.achievements import Rank as AchievementRank
|
||||||
from .......games.tetrio.api.typedefs import Rank
|
from .......games.tetrio.api.typedefs import Rank
|
||||||
from ......typedefs import Number
|
from ......typedefs import Number
|
||||||
from ....base import Avatar, Base, HistoryData
|
from ....base import Avatar, Base, HistoryData
|
||||||
@@ -15,6 +18,18 @@ class Badge(BaseModel):
|
|||||||
receive_at: datetime | None
|
receive_at: datetime | None
|
||||||
|
|
||||||
|
|
||||||
|
class Achievement(BaseModel):
|
||||||
|
key: int
|
||||||
|
rank_type: RankType
|
||||||
|
ar_type: ArType
|
||||||
|
stub: bool | None
|
||||||
|
rank: AchievementRank | None
|
||||||
|
achieved_score: float | None
|
||||||
|
pos: int | None
|
||||||
|
progress: float | None
|
||||||
|
total: int | None
|
||||||
|
|
||||||
|
|
||||||
class User(BaseModel):
|
class User(BaseModel):
|
||||||
id: str
|
id: str
|
||||||
name: str
|
name: str
|
||||||
@@ -37,7 +52,7 @@ class User(BaseModel):
|
|||||||
xp: Number
|
xp: Number
|
||||||
|
|
||||||
ar: Number
|
ar: Number
|
||||||
achievements: list[int]
|
achievements: list[Achievement]
|
||||||
|
|
||||||
playtime: str | None
|
playtime: str | None
|
||||||
join_at: datetime | None
|
join_at: datetime | None
|
||||||
@@ -118,6 +133,11 @@ class Zenith(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class Info(Base):
|
class Info(Base):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v2/tetrio/user/info'
|
||||||
|
|
||||||
user: User
|
user: User
|
||||||
tetra_league: TetraLeague | None
|
tetra_league: TetraLeague | None
|
||||||
zenith: Zenith | None
|
zenith: Zenith | None
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from typing_extensions import override
|
||||||
|
|
||||||
from .......games.tetrio.api.typedefs import Rank
|
from .......games.tetrio.api.typedefs import Rank
|
||||||
from ......typedefs import Number
|
from ......typedefs import Number
|
||||||
@@ -34,5 +35,10 @@ class Data(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
class List(Base):
|
class List(Base):
|
||||||
|
@property
|
||||||
|
@override
|
||||||
|
def path(self) -> str:
|
||||||
|
return 'v2/tetrio/user/list'
|
||||||
|
|
||||||
show_index: bool
|
show_index: bool
|
||||||
data: list[Data]
|
data: list[Data]
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ from ..config.config import CACHE_PATH, DATA_PATH, config
|
|||||||
|
|
||||||
driver = get_driver()
|
driver = get_driver()
|
||||||
|
|
||||||
TEMPLATES_DIR = DATA_PATH / 'templates'
|
TEMPLATES_DIR = config.tetris.dev.template_path or DATA_PATH / 'templates'
|
||||||
|
|
||||||
|
|
||||||
alc = on_alconna(Alconna('更新模板', Option('--revision', Args['revision', str], alias={'-R'})), permission=SUPERUSER)
|
alc = on_alconna(Alconna('更新模板', Option('--revision', Args['revision', str], alias={'-R'})), permission=SUPERUSER)
|
||||||
|
|
||||||
@@ -111,16 +112,6 @@ async def check_tag(tag: str) -> bool:
|
|||||||
).status_code != HTTPStatus.NOT_FOUND
|
).status_code != HTTPStatus.NOT_FOUND
|
||||||
|
|
||||||
|
|
||||||
@driver.on_startup
|
|
||||||
async def _():
|
|
||||||
if (path := (TEMPLATES_DIR / 'hash.sha256')).is_file() and await check_hash(path):
|
|
||||||
logger.success('模板验证成功')
|
|
||||||
return
|
|
||||||
if not await init_templates('latest'):
|
|
||||||
msg = '模板初始化失败'
|
|
||||||
raise RuntimeError(msg)
|
|
||||||
|
|
||||||
|
|
||||||
@alc.handle()
|
@alc.handle()
|
||||||
async def _(revision: str | None = None):
|
async def _(revision: str | None = None):
|
||||||
if revision is not None and not await check_tag(revision):
|
if revision is not None and not await check_tag(revision):
|
||||||
@@ -129,3 +120,17 @@ async def _(revision: str | None = None):
|
|||||||
if await init_templates(revision or 'latest'):
|
if await init_templates(revision or 'latest'):
|
||||||
await alc.finish('更新模板成功')
|
await alc.finish('更新模板成功')
|
||||||
await alc.finish('更新模板失败')
|
await alc.finish('更新模板失败')
|
||||||
|
|
||||||
|
|
||||||
|
if config.tetris.dev.enable_template_check:
|
||||||
|
# !https://github.com/python/mypy/issues/19516
|
||||||
|
# 只能放def后面了(
|
||||||
|
|
||||||
|
@driver.on_startup
|
||||||
|
async def _():
|
||||||
|
if (path := (TEMPLATES_DIR / 'hash.sha256')).is_file() and await check_hash(path):
|
||||||
|
logger.success('模板验证成功')
|
||||||
|
return
|
||||||
|
if not await init_templates('latest'):
|
||||||
|
msg = '模板初始化失败'
|
||||||
|
raise RuntimeError(msg)
|
||||||
|
|||||||
@@ -3,7 +3,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', 'unbind', 'query']
|
BaseCommandType: TypeAlias = Literal['bind', 'unbind', 'query']
|
||||||
TETRIOCommandType: TypeAlias = BaseCommandType | Literal['rank', 'config', 'list', 'record']
|
TETRIOCommandType: TypeAlias = BaseCommandType | Literal['rank', 'config', 'list', 'record', 'verify']
|
||||||
AllCommandType: TypeAlias = BaseCommandType | TETRIOCommandType
|
AllCommandType: TypeAlias = BaseCommandType | TETRIOCommandType
|
||||||
Me: TypeAlias = Literal[
|
Me: TypeAlias = Literal[
|
||||||
'我',
|
'我',
|
||||||
|
|||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -10,15 +10,15 @@ importers:
|
|||||||
devDependencies:
|
devDependencies:
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.3.3
|
specifier: ^3.3.3
|
||||||
version: 3.5.3
|
version: 3.6.2
|
||||||
|
|
||||||
packages:
|
packages:
|
||||||
|
|
||||||
prettier@3.5.3:
|
prettier@3.6.2:
|
||||||
resolution: {integrity: sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==}
|
resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==}
|
||||||
engines: {node: '>=14'}
|
engines: {node: '>=14'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
snapshots:
|
snapshots:
|
||||||
|
|
||||||
prettier@3.5.3: {}
|
prettier@3.6.2: {}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
#:schema https://json.schemastore.org/uv.json
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "nonebot-plugin-tetris-stats"
|
name = "nonebot-plugin-tetris-stats"
|
||||||
version = "1.8.1"
|
version = "1.11.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" }]
|
||||||
@@ -19,11 +21,10 @@ dependencies = [
|
|||||||
"nonebot-plugin-apscheduler>=0.5.0",
|
"nonebot-plugin-apscheduler>=0.5.0",
|
||||||
"nonebot-plugin-localstore>=0.7.1",
|
"nonebot-plugin-localstore>=0.7.1",
|
||||||
"nonebot-plugin-orm>=0.7.6",
|
"nonebot-plugin-orm>=0.7.6",
|
||||||
"nonebot-plugin-session>=0.3.2",
|
"nonebot-plugin-uninfo>=0.7.4",
|
||||||
"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-waiter>=0.8.0",
|
"nonebot-plugin-waiter>=0.8.0",
|
||||||
|
"nonebot-session-to-uninfo>=0.0.2",
|
||||||
"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",
|
||||||
@@ -58,6 +59,8 @@ dev = [
|
|||||||
"nonebot-adapter-kaiheila>=0.3.4",
|
"nonebot-adapter-kaiheila>=0.3.4",
|
||||||
"nonebot-adapter-onebot>=2.4.6",
|
"nonebot-adapter-onebot>=2.4.6",
|
||||||
"nonebot-adapter-qq>=1.5.3",
|
"nonebot-adapter-qq>=1.5.3",
|
||||||
|
"nonebot-plugin-orm[postgresql]>=0.8.2",
|
||||||
|
"nonebot-plugin-tarina-lang-turbo>=0.1.1",
|
||||||
"ruff>=0.7.1",
|
"ruff>=0.7.1",
|
||||||
]
|
]
|
||||||
typecheck = [
|
typecheck = [
|
||||||
@@ -159,7 +162,7 @@ defineConstant = { PYDANTIC_V2 = true }
|
|||||||
typeCheckingMode = "standard"
|
typeCheckingMode = "standard"
|
||||||
|
|
||||||
[tool.bumpversion]
|
[tool.bumpversion]
|
||||||
current_version = "1.8.1"
|
current_version = "1.11.0"
|
||||||
tag = true
|
tag = true
|
||||||
sign_tags = true
|
sign_tags = true
|
||||||
tag_name = "{new_version}"
|
tag_name = "{new_version}"
|
||||||
@@ -176,4 +179,4 @@ asyncio_mode = "auto"
|
|||||||
asyncio_default_fixture_loop_scope = "session"
|
asyncio_default_fixture_loop_scope = "session"
|
||||||
|
|
||||||
[tool.nonebot]
|
[tool.nonebot]
|
||||||
plugins = ["nonebot_plugin_tetris_stats"]
|
plugins = ["nonebot_plugin_tetris_stats", "nonebot_plugin_tarina_lang_turbo"]
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ from tests.fake_event import FakeGroupMessageEvent
|
|||||||
|
|
||||||
@pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
async def test_invalid_name(app: App) -> None:
|
async def test_invalid_name(app: App) -> None:
|
||||||
from nonebot_plugin_tetris_stats.games import alc
|
from nonebot_plugin_tetris_stats.games import alc # noqa: PLC0415
|
||||||
|
|
||||||
raw_message = 'tstats tetrio bind 芜湖'
|
raw_message = 'tstats tetrio bind 芜湖'
|
||||||
message = Message(raw_message)
|
message = Message(raw_message)
|
||||||
|
|||||||
Reference in New Issue
Block a user