feat(detector): 新增封禁谱面表并检查可疑谱面

- 在数据库中添加了封禁谱面表(banned_beatmaps)
- 实现了检查可疑谱面的功能,将不符合规范的谱面添加到封禁列表
- 更新了相关的模型和迁移文件
This commit is contained in:
chenjintang-shrimp
2025-08-15 08:05:02 +00:00
parent d87839f86e
commit 7c7c68c163
6 changed files with 158 additions and 2 deletions

View File

@@ -1,7 +1,9 @@
from __future__ import annotations
import math
import os
from typing import TYPE_CHECKING
import zipfile
from app.config import settings
from app.log import logger
@@ -9,6 +11,12 @@ from app.models.beatmap import BeatmapAttributes
from app.models.mods import APIMod
from app.models.score import GameMode
import httpx
from osupyparser import OsuFile
from osupyparser.osu.objects import Slider
from sqlmodel import Session, create_engine, select
from sqlmodel.ext.asyncio.session import AsyncSession
try:
import rosu_pp_py as rosu
except ImportError:
@@ -78,9 +86,21 @@ def calculate_pp(
)
attrs = perf.calculate(map)
pp = attrs.pp
engine = create_engine(settings.database_url)
from app.database.beatmap import BannedBeatmaps
beatmap_banned = False
with Session(engine) as session:
beatmap_id = session.exec(
select(BannedBeatmaps).where(BannedBeatmaps.beatmap_id == score.beatmap_id)
).first()
if beatmap_id:
beatmap_banned = True
# mrekk bp1: 2048pp; ppy-sb top1 rxbp1: 2198pp
if settings.suspicious_score_check and (
(attrs.difficulty.stars > 25 and score.accuracy < 0.8) or pp > 2300
beatmap_banned
or (attrs.difficulty.stars > 25 and score.accuracy < 0.8)
or pp > 2300
):
logger.warning(
f"User {score.user_id} played {score.beatmap_id} with {pp=} "
@@ -225,6 +245,24 @@ def calculate_score_to_level(total_score: int) -> float:
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
99999999999,
]
remaining_score = total_score
@@ -251,3 +289,50 @@ def calculate_weighted_pp(pp: float, index: int) -> float:
def calculate_weighted_acc(acc: float, index: int) -> float:
return calculate_pp_weight(index) * acc if acc > 0 else 0.0
async def get_suspscious_beatmap(beatmapset_id: int, session: AsyncSession):
url = (
f"https://txy1.sayobot.cn/beatmaps/download/novideo/{beatmapset_id}?server=auto"
)
async with httpx.AsyncClient() as client:
resp = await client.get(url)
if resp.status_code == 200:
import aiofiles
async with aiofiles.open(f"temp_beatmaps/{beatmapset_id}.osz", "wb") as f:
await f.write(resp.content)
with zipfile.ZipFile(f"temp_beatmaps/{beatmapset_id}.osz", "r") as beatmap_ref:
beatmap_ref.extractall(f"temp_beatmaps/{beatmapset_id}")
os.remove(f"temp_beatmaps/{beatmapset_id}.osz")
all_osu_files = []
for root, dirs, files in os.walk(f"temp_beatmaps/{beatmapset_id}"):
for name in files:
if name.endswith(".osu"):
all_osu_files.append(os.path.join(root, name))
for file in all_osu_files:
osufile = OsuFile(file).parse_file()
for obj in osufile.hit_objects:
if obj.pos.x < 0 or obj.pos.y < 0 or obj.pos.x > 512 or obj.pos.y > 384:
# 延迟导入以解决循环导入问题
from app.database.beatmap import BannedBeatmaps
session.add(
BannedBeatmaps(id=osufile.beatmap_id, beatmap_id=osufile.beatmap_id)
)
break
if type(obj) is Slider:
for point in obj.points:
if point.x < 0 or point.y < 0 or point.x > 512 or point.y > 384:
# 延迟导入以解决循环导入问题
from app.database.beatmap import BannedBeatmaps
session.add(
BannedBeatmaps(
id=osufile.beatmap_id, beatmap_id=osufile.beatmap_id
)
)
break
os.remove(file)
os.remove(f"temp_beatmaps/{beatmapset_id}")
return None

View File

@@ -3,7 +3,6 @@ from datetime import datetime
import hashlib
from typing import TYPE_CHECKING
from app.calculator import calculate_beatmap_attribute
from app.config import settings
from app.models.beatmap import BeatmapAttributes, BeatmapRankStatus
from app.models.mods import APIMod
@@ -202,6 +201,12 @@ class BeatmapResp(BeatmapBase):
return cls.model_validate(beatmap_)
class BannedBeatmaps(SQLModel, table=True):
__tablename__ = "banned_beatmaps" # pyright: ignore[reportAssignmentType]
id: int = Field(primary_key=True, index=True)
beatmap_id: int = Field(index=True)
async def calculate_beatmap_attributes(
beatmap_id: int,
ruleset: GameMode,
@@ -216,6 +221,9 @@ async def calculate_beatmap_attributes(
if await redis.exists(key):
return BeatmapAttributes.model_validate_json(await redis.get(key)) # pyright: ignore[reportArgumentType]
resp = await fetcher.get_or_fetch_beatmap_raw(redis, beatmap_id)
# 延迟导入以解决循环导入问题
from app.calculator import calculate_beatmap_attribute
attr = await asyncio.get_event_loop().run_in_executor(
None, calculate_beatmap_attribute, resp, ruleset, mods_
)

View File

@@ -183,6 +183,10 @@ class Beatmapset(AsyncAttrs, BeatmapsetBase, table=True):
if not beatmapset:
resp = await fetcher.get_beatmapset(sid)
beatmapset = await cls.from_resp(session, resp)
# 检查可疑谱面
from app.calculator import get_suspscious_beatmap
await get_suspscious_beatmap(sid, session)
return beatmapset