artemis/titles/wacca/schema/profile.py

532 lines
18 KiB
Python
Raw Normal View History

2024-03-22 16:22:07 +00:00
from typing import Dict, List, Optional
from core.data.schema import BaseData, metadata
from sqlalchemy import Column, PrimaryKeyConstraint, Table, UniqueConstraint, and_
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.engine import Row
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
2024-03-22 16:22:07 +00:00
from sqlalchemy.types import JSON, TIMESTAMP, Boolean, Integer, String
from ..handlers.helpers import PlayType
profile = Table(
"wacca_profile",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
2023-03-09 16:38:58 +00:00
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("version", Integer),
Column("username", String(8), nullable=False),
Column("xp", Integer, server_default="0"),
Column("wp", Integer, server_default="0"),
Column("wp_total", Integer, server_default="0"),
Column("wp_spent", Integer, server_default="0"),
Column("dan_type", Integer, server_default="0"),
2023-03-09 16:38:58 +00:00
Column("dan_level", Integer, server_default="0"),
Column("title_0", Integer, server_default="0"),
Column("title_1", Integer, server_default="0"),
Column("title_2", Integer, server_default="0"),
Column("rating", Integer, server_default="0"),
Column("vip_expire_time", TIMESTAMP),
Column("always_vip", Boolean, server_default="0"),
Column("login_count", Integer, server_default="0"),
Column("login_count_consec", Integer, server_default="0"),
Column("login_count_days", Integer, server_default="0"),
Column("login_count_days_consec", Integer, server_default="0"),
Column("login_count_today", Integer, server_default="0"),
Column("playcount_single", Integer, server_default="0"),
Column("playcount_multi_vs", Integer, server_default="0"),
Column("playcount_multi_coop", Integer, server_default="0"),
Column("playcount_stageup", Integer, server_default="0"),
Column("playcount_time_free", Integer, server_default="0"),
Column("friend_view_1", Integer),
Column("friend_view_2", Integer),
Column("friend_view_3", Integer),
Column("last_game_ver", String(50)),
Column("last_song_id", Integer, server_default="0"),
Column("last_song_difficulty", Integer, server_default="0"),
Column("last_folder_order", Integer, server_default="0"),
Column("last_folder_id", Integer, server_default="0"),
Column("last_song_order", Integer, server_default="0"),
Column("last_login_date", TIMESTAMP, server_default=func.now()),
Column("gate_tutorial_flags", JSON),
UniqueConstraint("user", "version", name="wacca_profile_uk"),
2023-03-09 16:38:58 +00:00
mysql_charset="utf8mb4",
)
option = Table(
"wacca_option",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
2023-03-09 16:38:58 +00:00
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("opt_id", Integer, nullable=False),
Column("value", Integer, nullable=False),
UniqueConstraint("user", "opt_id", name="wacca_option_uk"),
)
bingo = Table(
"wacca_bingo",
metadata,
2023-03-09 16:38:58 +00:00
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
primary_key=True,
nullable=False,
),
Column("page_number", Integer, nullable=False),
Column("page_progress", JSON, nullable=False),
UniqueConstraint("user", "page_number", name="wacca_bingo_uk"),
2023-03-09 16:38:58 +00:00
mysql_charset="utf8mb4",
)
friend = Table(
"wacca_friend",
metadata,
2023-03-09 16:38:58 +00:00
Column(
"profile_sender",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column(
"profile_reciever",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("is_accepted", Boolean, server_default="0"),
2023-03-09 16:38:58 +00:00
PrimaryKeyConstraint("profile_sender", "profile_reciever", name="arcade_owner_pk"),
mysql_charset="utf8mb4",
)
favorite = Table(
"wacca_favorite_song",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
2023-03-09 16:38:58 +00:00
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("song_id", Integer, nullable=False),
UniqueConstraint("user", "song_id", name="wacca_favorite_song_uk"),
2023-03-09 16:38:58 +00:00
mysql_charset="utf8mb4",
)
gate = Table(
"wacca_gate",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
2023-03-09 16:38:58 +00:00
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("gate_id", Integer, nullable=False),
Column("page", Integer, nullable=False, server_default="0"),
Column("progress", Integer, nullable=False, server_default="0"),
Column("loops", Integer, nullable=False, server_default="0"),
Column("last_used", TIMESTAMP, nullable=False, server_default=func.now()),
Column("mission_flag", Integer, nullable=False, server_default="0"),
Column("total_points", Integer, nullable=False, server_default="0"),
UniqueConstraint("user", "gate_id", name="wacca_gate_uk"),
)
2023-03-09 16:38:58 +00:00
class WaccaProfileData(BaseData):
2024-03-22 16:22:07 +00:00
async def create_profile(
2023-03-09 16:38:58 +00:00
self, aime_id: int, username: str, version: int
) -> Optional[int]:
"""
Given a game version, aime id, and username, create a profile and return it's ID
"""
2023-03-09 16:38:58 +00:00
sql = insert(profile).values(user=aime_id, username=username, version=version)
2023-03-09 16:38:58 +00:00
conflict = sql.on_duplicate_key_update(username=sql.inserted.username)
2024-03-22 16:22:07 +00:00
result = await self.execute(conflict)
if result is None:
2023-03-09 16:38:58 +00:00
self.logger.error(
f"{__name__} Failed to insert wacca profile! aime id: {aime_id} username: {username}"
)
return None
return result.lastrowid
2024-03-22 16:22:07 +00:00
async def update_profile_playtype(
2023-03-09 16:38:58 +00:00
self, profile_id: int, play_type: int, game_version: str
) -> None:
sql = profile.update(profile.c.id == profile_id).values(
playcount_single=profile.c.playcount_single + 1
if play_type == PlayType.PlayTypeSingle.value
2023-03-09 16:38:58 +00:00
else profile.c.playcount_single,
playcount_multi_vs=profile.c.playcount_multi_vs + 1
if play_type == PlayType.PlayTypeVs.value
2023-03-09 16:38:58 +00:00
else profile.c.playcount_multi_vs,
playcount_multi_coop=profile.c.playcount_multi_coop + 1
if play_type == PlayType.PlayTypeCoop.value
2023-03-09 16:38:58 +00:00
else profile.c.playcount_multi_coop,
playcount_stageup=profile.c.playcount_stageup + 1
if play_type == PlayType.PlayTypeStageup.value
2023-03-09 16:38:58 +00:00
else profile.c.playcount_stageup,
playcount_time_free=profile.c.playcount_time_free + 1
if play_type == PlayType.PlayTypeTimeFree.value
else profile.c.playcount_time_free,
2023-03-09 16:38:58 +00:00
last_game_ver=game_version,
)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
self.logger.error(
f"update_profile: failed to update profile! profile: {profile_id}"
)
return None
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def update_profile_lastplayed(
2023-03-09 16:38:58 +00:00
self,
profile_id: int,
last_song_id: int,
last_song_difficulty: int,
last_folder_order: int,
last_folder_id: int,
last_song_order: int,
) -> None:
sql = profile.update(profile.c.id == profile_id).values(
last_song_id=last_song_id,
last_song_difficulty=last_song_difficulty,
last_folder_order=last_folder_order,
last_folder_id=last_folder_id,
last_song_order=last_song_order,
)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
self.logger.error(
f"update_profile_lastplayed: failed to update profile! profile: {profile_id}"
)
return None
2024-03-22 16:22:07 +00:00
async def update_profile_dan(
2023-03-09 16:38:58 +00:00
self, profile_id: int, dan_level: int, dan_type: int
) -> Optional[int]:
sql = profile.update(profile.c.id == profile_id).values(
2023-03-09 16:38:58 +00:00
dan_level=dan_level, dan_type=dan_type
)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
if result is None:
2023-08-08 14:17:56 +00:00
self.logger.warning(
2023-03-09 16:38:58 +00:00
f"update_profile_dan: Failed to update! profile {profile_id}"
)
return None
return result.lastrowid
2024-03-22 16:22:07 +00:00
async def get_profile(
self, profile_id: int = 0, aime_id: int = None
) -> Optional[Row]:
"""
Given a game version and either a profile or aime id, return the profile
"""
if aime_id is not None:
sql = profile.select(profile.c.user == aime_id)
elif profile_id > 0:
sql = profile.select(profile.c.id == profile_id)
else:
2023-03-09 16:38:58 +00:00
self.logger.error(
f"get_profile: Bad arguments!! profile_id {profile_id} aime_id {aime_id}"
)
return None
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
return None
return result.fetchone()
2024-03-22 16:22:07 +00:00
async def get_options(
self, user_id: int, option_id: int = None
) -> Optional[List[Row]]:
"""
Get a specific user option for a profile, or all of them if none specified
"""
sql = option.select(
2023-03-09 16:38:58 +00:00
and_(
option.c.user == user_id,
option.c.opt_id == option_id if option_id is not None else True,
)
)
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
return None
if option_id is not None:
return result.fetchone()
else:
return result.fetchall()
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def update_option(
self, user_id: int, option_id: int, value: int
) -> Optional[int]:
2023-03-09 16:38:58 +00:00
sql = insert(option).values(user=user_id, opt_id=option_id, value=value)
2023-03-09 16:38:58 +00:00
conflict = sql.on_duplicate_key_update(value=value)
2024-03-22 16:22:07 +00:00
result = await self.execute(conflict)
if result is None:
2023-03-09 16:38:58 +00:00
self.logger.error(
f"{__name__} failed to insert option! profile: {user_id}, option: {option_id}, value: {value}"
)
return None
2023-03-09 16:38:58 +00:00
return result.lastrowid
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def add_favorite_song(self, user_id: int, song_id: int) -> Optional[int]:
2023-03-09 16:38:58 +00:00
sql = favorite.insert().values(user=user_id, song_id=song_id)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
self.logger.error(
f"{__name__} failed to insert favorite! profile: {user_id}, song_id: {song_id}"
)
return None
return result.lastrowid
2024-03-22 16:22:07 +00:00
async def remove_favorite_song(self, user_id: int, song_id: int) -> None:
2023-03-09 16:38:58 +00:00
sql = favorite.delete(
and_(favorite.c.user == user_id, favorite.c.song_id == song_id)
)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
self.logger.error(
f"{__name__} failed to remove favorite! profile: {user_id}, song_id: {song_id}"
)
return None
2024-03-22 16:22:07 +00:00
async def get_favorite_songs(self, user_id: int) -> Optional[List[Row]]:
sql = favorite.select(favorite.c.user == user_id)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
return None
return result.fetchall()
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def get_gates(self, user_id: int) -> Optional[List[Row]]:
sql = select(gate).where(gate.c.user == user_id)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
return None
return result.fetchall()
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def update_gate(
2023-03-09 16:38:58 +00:00
self,
user_id: int,
gate_id: int,
page: int,
progress: int,
loop: int,
mission_flag: int,
total_points: int,
) -> Optional[int]:
sql = insert(gate).values(
user=user_id,
gate_id=gate_id,
page=page,
progress=progress,
loops=loop,
mission_flag=mission_flag,
2023-03-09 16:38:58 +00:00
total_points=total_points,
)
conflict = sql.on_duplicate_key_update(
page=sql.inserted.page,
progress=sql.inserted.progress,
loops=sql.inserted.loops,
mission_flag=sql.inserted.mission_flag,
total_points=sql.inserted.total_points,
)
2024-03-22 16:22:07 +00:00
result = await self.execute(conflict)
2023-03-09 16:38:58 +00:00
if result is None:
self.logger.error(
f"{__name__} failed to update gate! user: {user_id}, gate_id: {gate_id}"
)
return None
return result.lastrowid
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def get_friends(self, user_id: int) -> Optional[List[Row]]:
sql = friend.select(friend.c.profile_sender == user_id)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
return None
return result.fetchall()
2024-03-22 16:22:07 +00:00
async def profile_to_aime_user(self, profile_id: int) -> Optional[int]:
sql = select(profile.c.user).where(profile.c.id == profile_id)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
if result is None:
2023-03-09 16:38:58 +00:00
self.logger.info(
f"profile_to_aime_user: No user found for profile {profile_id}"
)
return None
this_profile = result.fetchone()
if this_profile is None:
2023-03-09 16:38:58 +00:00
self.logger.info(
f"profile_to_aime_user: No user found for profile {profile_id}"
)
return None
2023-03-09 16:38:58 +00:00
return this_profile["user"]
2024-03-22 16:22:07 +00:00
async def session_login(
2023-03-09 16:38:58 +00:00
self, profile_id: int, is_new_day: bool, is_consec_day: bool
) -> None:
# TODO: Reset consec days counter
sql = profile.update(profile.c.id == profile_id).values(
2023-03-09 16:38:58 +00:00
login_count=profile.c.login_count + 1,
login_count_consec=profile.c.login_count_consec + 1,
login_count_days=profile.c.login_count_days + 1
if is_new_day
else profile.c.login_count_days,
login_count_days_consec=profile.c.login_count_days_consec + 1
if is_new_day and is_consec_day
else profile.c.login_count_days_consec,
login_count_today=1 if is_new_day else profile.c.login_count_today + 1,
last_login_date=func.now(),
)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
self.logger.error(
f"session_login: failed to update profile! profile: {profile_id}"
)
return None
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def session_logout(self, profile_id: int) -> None:
2023-03-09 16:38:58 +00:00
sql = profile.update(profile.c.id == id).values(login_count_consec=0)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
self.logger.error(
f"{__name__} failed to update profile! profile: {profile_id}"
)
return None
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def add_xp(self, profile_id: int, xp: int) -> None:
2023-03-09 16:38:58 +00:00
sql = profile.update(profile.c.id == profile_id).values(xp=profile.c.xp + xp)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
if result is None:
2023-03-09 16:38:58 +00:00
self.logger.error(
f"add_xp: Failed to update profile! profile_id {profile_id} xp {xp}"
)
return None
2024-03-22 16:22:07 +00:00
async def add_wp(self, profile_id: int, wp: int) -> None:
sql = profile.update(profile.c.id == profile_id).values(
2023-03-09 16:38:58 +00:00
wp=profile.c.wp + wp,
wp_total=profile.c.wp_total + wp,
)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
if result is None:
2023-03-09 16:38:58 +00:00
self.logger.error(
f"add_wp: Failed to update profile! profile_id {profile_id} wp {wp}"
)
return None
2024-03-22 16:22:07 +00:00
async def spend_wp(self, profile_id: int, wp: int) -> None:
sql = profile.update(profile.c.id == profile_id).values(
2023-03-09 16:38:58 +00:00
wp=profile.c.wp - wp,
wp_spent=profile.c.wp_spent + wp,
)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
if result is None:
2023-03-09 16:38:58 +00:00
self.logger.error(
f"spend_wp: Failed to update profile! profile_id {profile_id} wp {wp}"
)
return None
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def activate_vip(self, profile_id: int, expire_time) -> None:
sql = profile.update(profile.c.id == profile_id).values(
2023-03-09 16:38:58 +00:00
vip_expire_time=expire_time
)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
if result is None:
2023-03-09 16:38:58 +00:00
self.logger.error(
f"activate_vip: Failed to update profile! profile_id {profile_id} expire_time {expire_time}"
)
return None
2024-03-22 16:22:07 +00:00
async def update_user_rating(self, profile_id: int, new_rating: int) -> None:
2023-03-09 16:38:58 +00:00
sql = profile.update(profile.c.id == profile_id).values(rating=new_rating)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
if result is None:
2023-03-09 16:38:58 +00:00
self.logger.error(
f"update_user_rating: Failed to update profile! profile_id {profile_id} new_rating {new_rating}"
)
return None
2024-03-22 16:22:07 +00:00
async def update_bingo(
self, aime_id: int, page: int, progress: int
) -> Optional[int]:
sql = insert(bingo).values(
2023-03-09 16:38:58 +00:00
user=aime_id, page_number=page, page_progress=progress
)
2023-03-09 16:38:58 +00:00
conflict = sql.on_duplicate_key_update(page_number=page, page_progress=progress)
2024-03-22 16:22:07 +00:00
result = await self.execute(conflict)
2023-03-09 16:38:58 +00:00
if result is None:
self.logger.error(f"put_bingo: failed to update! aime_id: {aime_id}")
return None
return result.lastrowid
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def get_bingo(self, aime_id: int) -> Optional[List[Row]]:
2023-03-09 16:38:58 +00:00
sql = select(bingo).where(bingo.c.user == aime_id)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
return None
return result.fetchone()
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def get_bingo_page(self, aime_id: int, page: Dict) -> Optional[List[Row]]:
2023-03-09 16:38:58 +00:00
sql = select(bingo).where(
and_(bingo.c.user == aime_id, bingo.c.page_number == page)
)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
2023-03-09 16:38:58 +00:00
if result is None:
return None
return result.fetchone()
2024-03-22 16:22:07 +00:00
async def update_vip_time(self, profile_id: int, time_left) -> None:
2023-03-09 16:38:58 +00:00
sql = profile.update(profile.c.id == profile_id).values(
vip_expire_time=time_left
)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
if result is None:
self.logger.error(f"Failed to update VIP time for profile {profile_id}")
2023-03-09 16:38:58 +00:00
2024-03-22 16:22:07 +00:00
async def update_tutorial_flags(self, profile_id: int, flags: Dict) -> None:
2023-03-09 16:38:58 +00:00
sql = profile.update(profile.c.id == profile_id).values(
gate_tutorial_flags=flags
)
2024-03-22 16:22:07 +00:00
result = await self.execute(sql)
if result is None:
2023-03-09 16:38:58 +00:00
self.logger.error(
f"Failed to update tutorial flags for profile {profile_id}"
)