上传源码

This commit is contained in:
咕谷酱
2025-07-19 12:08:10 +08:00
parent 0595b7f530
commit c0246440f3
29 changed files with 5426 additions and 0 deletions

1
app/__init__.py Normal file
View File

@@ -0,0 +1 @@
# 初始化文件

159
app/auth.py Normal file
View File

@@ -0,0 +1,159 @@
from datetime import datetime, timedelta
from typing import Optional
from jose import JWTError, jwt
from passlib.context import CryptContext
from sqlalchemy.orm import Session
from app.database import User as DBUser, OAuthToken
from app.config import settings
import secrets
import string
import hashlib
import bcrypt
# 密码哈希上下文
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
# bcrypt 缓存(模拟应用状态缓存)
bcrypt_cache = {}
def verify_password_legacy(plain_password: str, bcrypt_hash: str) -> bool:
"""
验证密码 - 使用 osu! 的验证方式
1. 明文密码 -> MD5哈希
2. MD5哈希 -> bcrypt验证
"""
# 1. 明文密码转 MD5
pw_md5 = hashlib.md5(plain_password.encode()).hexdigest().encode()
# 2. 检查缓存
if bcrypt_hash in bcrypt_cache:
return bcrypt_cache[bcrypt_hash] == pw_md5
# 3. 如果缓存中没有,进行 bcrypt 验证
try:
# 验证 MD5 哈希与 bcrypt 哈希
is_valid = bcrypt.checkpw(pw_md5, bcrypt_hash.encode())
# 如果验证成功,将结果缓存
if is_valid:
bcrypt_cache[bcrypt_hash] = pw_md5
return is_valid
except Exception as e:
print(f"Password verification error: {e}")
return False
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""验证密码(向后兼容)"""
# 首先尝试新的验证方式
if verify_password_legacy(plain_password, hashed_password):
return True
# 如果失败,尝试标准 bcrypt 验证
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
"""生成密码哈希 - 使用 osu! 的方式"""
# 1. 明文密码 -> MD5
pw_md5 = hashlib.md5(password.encode()).hexdigest().encode()
# 2. MD5 -> bcrypt
pw_bcrypt = bcrypt.hashpw(pw_md5, bcrypt.gensalt())
return pw_bcrypt.decode()
def authenticate_user_legacy(db: Session, name: str, password: str) -> Optional[DBUser]:
"""
验证用户身份 - 使用类似 from_login 的逻辑
"""
# 1. 明文密码转 MD5
pw_md5 = hashlib.md5(password.encode()).hexdigest()
# 2. 根据用户名查找用户
user = db.query(DBUser).filter(DBUser.name == name).first()
if not user:
return None
# 3. 验证密码
if not user.pw_bcrypt:
return None
# 4. 检查缓存
if user.pw_bcrypt in bcrypt_cache:
if bcrypt_cache[user.pw_bcrypt] == pw_md5.encode():
return user
else:
return None
# 5. 验证 bcrypt
try:
is_valid = bcrypt.checkpw(pw_md5.encode(), user.pw_bcrypt.encode())
if is_valid:
# 缓存验证结果
bcrypt_cache[user.pw_bcrypt] = pw_md5.encode()
return user
except Exception as e:
print(f"Authentication error for user {name}: {e}")
return None
def authenticate_user(db: Session, username: str, password: str) -> Optional[DBUser]:
"""验证用户身份"""
return authenticate_user_legacy(db, username, password)
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
"""创建访问令牌"""
to_encode = data.copy()
if expires_delta:
expire = datetime.utcnow() + expires_delta
else:
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
to_encode.update({"exp": expire})
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
return encoded_jwt
def generate_refresh_token() -> str:
"""生成刷新令牌"""
length = 64
characters = string.ascii_letters + string.digits
return ''.join(secrets.choice(characters) for _ in range(length))
def verify_token(token: str) -> Optional[dict]:
"""验证访问令牌"""
try:
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
return payload
except JWTError:
return None
def store_token(db: Session, user_id: int, access_token: str, refresh_token: str, expires_in: int) -> OAuthToken:
"""存储令牌到数据库"""
expires_at = datetime.utcnow() + timedelta(seconds=expires_in)
# 删除用户的旧令牌
db.query(OAuthToken).filter(OAuthToken.user_id == user_id).delete()
# 创建新令牌记录
token_record = OAuthToken(
user_id=user_id,
access_token=access_token,
refresh_token=refresh_token,
expires_at=expires_at
)
db.add(token_record)
db.commit()
db.refresh(token_record)
return token_record
def get_token_by_access_token(db: Session, access_token: str) -> Optional[OAuthToken]:
"""根据访问令牌获取令牌记录"""
return db.query(OAuthToken).filter(
OAuthToken.access_token == access_token,
OAuthToken.expires_at > datetime.utcnow()
).first()
def get_token_by_refresh_token(db: Session, refresh_token: str) -> Optional[OAuthToken]:
"""根据刷新令牌获取令牌记录"""
return db.query(OAuthToken).filter(
OAuthToken.refresh_token == refresh_token,
OAuthToken.expires_at > datetime.utcnow()
).first()

25
app/config.py Normal file
View File

@@ -0,0 +1,25 @@
import os
from dotenv import load_dotenv
load_dotenv()
class Settings:
# 数据库设置
DATABASE_URL: str = os.getenv("DATABASE_URL", "mysql+pymysql://root:password@localhost:3306/osu_api")
REDIS_URL: str = os.getenv("REDIS_URL", "redis://localhost:6379/0")
# JWT 设置
SECRET_KEY: str = os.getenv("SECRET_KEY", "your-secret-key-here")
ALGORITHM: str = os.getenv("ALGORITHM", "HS256")
ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "1440"))
# OAuth 设置
OSU_CLIENT_ID: str = os.getenv("OSU_CLIENT_ID", "5")
OSU_CLIENT_SECRET: str = os.getenv("OSU_CLIENT_SECRET", "FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk")
# 服务器设置
HOST: str = os.getenv("HOST", "0.0.0.0")
PORT: int = int(os.getenv("PORT", "8000"))
DEBUG: bool = os.getenv("DEBUG", "True").lower() == "true"
settings = Settings()

403
app/database copy.py Normal file
View File

@@ -0,0 +1,403 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Float, Text, JSON, ForeignKey, Date, DECIMAL
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from datetime import datetime
Base = declarative_base()
class User(Base):
__tablename__ = "users"
# 主键
id = Column(Integer, primary_key=True, index=True)
# 基本信息(匹配 migrations 中的结构)
name = Column(String(32), unique=True, index=True, nullable=False) # 用户名
safe_name = Column(String(32), unique=True, index=True, nullable=False) # 安全用户名
email = Column(String(254), unique=True, index=True, nullable=False)
priv = Column(Integer, default=1, nullable=False) # 权限
pw_bcrypt = Column(String(60), nullable=False) # bcrypt 哈希密码
country = Column(String(2), default='CN', nullable=False) # 国家代码
# 状态和时间
silence_end = Column(Integer, default=0, nullable=False)
donor_end = Column(Integer, default=0, nullable=False)
creation_time = Column(Integer, default=0, nullable=False) # Unix 时间戳
latest_activity = Column(Integer, default=0, nullable=False) # Unix 时间戳
# 游戏相关
preferred_mode = Column(Integer, default=0, nullable=False) # 偏好游戏模式
play_style = Column(Integer, default=0, nullable=False) # 游戏风格
# 扩展信息
clan_id = Column(Integer, default=0, nullable=False)
clan_priv = Column(Integer, default=0, nullable=False)
custom_badge_name = Column(String(16))
custom_badge_icon = Column(String(64))
userpage_content = Column(String(2048))
api_key = Column(String(36), unique=True)
# 虚拟字段用于兼容性
@property
def username(self):
return self.name
@property
def country_code(self):
return self.country
@property
def join_date(self):
return datetime.fromtimestamp(self.creation_time) if self.creation_time > 0 else datetime.utcnow()
@property
def last_visit(self):
return datetime.fromtimestamp(self.latest_activity) if self.latest_activity > 0 else None
# 关联关系
lazer_profile = relationship("LazerUserProfile", back_populates="user", uselist=False, cascade="all, delete-orphan")
lazer_statistics = relationship("LazerUserStatistics", back_populates="user", cascade="all, delete-orphan")
lazer_achievements = relationship("LazerUserAchievement", back_populates="user", cascade="all, delete-orphan")
# ============================================
# Lazer API 专用表模型
# ============================================
class LazerUserProfile(Base):
__tablename__ = "lazer_user_profiles"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
# 基本状态字段
is_active = Column(Boolean, default=True)
is_bot = Column(Boolean, default=False)
is_deleted = Column(Boolean, default=False)
is_online = Column(Boolean, default=True)
is_supporter = Column(Boolean, default=False)
is_restricted = Column(Boolean, default=False)
session_verified = Column(Boolean, default=False)
has_supported = Column(Boolean, default=False)
pm_friends_only = Column(Boolean, default=False)
# 基本资料字段
default_group = Column(String(50), default='default')
last_visit = Column(DateTime)
join_date = Column(DateTime)
profile_colour = Column(String(7))
profile_hue = Column(Integer)
# 社交媒体和个人资料字段
avatar_url = Column(String(500))
cover_url = Column(String(500))
discord = Column(String(100))
twitter = Column(String(100))
website = Column(String(500))
title = Column(String(100))
title_url = Column(String(500))
interests = Column(Text)
location = Column(String(100))
occupation = Column(String(100))
# 游戏相关字段
playmode = Column(String(10), default='osu')
support_level = Column(Integer, default=0)
max_blocks = Column(Integer, default=100)
max_friends = Column(Integer, default=500)
post_count = Column(Integer, default=0)
# 页面内容
page_html = Column(Text)
page_raw = Column(Text)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关联关系
user = relationship("User", back_populates="lazer_profile")
class LazerUserCountry(Base):
__tablename__ = "lazer_user_countries"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
code = Column(String(2), nullable=False)
name = Column(String(100), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class LazerUserKudosu(Base):
__tablename__ = "lazer_user_kudosu"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
available = Column(Integer, default=0)
total = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class LazerUserCounts(Base):
__tablename__ = "lazer_user_counts"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
# 统计计数字段
beatmap_playcounts_count = Column(Integer, default=0)
comments_count = Column(Integer, default=0)
favourite_beatmapset_count = Column(Integer, default=0)
follower_count = Column(Integer, default=0)
graveyard_beatmapset_count = Column(Integer, default=0)
guest_beatmapset_count = Column(Integer, default=0)
loved_beatmapset_count = Column(Integer, default=0)
mapping_follower_count = Column(Integer, default=0)
nominated_beatmapset_count = Column(Integer, default=0)
pending_beatmapset_count = Column(Integer, default=0)
ranked_beatmapset_count = Column(Integer, default=0)
ranked_and_approved_beatmapset_count = Column(Integer, default=0)
unranked_beatmapset_count = Column(Integer, default=0)
scores_best_count = Column(Integer, default=0)
scores_first_count = Column(Integer, default=0)
scores_pinned_count = Column(Integer, default=0)
scores_recent_count = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class LazerUserStatistics(Base):
__tablename__ = "lazer_user_statistics"
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
mode = Column(String(10), nullable=False, default='osu')
# 基本命中统计
count_100 = Column(Integer, default=0)
count_300 = Column(Integer, default=0)
count_50 = Column(Integer, default=0)
count_miss = Column(Integer, default=0)
# 等级信息
level_current = Column(Integer, default=1)
level_progress = Column(Integer, default=0)
# 排名信息
global_rank = Column(Integer)
global_rank_exp = Column(Integer)
country_rank = Column(Integer)
# PP 和分数
pp = Column(DECIMAL(10, 2), default=0.00)
pp_exp = Column(DECIMAL(10, 2), default=0.00)
ranked_score = Column(Integer, default=0)
hit_accuracy = Column(DECIMAL(5, 2), default=0.00)
total_score = Column(Integer, default=0)
total_hits = Column(Integer, default=0)
maximum_combo = Column(Integer, default=0)
# ============================================
# 旧的兼容性表模型(保留以便向后兼容)
# ============================================
class LegacyUserStatistics(Base):
__tablename__ = "user_statistics"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
mode = Column(String(10), nullable=False) # osu, taiko, fruits, mania
# 基本统计
count_100 = Column(Integer, default=0)
count_300 = Column(Integer, default=0)
count_50 = Column(Integer, default=0)
count_miss = Column(Integer, default=0)
# 等级信息
level_current = Column(Integer, default=1)
level_progress = Column(Integer, default=0)
# 排名信息
global_rank = Column(Integer)
global_rank_exp = Column(Integer)
country_rank = Column(Integer)
# PP 和分数
pp = Column(Float, default=0.0)
pp_exp = Column(Float, default=0.0)
ranked_score = Column(Integer, default=0)
hit_accuracy = Column(Float, default=0.0)
total_score = Column(Integer, default=0)
total_hits = Column(Integer, default=0)
maximum_combo = Column(Integer, default=0)
# 游戏统计
play_count = Column(Integer, default=0)
play_time = Column(Integer, default=0)
replays_watched_by_others = Column(Integer, default=0)
is_ranked = Column(Boolean, default=False)
# 成绩等级计数
grade_ss = Column(Integer, default=0)
grade_ssh = Column(Integer, default=0)
grade_s = Column(Integer, default=0)
grade_sh = Column(Integer, default=0)
grade_a = Column(Integer, default=0)
class OAuthToken(Base):
__tablename__ = "oauth_tokens"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
access_token = Column(String(255), nullable=False, index=True)
refresh_token = Column(String(255), nullable=False, index=True)
expires_at = Column(DateTime, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
previous_usernames = Column(JSON, default=list)
replays_watched_counts = Column(JSON, default=list)
# 关联关系
statistics = relationship("UserStatistics", back_populates="user", cascade="all, delete-orphan")
achievements = relationship("UserAchievement", back_populates="user", cascade="all, delete-orphan")
team_membership = relationship("TeamMember", back_populates="user", cascade="all, delete-orphan")
daily_challenge_stats = relationship("DailyChallengeStats", back_populates="user", uselist=False, cascade="all, delete-orphan")
rank_history = relationship("RankHistory", back_populates="user", cascade="all, delete-orphan")
class UserStatistics(Base):
__tablename__ = "user_statistics"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
mode = Column(String(10), nullable=False) # osu, taiko, fruits, mania
# 基本统计
count_100 = Column(Integer, default=0)
count_300 = Column(Integer, default=0)
count_50 = Column(Integer, default=0)
count_miss = Column(Integer, default=0)
# 等级信息
level_current = Column(Integer, default=1)
level_progress = Column(Integer, default=0)
# 排名信息
global_rank = Column(Integer)
global_rank_exp = Column(Integer)
country_rank = Column(Integer)
# PP 和分数
pp = Column(Float, default=0.0)
pp_exp = Column(Float, default=0.0)
ranked_score = Column(Integer, default=0)
hit_accuracy = Column(Float, default=0.0)
total_score = Column(Integer, default=0)
total_hits = Column(Integer, default=0)
maximum_combo = Column(Integer, default=0)
# 游戏统计
play_count = Column(Integer, default=0)
play_time = Column(Integer, default=0) # 秒
replays_watched_by_others = Column(Integer, default=0)
is_ranked = Column(Boolean, default=False)
# 成绩等级计数
grade_ss = Column(Integer, default=0)
grade_ssh = Column(Integer, default=0)
grade_s = Column(Integer, default=0)
grade_sh = Column(Integer, default=0)
grade_a = Column(Integer, default=0)
# 最高排名记录
rank_highest = Column(Integer)
rank_highest_updated_at = Column(DateTime)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关联关系
user = relationship("User", back_populates="statistics")
class UserAchievement(Base):
__tablename__ = "user_achievements"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
achievement_id = Column(Integer, nullable=False)
achieved_at = Column(DateTime, default=datetime.utcnow)
user = relationship("User", back_populates="achievements")
class Team(Base):
__tablename__ = "teams"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), nullable=False)
short_name = Column(String(10), nullable=False)
flag_url = Column(String(500))
created_at = Column(DateTime, default=datetime.utcnow)
members = relationship("TeamMember", back_populates="team", cascade="all, delete-orphan")
class TeamMember(Base):
__tablename__ = "team_members"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
team_id = Column(Integer, ForeignKey("teams.id"), nullable=False)
joined_at = Column(DateTime, default=datetime.utcnow)
user = relationship("User", back_populates="team_membership")
team = relationship("Team", back_populates="members")
class DailyChallengeStats(Base):
__tablename__ = "daily_challenge_stats"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, unique=True)
daily_streak_best = Column(Integer, default=0)
daily_streak_current = Column(Integer, default=0)
last_update = Column(DateTime)
last_weekly_streak = Column(DateTime)
playcount = Column(Integer, default=0)
top_10p_placements = Column(Integer, default=0)
top_50p_placements = Column(Integer, default=0)
weekly_streak_best = Column(Integer, default=0)
weekly_streak_current = Column(Integer, default=0)
user = relationship("User", back_populates="daily_challenge_stats")
class RankHistory(Base):
__tablename__ = "rank_history"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
mode = Column(String(10), nullable=False)
rank_data = Column(JSON, nullable=False) # Array of ranks
date_recorded = Column(DateTime, default=datetime.utcnow)
user = relationship("User", back_populates="rank_history")
class OAuthToken(Base):
__tablename__ = "oauth_tokens"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
access_token = Column(String(500), unique=True, nullable=False)
refresh_token = Column(String(500), unique=True, nullable=False)
token_type = Column(String(20), default="Bearer")
scope = Column(String(100), default="*")
expires_at = Column(DateTime, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
user = relationship("User")

426
app/database.py Normal file
View File

@@ -0,0 +1,426 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Float, Text, JSON, ForeignKey, Date, DECIMAL
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from datetime import datetime
from dataclasses import dataclass
Base = declarative_base()
class User(Base):
__tablename__ = "users"
# 主键
id = Column(Integer, primary_key=True, index=True)
# 基本信息(匹配 migrations 中的结构)
name = Column(String(32), unique=True, index=True, nullable=False) # 用户名
safe_name = Column(String(32), unique=True, index=True, nullable=False) # 安全用户名
email = Column(String(254), unique=True, index=True, nullable=False)
priv = Column(Integer, default=1, nullable=False) # 权限
pw_bcrypt = Column(String(60), nullable=False) # bcrypt 哈希密码
country = Column(String(2), default='CN', nullable=False) # 国家代码
# 状态和时间
silence_end = Column(Integer, default=0, nullable=False)
donor_end = Column(Integer, default=0, nullable=False)
creation_time = Column(Integer, default=0, nullable=False) # Unix 时间戳
latest_activity = Column(Integer, default=0, nullable=False) # Unix 时间戳
# 游戏相关
preferred_mode = Column(Integer, default=0, nullable=False) # 偏好游戏模式
play_style = Column(Integer, default=0, nullable=False) # 游戏风格
# 扩展信息
clan_id = Column(Integer, default=0, nullable=False)
clan_priv = Column(Integer, default=0, nullable=False)
custom_badge_name = Column(String(16))
custom_badge_icon = Column(String(64))
userpage_content = Column(String(2048))
api_key = Column(String(36), unique=True)
# 虚拟字段用于兼容性
@property
def username(self):
return self.name
@property
def country_code(self):
return self.country
@property
def join_date(self):
creation_time = getattr(self, 'creation_time', 0)
return datetime.fromtimestamp(creation_time) if creation_time > 0 else datetime.utcnow()
@property
def last_visit(self):
latest_activity = getattr(self, 'latest_activity', 0)
return datetime.fromtimestamp(latest_activity) if latest_activity > 0 else None
# 关联关系
lazer_profile = relationship("LazerUserProfile", back_populates="user", uselist=False, cascade="all, delete-orphan")
lazer_statistics = relationship("LazerUserStatistics", back_populates="user", cascade="all, delete-orphan")
lazer_achievements = relationship("LazerUserAchievement", back_populates="user", cascade="all, delete-orphan")
statistics = relationship("LegacyUserStatistics", back_populates="user", cascade="all, delete-orphan")
achievements = relationship("LazerUserAchievement", back_populates="user", cascade="all, delete-orphan")
team_membership = relationship("TeamMember", back_populates="user", cascade="all, delete-orphan")
daily_challenge_stats = relationship("DailyChallengeStats", back_populates="user", uselist=False, cascade="all, delete-orphan")
rank_history = relationship("RankHistory", back_populates="user", cascade="all, delete-orphan")
avatar = relationship("UserAvatar", back_populates="user", primaryjoin="and_(User.id==UserAvatar.user_id, UserAvatar.is_active==True)", uselist=False)
# ============================================
# Lazer API 专用表模型
# ============================================
class LazerUserProfile(Base):
__tablename__ = "lazer_user_profiles"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
# 基本状态字段
is_active = Column(Boolean, default=True)
is_bot = Column(Boolean, default=False)
is_deleted = Column(Boolean, default=False)
is_online = Column(Boolean, default=True)
is_supporter = Column(Boolean, default=False)
is_restricted = Column(Boolean, default=False)
session_verified = Column(Boolean, default=False)
has_supported = Column(Boolean, default=False)
pm_friends_only = Column(Boolean, default=False)
# 基本资料字段
default_group = Column(String(50), default='default')
last_visit = Column(DateTime)
join_date = Column(DateTime)
profile_colour = Column(String(7))
profile_hue = Column(Integer)
# 社交媒体和个人资料字段
avatar_url = Column(String(500))
cover_url = Column(String(500))
discord = Column(String(100))
twitter = Column(String(100))
website = Column(String(500))
title = Column(String(100))
title_url = Column(String(500))
interests = Column(Text)
location = Column(String(100))
occupation = None # 职业字段,默认为 None
# 游戏相关字段
playmode = Column(String(10), default='osu')
support_level = Column(Integer, default=0)
max_blocks = Column(Integer, default=100)
max_friends = Column(Integer, default=500)
post_count = Column(Integer, default=0)
# 页面内容
page_html = Column(Text)
page_raw = Column(Text)
#created_at = Column(DateTime, default=datetime.utcnow)
#updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关联关系
user = relationship("User", back_populates="lazer_profile")
class LazerUserCountry(Base):
__tablename__ = "lazer_user_countries"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
code = Column(String(2), nullable=False)
name = Column(String(100), nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class LazerUserKudosu(Base):
__tablename__ = "lazer_user_kudosu"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
available = Column(Integer, default=0)
total = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class LazerUserCounts(Base):
__tablename__ = "lazer_user_counts"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
# 统计计数字段
beatmap_playcounts_count = Column(Integer, default=0)
comments_count = Column(Integer, default=0)
favourite_beatmapset_count = Column(Integer, default=0)
follower_count = Column(Integer, default=0)
graveyard_beatmapset_count = Column(Integer, default=0)
guest_beatmapset_count = Column(Integer, default=0)
loved_beatmapset_count = Column(Integer, default=0)
mapping_follower_count = Column(Integer, default=0)
nominated_beatmapset_count = Column(Integer, default=0)
pending_beatmapset_count = Column(Integer, default=0)
ranked_beatmapset_count = Column(Integer, default=0)
ranked_and_approved_beatmapset_count = Column(Integer, default=0)
unranked_beatmapset_count = Column(Integer, default=0)
scores_best_count = Column(Integer, default=0)
scores_first_count = Column(Integer, default=0)
scores_pinned_count = Column(Integer, default=0)
scores_recent_count = Column(Integer, default=0)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
class LazerUserStatistics(Base):
__tablename__ = "lazer_user_statistics"
user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
mode = Column(String(10), nullable=False, default='osu', primary_key=True)
# 基本命中统计
count_100 = Column(Integer, default=0)
count_300 = Column(Integer, default=0)
count_50 = Column(Integer, default=0)
count_miss = Column(Integer, default=0)
# 等级信息
level_current = Column(Integer, default=1)
level_progress = Column(Integer, default=0)
# 排名信息
global_rank = Column(Integer)
global_rank_exp = Column(Integer)
country_rank = Column(Integer)
# PP 和分数
pp = Column(DECIMAL(10, 2), default=0.00)
pp_exp = Column(DECIMAL(10, 2), default=0.00)
ranked_score = Column(Integer, default=0)
hit_accuracy = Column(DECIMAL(5, 2), default=0.00)
total_score = Column(Integer, default=0)
total_hits = Column(Integer, default=0)
maximum_combo = Column(Integer, default=0)
# 游戏统计
play_count = Column(Integer, default=0)
play_time = Column(Integer, default=0) # 秒
replays_watched_by_others = Column(Integer, default=0)
is_ranked = Column(Boolean, default=False)
# 成绩等级计数
grade_ss = Column(Integer, default=0)
grade_ssh = Column(Integer, default=0)
grade_s = Column(Integer, default=0)
grade_sh = Column(Integer, default=0)
grade_a = Column(Integer, default=0)
# 最高排名记录
rank_highest = Column(Integer)
rank_highest_updated_at = Column(DateTime)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关联关系
user = relationship("User", back_populates="lazer_statistics")
class LazerUserAchievement(Base):
__tablename__ = "lazer_user_achievements"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
achievement_id = Column(Integer, nullable=False)
achieved_at = Column(DateTime, default=datetime.utcnow)
#created_at = Column(DateTime, default=datetime.utcnow)
#updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
user = relationship("User", back_populates="lazer_achievements")
# ============================================
# 旧的兼容性表模型(保留以便向后兼容)
# ============================================
class LegacyUserStatistics(Base):
__tablename__ = "user_statistics"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
mode = Column(String(10), nullable=False) # osu, taiko, fruits, mania
# 基本统计
count_100 = Column(Integer, default=0)
count_300 = Column(Integer, default=0)
count_50 = Column(Integer, default=0)
count_miss = Column(Integer, default=0)
# 等级信息
level_current = Column(Integer, default=1)
level_progress = Column(Integer, default=0)
# 排名信息
global_rank = Column(Integer)
global_rank_exp = Column(Integer)
country_rank = Column(Integer)
# PP 和分数
pp = Column(Float, default=0.0)
pp_exp = Column(Float, default=0.0)
ranked_score = Column(Integer, default=0)
hit_accuracy = Column(Float, default=0.0)
total_score = Column(Integer, default=0)
total_hits = Column(Integer, default=0)
maximum_combo = Column(Integer, default=0)
# 游戏统计
play_count = Column(Integer, default=0)
play_time = Column(Integer, default=0)
replays_watched_by_others = Column(Integer, default=0)
is_ranked = Column(Boolean, default=False)
# 成绩等级计数
grade_ss = Column(Integer, default=0)
grade_ssh = Column(Integer, default=0)
grade_s = Column(Integer, default=0)
grade_sh = Column(Integer, default=0)
grade_a = Column(Integer, default=0)
# 最高排名记录
rank_highest = Column(Integer)
rank_highest_updated_at = Column(DateTime)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
# 关联关系
user = relationship("User", back_populates="statistics")
class LegacyOAuthToken(Base):
__tablename__ = "legacy_oauth_tokens"
id = Column(Integer, primary_key=True, autoincrement=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
access_token = Column(String(255), nullable=False, index=True)
refresh_token = Column(String(255), nullable=False, index=True)
expires_at = Column(DateTime, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
previous_usernames = Column(JSON, default=list)
replays_watched_counts = Column(JSON, default=list)
# 用户关系
user = relationship("User")
# class UserAchievement(Base):
# __tablename__ = "lazer_user_achievements"
# id = Column(Integer, primary_key=True, index=True)
# user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
# achievement_id = Column(Integer, nullable=False)
# achieved_at = Column(DateTime, default=datetime.utcnow)
# user = relationship("User", back_populates="achievements")
# 类型转换用的 UserAchievement不是 SQLAlchemy 模型)
@dataclass
class UserAchievement:
achieved_at: datetime
achievement_id: int
class Team(Base):
__tablename__ = "teams"
id = Column(Integer, primary_key=True, index=True)
name = Column(String(100), nullable=False)
short_name = Column(String(10), nullable=False)
flag_url = Column(String(500))
created_at = Column(DateTime, default=datetime.utcnow)
members = relationship("TeamMember", back_populates="team", cascade="all, delete-orphan")
class TeamMember(Base):
__tablename__ = "team_members"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
team_id = Column(Integer, ForeignKey("teams.id"), nullable=False)
joined_at = Column(DateTime, default=datetime.utcnow)
user = relationship("User", back_populates="team_membership")
team = relationship("Team", back_populates="members")
class DailyChallengeStats(Base):
__tablename__ = "daily_challenge_stats"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, unique=True)
daily_streak_best = Column(Integer, default=0)
daily_streak_current = Column(Integer, default=0)
last_update = Column(DateTime)
last_weekly_streak = Column(DateTime)
playcount = Column(Integer, default=0)
top_10p_placements = Column(Integer, default=0)
top_50p_placements = Column(Integer, default=0)
weekly_streak_best = Column(Integer, default=0)
weekly_streak_current = Column(Integer, default=0)
user = relationship("User", back_populates="daily_challenge_stats")
class RankHistory(Base):
__tablename__ = "rank_history"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
mode = Column(String(10), nullable=False)
rank_data = Column(JSON, nullable=False) # Array of ranks
date_recorded = Column(DateTime, default=datetime.utcnow)
user = relationship("User", back_populates="rank_history")
class OAuthToken(Base):
__tablename__ = "oauth_tokens"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
access_token = Column(String(500), unique=True, nullable=False)
refresh_token = Column(String(500), unique=True, nullable=False)
token_type = Column(String(20), default="Bearer")
scope = Column(String(100), default="*")
expires_at = Column(DateTime, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow)
user = relationship("User")
class UserAvatar(Base):
__tablename__ = "user_avatars"
id = Column(Integer, primary_key=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
filename = Column(String(255), nullable=False)
original_filename = Column(String(255), nullable=False)
file_size = Column(Integer, nullable=False)
mime_type = Column(String(100), nullable=False)
is_active = Column(Boolean, default=True)
created_at = Column(Integer, default=lambda: int(datetime.now().timestamp()))
updated_at = Column(Integer, default=lambda: int(datetime.now().timestamp()))
r2_original_url = Column(String(500))
r2_game_url = Column(String(500))
user = relationship("User", back_populates="avatar")

29
app/dependencies.py Normal file
View File

@@ -0,0 +1,29 @@
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
try:
import redis
except ImportError:
redis = None
from app.config import settings
# 数据库引擎
engine = create_engine(settings.DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
# Redis 连接
if redis:
redis_client = redis.from_url(settings.REDIS_URL, decode_responses=True)
else:
redis_client = None
# 数据库依赖
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# Redis 依赖
def get_redis():
return redis_client

226
app/models.py Normal file
View File

@@ -0,0 +1,226 @@
from pydantic import BaseModel, Field
from typing import Optional, List
from datetime import datetime
from enum import Enum
class GameMode(str, Enum):
OSU = "osu"
TAIKO = "taiko"
FRUITS = "fruits"
MANIA = "mania"
class PlayStyle(str, Enum):
MOUSE = "mouse"
KEYBOARD = "keyboard"
TABLET = "tablet"
TOUCH = "touch"
class Country(BaseModel):
code: str
name: str
class Cover(BaseModel):
custom_url: Optional[str] = None
url: str
id: Optional[int] = None
class Level(BaseModel):
current: int
progress: int
class GradeCounts(BaseModel):
ss: int = 0
ssh: int = 0
s: int = 0
sh: int = 0
a: int = 0
class Statistics(BaseModel):
count_100: int = 0
count_300: int = 0
count_50: int = 0
count_miss: int = 0
level: Level
global_rank: Optional[int] = None
global_rank_exp: Optional[int] = None
pp: float = 0.0
pp_exp: float = 0.0
ranked_score: int = 0
hit_accuracy: float = 0.0
play_count: int = 0
play_time: int = 0
total_score: int = 0
total_hits: int = 0
maximum_combo: int = 0
replays_watched_by_others: int = 0
is_ranked: bool = False
grade_counts: GradeCounts
country_rank: Optional[int] = None
rank: Optional[dict] = None
class Kudosu(BaseModel):
available: int = 0
total: int = 0
class MonthlyPlaycount(BaseModel):
start_date: str
count: int
class UserAchievement(BaseModel):
achieved_at: datetime
achievement_id: int
class RankHighest(BaseModel):
rank: int
updated_at: datetime
class RankHistory(BaseModel):
mode: str
data: List[int]
class DailyChallengeStats(BaseModel):
daily_streak_best: int = 0
daily_streak_current: int = 0
last_update: Optional[datetime] = None
last_weekly_streak: Optional[datetime] = None
playcount: int = 0
top_10p_placements: int = 0
top_50p_placements: int = 0
user_id: int
weekly_streak_best: int = 0
weekly_streak_current: int = 0
class Team(BaseModel):
flag_url: str
id: int
name: str
short_name: str
class Page(BaseModel):
html: str = ""
raw: str = ""
class User(BaseModel):
# 基本信息
id: int
username: str
avatar_url: str
country_code: str
default_group: str = "default"
is_active: bool = True
is_bot: bool = False
is_deleted: bool = False
is_online: bool = True
is_supporter: bool = False
is_restricted: bool = False
last_visit: Optional[datetime] = None
pm_friends_only: bool = False
profile_colour: Optional[str] = None
# 个人资料
cover_url: Optional[str] = None
discord: Optional[str] = None
has_supported: bool = False
interests: Optional[str] = None
join_date: datetime
location: Optional[str] = None
max_blocks: int = 100
max_friends: int = 500
occupation: Optional[str] = None
playmode: GameMode = GameMode.OSU
playstyle: List[PlayStyle] = []
post_count: int = 0
profile_hue: Optional[int] = None
profile_order: List[str] = ["me", "recent_activity", "top_ranks", "medals", "historical", "beatmaps", "kudosu"]
title: Optional[str] = None
title_url: Optional[str] = None
twitter: Optional[str] = None
website: Optional[str] = None
session_verified: bool = False
support_level: int = 0
# 关联对象
country: Country
cover: Cover
kudosu: Kudosu
statistics: Statistics
statistics_rulesets: dict[str, Statistics]
# 计数信息
beatmap_playcounts_count: int = 0
comments_count: int = 0
favourite_beatmapset_count: int = 0
follower_count: int = 0
graveyard_beatmapset_count: int = 0
guest_beatmapset_count: int = 0
loved_beatmapset_count: int = 0
mapping_follower_count: int = 0
nominated_beatmapset_count: int = 0
pending_beatmapset_count: int = 0
ranked_beatmapset_count: int = 0
ranked_and_approved_beatmapset_count: int = 0
unranked_beatmapset_count: int = 0
scores_best_count: int = 0
scores_first_count: int = 0
scores_pinned_count: int = 0
scores_recent_count: int = 0
# 历史数据
account_history: List[dict] = []
active_tournament_banner: Optional[dict] = None
active_tournament_banners: List[dict] = []
badges: List[dict] = []
current_season_stats: Optional[dict] = None
daily_challenge_user_stats: Optional[DailyChallengeStats] = None
groups: List[dict] = []
monthly_playcounts: List[MonthlyPlaycount] = []
page: Page = Page()
previous_usernames: List[str] = []
rank_highest: Optional[RankHighest] = None
rank_history: Optional[RankHistory] = None
rankHistory: Optional[RankHistory] = None # 兼容性别名
replays_watched_counts: List[dict] = []
team: Optional[Team] = None
user_achievements: List[UserAchievement] = []
# OAuth 相关模型
class TokenRequest(BaseModel):
grant_type: str
username: Optional[str] = None
password: Optional[str] = None
refresh_token: Optional[str] = None
client_id: str
client_secret: str
scope: str = "*"
class TokenResponse(BaseModel):
access_token: str
token_type: str = "Bearer"
expires_in: int
refresh_token: str
scope: str = "*"
class UserCreate(BaseModel):
username: str
password: str
email: str
country_code: str = "CN"

447
app/utils.py Normal file
View File

@@ -0,0 +1,447 @@
from typing import Dict, List, Optional
from datetime import datetime
from app.models import *
from app.database import User as DBUser, LazerUserStatistics, LazerUserProfile, LazerUserCountry, LazerUserKudosu, LazerUserCounts, LazerUserAchievement
from sqlalchemy.orm import Session
def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_session: Session = None) -> User:
"""将数据库用户模型转换为API用户模型使用 Lazer 表)"""
# 从db_user获取基本字段值
user_id = getattr(db_user, 'id')
user_name = getattr(db_user, 'name')
user_country = getattr(db_user, 'country')
user_country_code = user_country # 在User模型中country字段就是country_code
# 获取 Lazer 用户资料
profile = db_user.lazer_profile
if not profile:
# 如果没有 lazer 资料,使用默认值
profile = create_default_profile(db_user)
# 获取指定模式的统计信息
user_stats = None
for stat in db_user.lazer_statistics:
if stat.mode == ruleset:
user_stats = stat
break
if not user_stats:
# 如果没有找到指定模式的统计,创建默认统计
user_stats = create_default_lazer_statistics(ruleset)
# 获取国家信息
country = Country(
code=db_user.country_code,
name=get_country_name(db_user.country_code)
)
# 获取 Kudosu 信息
kudosu = Kudosu(available=0, total=0)
# 获取计数信息
counts = create_default_counts()
# 转换统计信息
statistics = Statistics(
count_100=user_stats.count_100,
count_300=user_stats.count_300,
count_50=user_stats.count_50,
count_miss=user_stats.count_miss,
level=Level(
current=user_stats.level_current,
progress=user_stats.level_progress
),
global_rank=user_stats.global_rank,
global_rank_exp=user_stats.global_rank_exp,
pp=float(user_stats.pp) if user_stats.pp else 0.0,
pp_exp=float(user_stats.pp_exp) if user_stats.pp_exp else 0.0,
ranked_score=user_stats.ranked_score,
hit_accuracy=float(user_stats.hit_accuracy) if user_stats.hit_accuracy else 0.0,
play_count=user_stats.play_count,
play_time=user_stats.play_time,
total_score=user_stats.total_score,
total_hits=user_stats.total_hits,
maximum_combo=user_stats.maximum_combo,
replays_watched_by_others=user_stats.replays_watched_by_others,
is_ranked=user_stats.is_ranked,
grade_counts=GradeCounts(
ss=user_stats.grade_ss,
ssh=user_stats.grade_ssh,
s=user_stats.grade_s,
sh=user_stats.grade_sh,
a=user_stats.grade_a
),
country_rank=user_stats.country_rank,
rank={"country": user_stats.country_rank} if user_stats.country_rank else None
)
# 转换所有模式的统计信息
statistics_rulesets = {}
for stat in db_user.statistics:
statistics_rulesets[stat.mode] = Statistics(
count_100=stat.count_100,
count_300=stat.count_300,
count_50=stat.count_50,
count_miss=stat.count_miss,
level=Level(current=stat.level_current, progress=stat.level_progress),
global_rank=stat.global_rank,
global_rank_exp=stat.global_rank_exp,
pp=stat.pp,
pp_exp=stat.pp_exp,
ranked_score=stat.ranked_score,
hit_accuracy=stat.hit_accuracy,
play_count=stat.play_count,
play_time=stat.play_time,
total_score=stat.total_score,
total_hits=stat.total_hits,
maximum_combo=stat.maximum_combo,
replays_watched_by_others=stat.replays_watched_by_others,
is_ranked=stat.is_ranked,
grade_counts=GradeCounts(
ss=stat.grade_ss,
ssh=stat.grade_ssh,
s=stat.grade_s,
sh=stat.grade_sh,
a=stat.grade_a
)
)
# 转换国家信息
country = Country(
code=user_country_code,
name=get_country_name(user_country_code)
)
# 转换封面信息
cover_url = profile.cover_url if profile and profile.cover_url else "https://assets.ppy.sh/user-profile-covers/default.jpeg"
cover = Cover(
custom_url=profile.cover_url if profile else None,
url=str(cover_url),
id=None
)
# 转换 Kudosu 信息
kudosu = Kudosu(available=0, total=0)
# 转换成就信息
user_achievements = []
if db_user.lazer_achievements:
for achievement in db_user.lazer_achievements:
user_achievements.append(UserAchievement(
achieved_at=achievement.achieved_at,
achievement_id=achievement.achievement_id
))
# 转换排名历史
rank_history = None
rank_history_data = None
for rh in db_user.rank_history:
if rh.mode == ruleset:
rank_history_data = rh.rank_data
break
if rank_history_data:
rank_history = RankHistory(mode=ruleset, data=rank_history_data)
# 转换每日挑战统计
daily_challenge_stats = None
if db_user.daily_challenge_stats:
dcs = db_user.daily_challenge_stats
daily_challenge_stats = DailyChallengeStats(
daily_streak_best=dcs.daily_streak_best,
daily_streak_current=dcs.daily_streak_current,
last_update=dcs.last_update,
last_weekly_streak=dcs.last_weekly_streak,
playcount=dcs.playcount,
top_10p_placements=dcs.top_10p_placements,
top_50p_placements=dcs.top_50p_placements,
user_id=dcs.user_id,
weekly_streak_best=dcs.weekly_streak_best,
weekly_streak_current=dcs.weekly_streak_current
)
# 转换最高排名
rank_highest = None
if user_stats.rank_highest:
rank_highest = RankHighest(
rank=user_stats.rank_highest,
updated_at=user_stats.rank_highest_updated_at or datetime.utcnow()
)
# 转换团队信息
team = None
if db_user.team_membership:
team_member = db_user.team_membership[0] # 假设用户只属于一个团队
team = Team(
flag_url=team_member.team.flag_url or "",
id=team_member.team.id,
name=team_member.team.name,
short_name=team_member.team.short_name
)
# 创建用户对象
# 从db_user获取基本字段值
user_id = getattr(db_user, 'id')
user_name = getattr(db_user, 'name')
user_country = getattr(db_user, 'country')
# 获取用户头像URL
avatar_url = None
# 首先检查 profile 中的 avatar_url
if profile and hasattr(profile, 'avatar_url') and profile.avatar_url:
avatar_url = str(profile.avatar_url)
# 然后检查是否有关联的头像记录
if avatar_url is None and hasattr(db_user, 'avatar') and db_user.avatar is not None:
if db_user.avatar.r2_game_url:
# 优先使用游戏用的头像URL
avatar_url = str(db_user.avatar.r2_game_url)
elif db_user.avatar.r2_original_url:
# 其次使用原始头像URL
avatar_url = str(db_user.avatar.r2_original_url)
# 如果还是没有找到,通过查询获取
if db_session and avatar_url is None:
try:
# 导入UserAvatar模型
from app.database import UserAvatar
# 尝试查找用户的头像记录
avatar_record = db_session.query(UserAvatar).filter_by(user_id=user_id, is_active=True).first()
if avatar_record is not None:
if avatar_record.r2_game_url is not None:
# 优先使用游戏用的头像URL
avatar_url = str(avatar_record.r2_game_url)
elif avatar_record.r2_original_url is not None:
# 其次使用原始头像URL
avatar_url = str(avatar_record.r2_original_url)
except Exception as e:
print(f"获取用户头像时出错: {e}")
print(f"最终头像URL: {avatar_url}")
# 如果仍然没有找到头像URL则使用默认URL
if avatar_url is None:
avatar_url = f"https://a.gu-osu.gmoe.cc/api/users/avatar/1"
user = User(
id=user_id,
username=user_name,
avatar_url=avatar_url, # 使用我们上面获取的头像URL
country_code=user_country,
default_group=profile.default_group if profile else "default",
is_active=profile.is_active if profile else True,
is_bot=profile.is_bot if profile else False,
is_deleted=profile.is_deleted if profile else False,
is_online=profile.is_online if profile else True,
is_supporter=profile.is_supporter if profile else False,
is_restricted=profile.is_restricted if profile else False,
last_visit=db_user.last_visit,
pm_friends_only=profile.pm_friends_only if profile else False,
profile_colour=profile.profile_colour if profile else None,
cover_url=cover_url,
discord=profile.discord if profile else None,
has_supported=profile.has_supported if profile else False,
interests=profile.interests if profile else None,
join_date=db_user.join_date,
location=profile.location if profile else None,
max_blocks=profile.max_blocks if profile else 100,
max_friends=profile.max_friends if profile else 500,
occupation=None, # 职业字段,默认为 None #待修改
#playmode=GameMode(db_user.playmode),
playmode=GameMode("osu"), #待修改
playstyle=[PlayStyle.MOUSE, PlayStyle.KEYBOARD, PlayStyle.TABLET], #待修改
post_count=0,
profile_hue=None,
profile_order= ['me', 'recent_activity', 'top_ranks', 'medals', 'historical', 'beatmaps', 'kudosu'],
title=None,
title_url=None,
twitter=None,
website='https://gmoe.cc',
session_verified=True,
support_level=0,
country=country,
cover=cover,
kudosu=kudosu,
statistics=statistics,
statistics_rulesets=statistics_rulesets,
beatmap_playcounts_count=3306,
comments_count=0,
favourite_beatmapset_count=0,
follower_count=0,
graveyard_beatmapset_count=0,
guest_beatmapset_count=0,
loved_beatmapset_count=0,
mapping_follower_count=0,
nominated_beatmapset_count=0,
pending_beatmapset_count=0,
ranked_beatmapset_count=0,
ranked_and_approved_beatmapset_count=0,
unranked_beatmapset_count=0,
scores_best_count=0,
scores_first_count=0,
scores_pinned_count=0,
scores_recent_count=0,
account_history=[],
active_tournament_banner=None,
active_tournament_banners=[],
badges=[],
current_season_stats=None,
daily_challenge_user_stats=None,
groups=[],
monthly_playcounts=[],
#page=Page(html=db_user.page_html, raw=db_user.page_raw),
page=Page(), # Provide a default Page object
previous_usernames=[],
rank_highest=rank_highest,
rank_history=rank_history,
rankHistory=rank_history, # 兼容性别名
replays_watched_counts=[],
team=team,
user_achievements=user_achievements
)
return user
def get_country_name(country_code: str) -> str:
"""根据国家代码获取国家名称"""
country_names = {
"CN": "China",
"JP": "Japan",
"US": "United States",
"GB": "United Kingdom",
"DE": "Germany",
"FR": "France",
"KR": "South Korea",
"CA": "Canada",
"AU": "Australia",
"BR": "Brazil",
# 可以添加更多国家
}
return country_names.get(country_code, "Unknown")
def create_default_profile(db_user: DBUser):
"""创建默认的用户资料"""
class MockProfile:
def __init__(self):
self.is_active = True
self.is_bot = False
self.is_deleted = False
self.is_online = True
self.is_supporter = False
self.is_restricted = False
self.session_verified = False
self.has_supported = False
self.pm_friends_only = False
self.default_group = 'default'
self.last_visit = None
self.join_date = db_user.join_date
self.profile_colour = None
self.profile_hue = None
self.avatar_url = None
self.cover_url = None
self.discord = None
self.twitter = None
self.website = None
self.title = None
self.title_url = None
self.interests = None
self.location = None
self.occupation = None
self.playmode = 'osu'
self.support_level = 0
self.max_blocks = 100
self.max_friends = 500
self.post_count = 0
self.page_html = None
self.page_raw = None
return MockProfile()
def create_default_lazer_statistics(mode: str):
"""创建默认的 Lazer 统计信息"""
class MockLazerStatistics:
def __init__(self, mode: str):
self.mode = mode
self.count_100 = 0
self.count_300 = 0
self.count_50 = 0
self.count_miss = 0
self.level_current = 1
self.level_progress = 0
self.global_rank = None
self.global_rank_exp = None
self.pp = 0.0
self.pp_exp = 0.0
self.ranked_score = 0
self.hit_accuracy = 0.0
self.play_count = 0
self.play_time = 0
self.total_score = 0
self.total_hits = 0
self.maximum_combo = 0
self.replays_watched_by_others = 0
self.is_ranked = False
self.grade_ss = 0
self.grade_ssh = 0
self.grade_s = 0
self.grade_sh = 0
self.grade_a = 0
self.country_rank = None
self.rank_highest = None
self.rank_highest_updated_at = None
return MockLazerStatistics(mode)
def create_default_country(country_code: str):
"""创建默认的国家信息"""
class MockCountry:
def __init__(self, code: str):
self.code = code
self.name = get_country_name(code)
return MockCountry(country_code)
def create_default_kudosu():
"""创建默认的 Kudosu 信息"""
class MockKudosu:
def __init__(self):
self.available = 0
self.total = 0
return MockKudosu()
def create_default_counts():
"""创建默认的计数信息"""
class MockCounts:
def __init__(self):
self.beatmap_playcounts_count = 0
self.comments_count = 0
self.favourite_beatmapset_count = 0
self.follower_count = 0
self.graveyard_beatmapset_count = 0
self.guest_beatmapset_count = 0
self.loved_beatmapset_count = 0
self.mapping_follower_count = 0
self.nominated_beatmapset_count = 0
self.pending_beatmapset_count = 0
self.ranked_beatmapset_count = 0
self.ranked_and_approved_beatmapset_count = 0
self.unranked_beatmapset_count = 0
self.scores_best_count = 0
self.scores_first_count = 0
self.scores_pinned_count = 0
self.scores_recent_count = 0
return MockCounts()