mirror of
https://github.com/A-Minos/nonebot-plugin-tetris-stats.git
synced 2026-03-05 05:36:54 +08:00
✨ 适配 Trending (#539)
* ✨ 适配 v1 tetrio 的 Trending * 🗃️ 添加 compare_delta 配置项 * 🗃️ 添加 TETRIOLeagueUserMap 索引表 * ✨ 添加对比时间配置项 * ✨ 添加 compare_delta 解析函数 * ✨ 添加 Trending 类的 compare 方法 * 🗃️ 移除不正确的复合索引 * ✨ 定时任务拉取tl数据时同步更新索引 * ✨ 适配 trending * 🐛 修复 find_entry 在无 uid 时的索引返回逻辑 * 📝 修正 compare_delta 迁移父迁移注释 * 🗃️ 为非 PostgreSQL 回填迁移补充外键约束 * 🔒 迁移中使用参数绑定设置 PG 内存参数 * ✨ 修正 Trends 的 vs 为 adpm * 🐛 修正获取玩家 ID 的范围
This commit is contained in:
@@ -1,13 +1,17 @@
|
||||
from datetime import datetime, timedelta, timezone
|
||||
|
||||
from nonebot.adapters import Event
|
||||
from nonebot.matcher import Matcher
|
||||
from nonebot_plugin_alconna import At
|
||||
from nonebot_plugin_alconna.uniseg import Image, UniMessage
|
||||
from nonebot_plugin_orm import get_session
|
||||
from nonebot_plugin_orm import AsyncSession, get_session
|
||||
from nonebot_plugin_uninfo import Uninfo
|
||||
from nonebot_plugin_uninfo.orm import get_session_persist_id
|
||||
from nonebot_plugin_user import User as NBUser
|
||||
from nonebot_plugin_user import get_user
|
||||
from sqlalchemy import select
|
||||
|
||||
from ...db import query_bind_info, trigger
|
||||
from ...db import query_bind_info, resolve_compare_delta, trigger
|
||||
from ...i18n import Lang
|
||||
from ...utils.exception import FallbackError
|
||||
from ...utils.lang import get_lang
|
||||
@@ -20,17 +24,73 @@ from ...utils.render.schemas.v1.top.info import Info
|
||||
from ...utils.typedefs import Me
|
||||
from . import alc
|
||||
from .api import Player
|
||||
from .api.models import TOPHistoricalData
|
||||
from .api.schemas.user_profile import Data, UserProfile
|
||||
from .constant import GAME_TYPE
|
||||
from .models import TOPUserConfig
|
||||
|
||||
UTC = timezone.utc
|
||||
|
||||
|
||||
async def get_compare_profile(session: AsyncSession, user_name: str, target_time: datetime) -> UserProfile | None:
|
||||
before = await session.scalar(
|
||||
select(TOPHistoricalData)
|
||||
.where(
|
||||
TOPHistoricalData.user_unique_identifier == user_name,
|
||||
TOPHistoricalData.api_type == 'User Profile',
|
||||
TOPHistoricalData.update_time <= target_time,
|
||||
)
|
||||
.order_by(TOPHistoricalData.update_time.desc())
|
||||
.limit(1)
|
||||
)
|
||||
after = await session.scalar(
|
||||
select(TOPHistoricalData)
|
||||
.where(
|
||||
TOPHistoricalData.user_unique_identifier == user_name,
|
||||
TOPHistoricalData.api_type == 'User Profile',
|
||||
TOPHistoricalData.update_time >= target_time,
|
||||
)
|
||||
.order_by(TOPHistoricalData.update_time.asc())
|
||||
.limit(1)
|
||||
)
|
||||
if before is None:
|
||||
selected = after
|
||||
elif after is None:
|
||||
selected = before
|
||||
else:
|
||||
selected = (
|
||||
before
|
||||
if abs((target_time - before.update_time).total_seconds())
|
||||
<= abs((target_time - after.update_time).total_seconds())
|
||||
else after
|
||||
)
|
||||
if selected is None or not isinstance(selected.data, UserProfile):
|
||||
return None
|
||||
return selected.data
|
||||
|
||||
|
||||
def compare_metrics(
|
||||
current: TetrisMetricsBasicWithLPM, compare: TetrisMetricsBasicWithLPM | None
|
||||
) -> tuple[Trending, Trending]:
|
||||
if compare is None:
|
||||
return Trending.KEEP, Trending.KEEP
|
||||
return Trending.compare(compare.lpm, current.lpm), Trending.compare(compare.apm, current.apm)
|
||||
|
||||
|
||||
@alc.assign('TOP.query')
|
||||
async def _(event: Event, matcher: Matcher, target: At | Me, event_session: Uninfo):
|
||||
async def _( # noqa: PLR0913
|
||||
user: NBUser,
|
||||
event: Event,
|
||||
matcher: Matcher,
|
||||
target: At | Me,
|
||||
event_session: Uninfo,
|
||||
compare: timedelta | None = None,
|
||||
):
|
||||
async with trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='query',
|
||||
command_args=[],
|
||||
command_args=[f'--compare {compare}'] if compare is not None else [],
|
||||
):
|
||||
async with get_session() as session:
|
||||
bind = await query_bind_info(
|
||||
@@ -40,17 +100,21 @@ async def _(event: Event, matcher: Matcher, target: At | Me, event_session: Unin
|
||||
),
|
||||
game_platform=GAME_TYPE,
|
||||
)
|
||||
if bind is None:
|
||||
await matcher.finish(Lang.bind.not_found())
|
||||
if bind is None:
|
||||
await matcher.finish(Lang.bind.not_found())
|
||||
compare_delta = await resolve_compare_delta(TOPUserConfig, session, user.id, compare)
|
||||
player = Player(user_name=bind.game_account, trust=True)
|
||||
profile = await player.get_profile()
|
||||
compare_profile = await get_compare_profile(
|
||||
session,
|
||||
profile.user_name,
|
||||
datetime.now(tz=UTC) - compare_delta,
|
||||
)
|
||||
await (
|
||||
UniMessage.i18n(Lang.interaction.warning.unverified)
|
||||
+ (
|
||||
UniMessage('\n')
|
||||
if not (
|
||||
result := await make_query_result(
|
||||
await Player(user_name=bind.game_account, trust=True).get_profile()
|
||||
)
|
||||
).has(Image)
|
||||
if not (result := await make_query_result(profile, compare_profile)).has(Image)
|
||||
else UniMessage()
|
||||
)
|
||||
+ result
|
||||
@@ -58,14 +122,22 @@ async def _(event: Event, matcher: Matcher, target: At | Me, event_session: Unin
|
||||
|
||||
|
||||
@alc.assign('TOP.query')
|
||||
async def _(account: Player, event_session: Uninfo):
|
||||
async def _(user: NBUser, account: Player, event_session: Uninfo, compare: timedelta | None = None):
|
||||
async with trigger(
|
||||
session_persist_id=await get_session_persist_id(event_session),
|
||||
game_platform=GAME_TYPE,
|
||||
command_type='query',
|
||||
command_args=[],
|
||||
command_args=[f'--compare {compare}'] if compare is not None else [],
|
||||
):
|
||||
await (await make_query_result(await account.get_profile())).finish()
|
||||
async with get_session() as session:
|
||||
compare_delta = await resolve_compare_delta(TOPUserConfig, session, user.id, compare)
|
||||
profile = await account.get_profile()
|
||||
compare_profile = await get_compare_profile(
|
||||
session,
|
||||
profile.user_name,
|
||||
datetime.now(tz=UTC) - compare_delta,
|
||||
)
|
||||
await (await make_query_result(profile, compare_profile)).finish()
|
||||
|
||||
|
||||
def get_avg_metrics(data: list[Data]) -> TetrisMetricsBasicWithLPM:
|
||||
@@ -77,29 +149,33 @@ def get_avg_metrics(data: list[Data]) -> TetrisMetricsBasicWithLPM:
|
||||
return get_metrics(lpm=total_lpm / num, apm=total_apm / num)
|
||||
|
||||
|
||||
async def make_query_image(profile: UserProfile) -> bytes:
|
||||
async def make_query_image(profile: UserProfile, compare: UserProfile | None) -> bytes:
|
||||
if profile.today is None or profile.total is None:
|
||||
raise FallbackError
|
||||
today = get_metrics(lpm=profile.today.lpm, apm=profile.today.apm)
|
||||
history = get_avg_metrics(profile.total)
|
||||
compare_today = get_metrics(lpm=compare.today.lpm, apm=compare.today.apm) if compare and compare.today else None
|
||||
compare_history = get_avg_metrics(compare.total) if compare is not None and compare.total is not None else None
|
||||
today_lpm_trending, today_apm_trending = compare_metrics(today, compare_today)
|
||||
history_lpm_trending, history_apm_trending = compare_metrics(history, compare_history)
|
||||
return await render_image(
|
||||
Info(
|
||||
user=People(avatar=get_avatar(profile.user_name), name=profile.user_name),
|
||||
today=InfoData(
|
||||
pps=today.pps,
|
||||
lpm=today.lpm,
|
||||
lpm_trending=Trending.KEEP,
|
||||
lpm_trending=today_lpm_trending,
|
||||
apm=today.apm,
|
||||
apl=today.apl,
|
||||
apm_trending=Trending.KEEP,
|
||||
apm_trending=today_apm_trending,
|
||||
),
|
||||
historical=InfoData(
|
||||
pps=history.pps,
|
||||
lpm=history.lpm,
|
||||
lpm_trending=Trending.KEEP,
|
||||
lpm_trending=history_lpm_trending,
|
||||
apm=history.apm,
|
||||
apl=history.apl,
|
||||
apm_trending=Trending.KEEP,
|
||||
apm_trending=history_apm_trending,
|
||||
),
|
||||
lang=get_lang(),
|
||||
),
|
||||
@@ -125,9 +201,9 @@ def make_query_text(profile: UserProfile) -> UniMessage:
|
||||
return UniMessage(message)
|
||||
|
||||
|
||||
async def make_query_result(profile: UserProfile) -> UniMessage:
|
||||
async def make_query_result(profile: UserProfile, compare: UserProfile | None) -> UniMessage:
|
||||
try:
|
||||
return UniMessage.image(raw=await make_query_image(profile))
|
||||
return UniMessage.image(raw=await make_query_image(profile, compare))
|
||||
except FallbackError:
|
||||
...
|
||||
return make_query_text(profile)
|
||||
|
||||
Reference in New Issue
Block a user