feat(score): support osu-rx & osu-ap & all beatmap leaderboard like osu.ppy.sb
This commit is contained in:
@@ -32,3 +32,10 @@ FETCHER_CALLBACK_URL="http://localhost:8000/fetcher/callback"
|
|||||||
|
|
||||||
# 日志设置
|
# 日志设置
|
||||||
LOG_LEVEL="INFO"
|
LOG_LEVEL="INFO"
|
||||||
|
|
||||||
|
# 游戏设置
|
||||||
|
ENABLE_OSU_RX=false # 启用 osu!RX 统计数据
|
||||||
|
ENABLE_OSU_AP=false # 启用 osu!AP 统计数据
|
||||||
|
ENABLE_ALL_MODS_PP=false # 启用所有 Mod 的 PP 计算
|
||||||
|
ENABLE_SUPPORTER_FOR_ALL_USERS=false # 启用所有新注册用户的支持者状态
|
||||||
|
ENABLE_ALL_BEATMAP_LEADERBOARD=false # 启用所有谱面的排行榜(没有排行榜的谱面会以 APPROVED 状态返回)
|
||||||
|
|||||||
@@ -40,6 +40,13 @@ class Settings(BaseSettings):
|
|||||||
# 日志设置
|
# 日志设置
|
||||||
log_level: str = "INFO"
|
log_level: str = "INFO"
|
||||||
|
|
||||||
|
# 游戏设置
|
||||||
|
enable_osu_rx: bool = False
|
||||||
|
enable_osu_ap: bool = False
|
||||||
|
enable_all_mods_pp: bool = False
|
||||||
|
enable_supporter_for_all_users: bool = False
|
||||||
|
enable_all_beatmap_leaderboard: bool = False
|
||||||
|
|
||||||
@field_validator("fetcher_scopes", mode="before")
|
@field_validator("fetcher_scopes", mode="before")
|
||||||
def validate_fetcher_scopes(cls, v: Any) -> list[str]:
|
def validate_fetcher_scopes(cls, v: Any) -> list[str]:
|
||||||
if isinstance(v, str):
|
if isinstance(v, str):
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
from app.models.beatmap import BeatmapRankStatus
|
from app.models.beatmap import BeatmapRankStatus
|
||||||
from app.models.score import MODE_TO_INT, GameMode
|
from app.models.score import MODE_TO_INT, GameMode
|
||||||
|
|
||||||
@@ -62,10 +63,6 @@ class Beatmap(BeatmapBase, table=True):
|
|||||||
back_populates="beatmaps", sa_relationship_kwargs={"lazy": "joined"}
|
back_populates="beatmaps", sa_relationship_kwargs={"lazy": "joined"}
|
||||||
)
|
)
|
||||||
|
|
||||||
@property
|
|
||||||
def can_ranked(self) -> bool:
|
|
||||||
return self.beatmap_status > BeatmapRankStatus.PENDING
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_resp(cls, session: AsyncSession, resp: "BeatmapResp") -> "Beatmap":
|
async def from_resp(cls, session: AsyncSession, resp: "BeatmapResp") -> "Beatmap":
|
||||||
d = resp.model_dump()
|
d = resp.model_dump()
|
||||||
@@ -160,12 +157,20 @@ class BeatmapResp(BeatmapBase):
|
|||||||
) -> "BeatmapResp":
|
) -> "BeatmapResp":
|
||||||
from .score import Score
|
from .score import Score
|
||||||
|
|
||||||
|
beatmap_status = beatmap.beatmap_status
|
||||||
beatmap_ = beatmap.model_dump()
|
beatmap_ = beatmap.model_dump()
|
||||||
if query_mode is not None and beatmap.mode != query_mode:
|
if query_mode is not None and beatmap.mode != query_mode:
|
||||||
beatmap_["convert"] = True
|
beatmap_["convert"] = True
|
||||||
beatmap_["is_scoreable"] = beatmap.beatmap_status > BeatmapRankStatus.PENDING
|
beatmap_["is_scoreable"] = beatmap_status.has_leaderboard()
|
||||||
beatmap_["status"] = beatmap.beatmap_status.name.lower()
|
if (
|
||||||
beatmap_["ranked"] = beatmap.beatmap_status.value
|
settings.enable_all_beatmap_leaderboard
|
||||||
|
and not beatmap_status.has_leaderboard()
|
||||||
|
):
|
||||||
|
beatmap_["ranked"] = BeatmapRankStatus.APPROVED.value
|
||||||
|
beatmap_["status"] = BeatmapRankStatus.APPROVED.name.lower()
|
||||||
|
else:
|
||||||
|
beatmap_["status"] = beatmap_status.name.lower()
|
||||||
|
beatmap_["ranked"] = beatmap_status.value
|
||||||
beatmap_["mode_int"] = MODE_TO_INT[beatmap.mode]
|
beatmap_["mode_int"] = MODE_TO_INT[beatmap.mode]
|
||||||
if not from_set:
|
if not from_set:
|
||||||
beatmap_["beatmapset"] = await BeatmapsetResp.from_db(
|
beatmap_["beatmapset"] = await BeatmapsetResp.from_db(
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import TYPE_CHECKING, NotRequired, TypedDict
|
from typing import TYPE_CHECKING, NotRequired, TypedDict
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
from app.models.beatmap import BeatmapRankStatus, Genre, Language
|
from app.models.beatmap import BeatmapRankStatus, Genre, Language
|
||||||
from app.models.score import GameMode
|
from app.models.score import GameMode
|
||||||
|
|
||||||
@@ -228,11 +229,21 @@ class BeatmapsetResp(BeatmapsetBase):
|
|||||||
required=beatmapset.nominations_required,
|
required=beatmapset.nominations_required,
|
||||||
current=beatmapset.nominations_current,
|
current=beatmapset.nominations_current,
|
||||||
),
|
),
|
||||||
"status": beatmapset.beatmap_status.name.lower(),
|
"is_scoreable": beatmapset.beatmap_status.has_leaderboard(),
|
||||||
"ranked": beatmapset.beatmap_status.value,
|
|
||||||
"is_scoreable": beatmapset.beatmap_status > BeatmapRankStatus.PENDING,
|
|
||||||
**beatmapset.model_dump(),
|
**beatmapset.model_dump(),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beatmap_status = beatmapset.beatmap_status
|
||||||
|
if (
|
||||||
|
settings.enable_all_beatmap_leaderboard
|
||||||
|
and not beatmap_status.has_leaderboard()
|
||||||
|
):
|
||||||
|
update["status"] = BeatmapRankStatus.APPROVED.name.lower()
|
||||||
|
update["ranked"] = BeatmapRankStatus.APPROVED.value
|
||||||
|
else:
|
||||||
|
update["status"] = beatmap_status.name.lower()
|
||||||
|
update["ranked"] = beatmap_status.value
|
||||||
|
|
||||||
if session and user:
|
if session and user:
|
||||||
existing_favourite = (
|
existing_favourite = (
|
||||||
await session.exec(
|
await session.exec(
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ from app.calculator import (
|
|||||||
calculate_weighted_pp,
|
calculate_weighted_pp,
|
||||||
clamp,
|
clamp,
|
||||||
)
|
)
|
||||||
|
from app.config import settings
|
||||||
from app.database.team import TeamMember
|
from app.database.team import TeamMember
|
||||||
from app.models.model import RespWithCursor, UTCBaseModel
|
from app.models.model import RespWithCursor, UTCBaseModel
|
||||||
from app.models.mods import APIMod, mods_can_get_pp
|
from app.models.mods import APIMod, mods_can_get_pp
|
||||||
@@ -324,6 +325,13 @@ async def get_leaderboard(
|
|||||||
user: User | None = None,
|
user: User | None = None,
|
||||||
limit: int = 50,
|
limit: int = 50,
|
||||||
) -> tuple[list[Score], Score | None]:
|
) -> tuple[list[Score], Score | None]:
|
||||||
|
is_rx = "RX" in (mods or [])
|
||||||
|
is_ap = "AP" in (mods or [])
|
||||||
|
if settings.enable_osu_rx and is_rx:
|
||||||
|
mode = GameMode.OSURX
|
||||||
|
elif settings.enable_osu_ap and is_ap:
|
||||||
|
mode = GameMode.OSUAP
|
||||||
|
|
||||||
wheres = await _score_where(type, beatmap, mode, mods, user)
|
wheres = await _score_where(type, beatmap, mode, mods, user)
|
||||||
if wheres is None:
|
if wheres is None:
|
||||||
return [], None
|
return [], None
|
||||||
@@ -637,6 +645,14 @@ async def process_score(
|
|||||||
) -> Score:
|
) -> Score:
|
||||||
assert user.id
|
assert user.id
|
||||||
can_get_pp = info.passed and ranked and mods_can_get_pp(info.ruleset_id, info.mods)
|
can_get_pp = info.passed and ranked and mods_can_get_pp(info.ruleset_id, info.mods)
|
||||||
|
acronyms = [mod["acronym"] for mod in info.mods]
|
||||||
|
is_rx = "RX" in acronyms
|
||||||
|
is_ap = "AP" in acronyms
|
||||||
|
gamemode = INT_TO_MODE[info.ruleset_id]
|
||||||
|
if settings.enable_osu_rx and is_rx and gamemode == GameMode.OSU:
|
||||||
|
gamemode = GameMode.OSURX
|
||||||
|
elif settings.enable_osu_ap and is_ap and gamemode == GameMode.OSU:
|
||||||
|
gamemode = GameMode.OSUAP
|
||||||
score = Score(
|
score = Score(
|
||||||
accuracy=info.accuracy,
|
accuracy=info.accuracy,
|
||||||
max_combo=info.max_combo,
|
max_combo=info.max_combo,
|
||||||
@@ -648,7 +664,7 @@ async def process_score(
|
|||||||
total_score_without_mods=info.total_score_without_mods,
|
total_score_without_mods=info.total_score_without_mods,
|
||||||
beatmap_id=beatmap_id,
|
beatmap_id=beatmap_id,
|
||||||
ended_at=datetime.now(UTC),
|
ended_at=datetime.now(UTC),
|
||||||
gamemode=INT_TO_MODE[info.ruleset_id],
|
gamemode=gamemode,
|
||||||
started_at=score_token.created_at,
|
started_at=score_token.created_at,
|
||||||
user_id=user.id,
|
user_id=user.id,
|
||||||
preserve=info.passed,
|
preserve=info.passed,
|
||||||
|
|||||||
@@ -14,6 +14,20 @@ class BeatmapRankStatus(IntEnum):
|
|||||||
QUALIFIED = 3
|
QUALIFIED = 3
|
||||||
LOVED = 4
|
LOVED = 4
|
||||||
|
|
||||||
|
def has_leaderboard(self) -> bool:
|
||||||
|
return self in {
|
||||||
|
BeatmapRankStatus.RANKED,
|
||||||
|
BeatmapRankStatus.APPROVED,
|
||||||
|
BeatmapRankStatus.QUALIFIED,
|
||||||
|
BeatmapRankStatus.LOVED,
|
||||||
|
}
|
||||||
|
|
||||||
|
def has_pp(self) -> bool:
|
||||||
|
return self in {
|
||||||
|
BeatmapRankStatus.RANKED,
|
||||||
|
BeatmapRankStatus.APPROVED,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Genre(IntEnum):
|
class Genre(IntEnum):
|
||||||
ANY = 0
|
ANY = 0
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from copy import deepcopy
|
|||||||
import json
|
import json
|
||||||
from typing import Literal, NotRequired, TypedDict
|
from typing import Literal, NotRequired, TypedDict
|
||||||
|
|
||||||
|
from app.config import settings as app_settings
|
||||||
from app.path import STATIC_DIR
|
from app.path import STATIC_DIR
|
||||||
|
|
||||||
|
|
||||||
@@ -155,8 +156,15 @@ for i in range(4, 10):
|
|||||||
|
|
||||||
|
|
||||||
def mods_can_get_pp(ruleset_id: int, mods: list[APIMod]) -> bool:
|
def mods_can_get_pp(ruleset_id: int, mods: list[APIMod]) -> bool:
|
||||||
|
if app_settings.enable_all_mods_pp:
|
||||||
|
return True
|
||||||
ranked_mods = RANKED_MODS[ruleset_id]
|
ranked_mods = RANKED_MODS[ruleset_id]
|
||||||
for mod in mods:
|
for mod in mods:
|
||||||
|
if app_settings.enable_osu_rx and mod["acronym"] == "RX" and ruleset_id == 0:
|
||||||
|
continue
|
||||||
|
if app_settings.enable_osu_ap and mod["acronym"] == "AP" and ruleset_id == 0:
|
||||||
|
continue
|
||||||
|
|
||||||
mod["settings"] = mod.get("settings", {})
|
mod["settings"] = mod.get("settings", {})
|
||||||
if (settings := ranked_mods.get(mod["acronym"])) is None:
|
if (settings := ranked_mods.get(mod["acronym"])) is None:
|
||||||
return False
|
return False
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class GameMode(str, Enum):
|
|||||||
TAIKO = "taiko"
|
TAIKO = "taiko"
|
||||||
FRUITS = "fruits"
|
FRUITS = "fruits"
|
||||||
MANIA = "mania"
|
MANIA = "mania"
|
||||||
|
OSURX = "osurx"
|
||||||
|
OSUAP = "osuap"
|
||||||
|
|
||||||
def to_rosu(self) -> "rosu.GameMode":
|
def to_rosu(self) -> "rosu.GameMode":
|
||||||
import rosu_pp_py as rosu
|
import rosu_pp_py as rosu
|
||||||
@@ -25,6 +27,8 @@ class GameMode(str, Enum):
|
|||||||
GameMode.TAIKO: rosu.GameMode.Taiko,
|
GameMode.TAIKO: rosu.GameMode.Taiko,
|
||||||
GameMode.FRUITS: rosu.GameMode.Catch,
|
GameMode.FRUITS: rosu.GameMode.Catch,
|
||||||
GameMode.MANIA: rosu.GameMode.Mania,
|
GameMode.MANIA: rosu.GameMode.Mania,
|
||||||
|
GameMode.OSURX: rosu.GameMode.Osu,
|
||||||
|
GameMode.OSUAP: rosu.GameMode.Osu,
|
||||||
}[self]
|
}[self]
|
||||||
|
|
||||||
|
|
||||||
@@ -33,8 +37,11 @@ MODE_TO_INT = {
|
|||||||
GameMode.TAIKO: 1,
|
GameMode.TAIKO: 1,
|
||||||
GameMode.FRUITS: 2,
|
GameMode.FRUITS: 2,
|
||||||
GameMode.MANIA: 3,
|
GameMode.MANIA: 3,
|
||||||
|
GameMode.OSURX: 0,
|
||||||
|
GameMode.OSUAP: 0,
|
||||||
}
|
}
|
||||||
INT_TO_MODE = {v: k for k, v in MODE_TO_INT.items()}
|
INT_TO_MODE = {v: k for k, v in MODE_TO_INT.items()}
|
||||||
|
INT_TO_MODE[0] = GameMode.OSU
|
||||||
|
|
||||||
|
|
||||||
class Rank(str, Enum):
|
class Rank(str, Enum):
|
||||||
|
|||||||
@@ -159,14 +159,22 @@ async def register_user(
|
|||||||
country_code="CN", # 默认国家
|
country_code="CN", # 默认国家
|
||||||
join_date=datetime.now(UTC),
|
join_date=datetime.now(UTC),
|
||||||
last_visit=datetime.now(UTC),
|
last_visit=datetime.now(UTC),
|
||||||
|
is_supporter=settings.enable_supporter_for_all_users,
|
||||||
|
support_level=int(settings.enable_supporter_for_all_users),
|
||||||
)
|
)
|
||||||
db.add(new_user)
|
db.add(new_user)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(new_user)
|
await db.refresh(new_user)
|
||||||
assert new_user.id is not None, "New user ID should not be None"
|
assert new_user.id is not None, "New user ID should not be None"
|
||||||
for i in GameMode:
|
for i in [GameMode.OSU, GameMode.TAIKO, GameMode.FRUITS, GameMode.MANIA]:
|
||||||
statistics = UserStatistics(mode=i, user_id=new_user.id)
|
statistics = UserStatistics(mode=i, user_id=new_user.id)
|
||||||
db.add(statistics)
|
db.add(statistics)
|
||||||
|
if settings.enable_osu_rx:
|
||||||
|
statistics_rx = UserStatistics(mode=GameMode.OSURX, user_id=new_user.id)
|
||||||
|
db.add(statistics_rx)
|
||||||
|
if settings.enable_osu_ap:
|
||||||
|
statistics_ap = UserStatistics(mode=GameMode.OSUAP, user_id=new_user.id)
|
||||||
|
db.add(statistics_ap)
|
||||||
daily_challenge_user_stats = DailyChallengeStats(user_id=new_user.id)
|
daily_challenge_user_stats = DailyChallengeStats(user_id=new_user.id)
|
||||||
db.add(daily_challenge_user_stats)
|
db.add(daily_challenge_user_stats)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ from datetime import UTC, datetime
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from app.calculator import clamp
|
from app.calculator import clamp
|
||||||
|
from app.config import settings
|
||||||
from app.database import (
|
from app.database import (
|
||||||
Beatmap,
|
Beatmap,
|
||||||
Playlist,
|
Playlist,
|
||||||
@@ -31,7 +32,6 @@ from app.dependencies.database import get_db, get_redis
|
|||||||
from app.dependencies.fetcher import get_fetcher
|
from app.dependencies.fetcher import get_fetcher
|
||||||
from app.dependencies.user import get_current_user
|
from app.dependencies.user import get_current_user
|
||||||
from app.fetcher import Fetcher
|
from app.fetcher import Fetcher
|
||||||
from app.models.beatmap import BeatmapRankStatus
|
|
||||||
from app.models.room import RoomCategory
|
from app.models.room import RoomCategory
|
||||||
from app.models.score import (
|
from app.models.score import (
|
||||||
INT_TO_MODE,
|
INT_TO_MODE,
|
||||||
@@ -92,10 +92,9 @@ async def submit_score(
|
|||||||
db_beatmap = await Beatmap.get_or_fetch(db, fetcher, bid=beatmap)
|
db_beatmap = await Beatmap.get_or_fetch(db, fetcher, bid=beatmap)
|
||||||
except HTTPError:
|
except HTTPError:
|
||||||
raise HTTPException(status_code=404, detail="Beatmap not found")
|
raise HTTPException(status_code=404, detail="Beatmap not found")
|
||||||
ranked = db_beatmap.beatmap_status in {
|
ranked = (
|
||||||
BeatmapRankStatus.RANKED,
|
db_beatmap.beatmap_status.has_pp() | settings.enable_all_beatmap_leaderboard
|
||||||
BeatmapRankStatus.APPROVED,
|
)
|
||||||
}
|
|
||||||
score = await process_score(
|
score = await process_score(
|
||||||
current_user,
|
current_user,
|
||||||
beatmap,
|
beatmap,
|
||||||
|
|||||||
42
app/service/osu_rx_statistics.py
Normal file
42
app/service/osu_rx_statistics.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
|
from app.database.lazer_user import User
|
||||||
|
from app.database.statistics import UserStatistics
|
||||||
|
from app.dependencies.database import engine
|
||||||
|
from app.models.score import GameMode
|
||||||
|
|
||||||
|
from sqlalchemy import exists
|
||||||
|
from sqlmodel import select
|
||||||
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
|
|
||||||
|
async def create_rx_statistics():
|
||||||
|
async with AsyncSession(engine) as session:
|
||||||
|
users = (await session.exec(select(User.id))).all()
|
||||||
|
for i in users:
|
||||||
|
if settings.enable_osu_rx:
|
||||||
|
is_exist = (
|
||||||
|
await session.exec(
|
||||||
|
select(exists()).where(
|
||||||
|
UserStatistics.user_id == i,
|
||||||
|
UserStatistics.mode == GameMode.OSURX,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
if not is_exist:
|
||||||
|
statistics_rx = UserStatistics(mode=GameMode.OSURX, user_id=i)
|
||||||
|
session.add(statistics_rx)
|
||||||
|
if settings.enable_osu_ap:
|
||||||
|
is_exist = (
|
||||||
|
await session.exec(
|
||||||
|
select(exists()).where(
|
||||||
|
UserStatistics.user_id == i,
|
||||||
|
UserStatistics.mode == GameMode.OSUAP,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
if not is_exist:
|
||||||
|
statistics_ap = UserStatistics(mode=GameMode.OSUAP, user_id=i)
|
||||||
|
session.add(statistics_ap)
|
||||||
|
await session.commit()
|
||||||
@@ -7,12 +7,12 @@ import struct
|
|||||||
import time
|
import time
|
||||||
from typing import override
|
from typing import override
|
||||||
|
|
||||||
|
from app.config import settings
|
||||||
from app.database import Beatmap, User
|
from app.database import Beatmap, User
|
||||||
from app.database.score import Score
|
from app.database.score import Score
|
||||||
from app.database.score_token import ScoreToken
|
from app.database.score_token import ScoreToken
|
||||||
from app.dependencies.database import engine
|
from app.dependencies.database import engine
|
||||||
from app.dependencies.fetcher import get_fetcher
|
from app.dependencies.fetcher import get_fetcher
|
||||||
from app.models.beatmap import BeatmapRankStatus
|
|
||||||
from app.models.mods import mods_to_int
|
from app.models.mods import mods_to_int
|
||||||
from app.models.score import LegacyReplaySoloScoreInfo, ScoreStatistics
|
from app.models.score import LegacyReplaySoloScoreInfo, ScoreStatistics
|
||||||
from app.models.spectator_hub import (
|
from app.models.spectator_hub import (
|
||||||
@@ -244,7 +244,8 @@ class SpectatorHub(Hub[StoreClientState]):
|
|||||||
):
|
):
|
||||||
return
|
return
|
||||||
if (
|
if (
|
||||||
BeatmapRankStatus.PENDING < store.beatmap_status <= BeatmapRankStatus.LOVED
|
settings.enable_all_beatmap_leaderboard
|
||||||
|
and store.beatmap_status.has_leaderboard()
|
||||||
) and any(k.is_hit() and v > 0 for k, v in score.score_info.statistics.items()):
|
) and any(k.is_hit() and v > 0 for k, v in score.score_info.statistics.items()):
|
||||||
await self._process_score(store, client)
|
await self._process_score(store, client)
|
||||||
store.state = None
|
store.state = None
|
||||||
|
|||||||
2
main.py
2
main.py
@@ -14,6 +14,7 @@ from app.router import (
|
|||||||
signalr_router,
|
signalr_router,
|
||||||
)
|
)
|
||||||
from app.service.daily_challenge import daily_challenge_job
|
from app.service.daily_challenge import daily_challenge_job
|
||||||
|
from app.service.osu_rx_statistics import create_rx_statistics
|
||||||
|
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
@@ -21,6 +22,7 @@ from fastapi import FastAPI
|
|||||||
@asynccontextmanager
|
@asynccontextmanager
|
||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
# on startup
|
# on startup
|
||||||
|
await create_rx_statistics()
|
||||||
await get_fetcher() # 初始化 fetcher
|
await get_fetcher() # 初始化 fetcher
|
||||||
init_scheduler()
|
init_scheduler()
|
||||||
await daily_challenge_job()
|
await daily_challenge_job()
|
||||||
|
|||||||
116
migrations/versions/19cdc9ce4dcb_gamemode_add_osurx_osupp.py
Normal file
116
migrations/versions/19cdc9ce4dcb_gamemode_add_osurx_osupp.py
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
"""gamemode: add osurx & osupp
|
||||||
|
|
||||||
|
Revision ID: 19cdc9ce4dcb
|
||||||
|
Revises: fdb3822a30ba
|
||||||
|
Create Date: 2025-08-10 06:10:08.093591
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "19cdc9ce4dcb"
|
||||||
|
down_revision: str | Sequence[str] | None = "fdb3822a30ba"
|
||||||
|
branch_labels: str | Sequence[str] | None = None
|
||||||
|
depends_on: str | Sequence[str] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade() -> None:
|
||||||
|
"""Upgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column(
|
||||||
|
"lazer_users",
|
||||||
|
"playmode",
|
||||||
|
type_=sa.Enum(
|
||||||
|
"OSU", "TAIKO", "FRUITS", "MANIA", "OSURX", "OSUAP", name="gamemode"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"beatmaps",
|
||||||
|
"mode",
|
||||||
|
type_=sa.Enum(
|
||||||
|
"OSU", "TAIKO", "FRUITS", "MANIA", "OSURX", "OSUAP", name="gamemode"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"lazer_user_statistics",
|
||||||
|
"mode",
|
||||||
|
type_=sa.Enum(
|
||||||
|
"OSU", "TAIKO", "FRUITS", "MANIA", "OSURX", "OSUAP", name="gamemode"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"score_tokens",
|
||||||
|
"ruleset_id",
|
||||||
|
type_=sa.Enum(
|
||||||
|
"OSU", "TAIKO", "FRUITS", "MANIA", "OSURX", "OSUAP", name="gamemode"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"scores",
|
||||||
|
"gamemode",
|
||||||
|
type_=sa.Enum(
|
||||||
|
"OSU", "TAIKO", "FRUITS", "MANIA", "OSURX", "OSUAP", name="gamemode"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"best_scores",
|
||||||
|
"gamemode",
|
||||||
|
type_=sa.Enum(
|
||||||
|
"OSU", "TAIKO", "FRUITS", "MANIA", "OSURX", "OSUAP", name="gamemode"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"total_score_best_scores",
|
||||||
|
"gamemode",
|
||||||
|
type_=sa.Enum(
|
||||||
|
"OSU", "TAIKO", "FRUITS", "MANIA", "OSURX", "OSUAP", name="gamemode"
|
||||||
|
),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column(
|
||||||
|
"total_score_best_scores",
|
||||||
|
"gamemode",
|
||||||
|
type_=sa.Enum("OSU", "TAIKO", "FRUITS", "MANIA", name="gamemode"),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"best_scores",
|
||||||
|
"gamemode",
|
||||||
|
type_=sa.Enum("OSU", "TAIKO", "FRUITS", "MANIA", name="gamemode"),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"scores",
|
||||||
|
"gamemode",
|
||||||
|
type_=sa.Enum("OSU", "TAIKO", "FRUITS", "MANIA", name="gamemode"),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"score_tokens",
|
||||||
|
"ruleset_id",
|
||||||
|
type_=sa.Enum("OSU", "TAIKO", "FRUITS", "MANIA", name="gamemode"),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"lazer_user_statistics",
|
||||||
|
"mode",
|
||||||
|
type_=sa.Enum("OSU", "TAIKO", "FRUITS", "MANIA", name="gamemode"),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"beatmaps",
|
||||||
|
"mode",
|
||||||
|
type_=sa.Enum("OSU", "TAIKO", "FRUITS", "MANIA", name="gamemode"),
|
||||||
|
)
|
||||||
|
op.alter_column(
|
||||||
|
"lazer_users",
|
||||||
|
"playmode",
|
||||||
|
type_=sa.Enum("OSU", "TAIKO", "FRUITS", "MANIA", name="gamemode"),
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
Reference in New Issue
Block a user