mai2: add tables for rivals and favorite music

This commit is contained in:
2024-06-08 19:04:27 -04:00
committed by beerpsi
parent 6ca16b28d5
commit 6c0cec4904
5 changed files with 406 additions and 5 deletions

View File

@ -0,0 +1,48 @@
"""mai2_add_favs_rivals
Revision ID: 4a02e623e5e6
Revises: 8ad40a6e7be2
Create Date: 2024-06-08 19:02:43.856395
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import mysql
# revision identifiers, used by Alembic.
revision = '4a02e623e5e6'
down_revision = '8ad40a6e7be2'
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('mai2_item_favorite_music',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user', sa.Integer(), nullable=False),
sa.Column('musicId', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('user', 'musicId', name='mai2_item_favorite_music_uk'),
mysql_charset='utf8mb4'
)
op.create_table('mai2_user_rival',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user', sa.Integer(), nullable=False),
sa.Column('rival', sa.Integer(), nullable=False),
sa.Column('show', sa.Boolean(), server_default='0', nullable=False),
sa.ForeignKeyConstraint(['rival'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'),
sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('user', 'rival', name='mai2_user_rival_uk'),
mysql_charset='utf8mb4'
)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_table('mai2_user_rival')
op.drop_table('mai2_item_favorite_music')
# ### end Alembic commands ###

View File

@ -1,7 +1,7 @@
"""mai2_buddies_support
Revision ID: 81e44dd6047a
Revises: d8950c7ce2fc
Revises: 6a7e8277763b
Create Date: 2024-03-12 19:10:37.063907
"""

View File

@ -885,3 +885,262 @@ class Mai2Base:
self.logger.error(f"Failed to delete {out_name}.bin, please remove it manually")
return {'returnCode': ret_code, 'apiName': 'UploadUserPhotoApi'}
async def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
user_id = data.get("userId", 0)
kind = data.get("kind", 0) # 1 is fav music, 2 is rival user IDs
next_index = data.get("nextIndex", 0)
max_ct = data.get("maxCount", 100) # always 100
is_all = data.get("isAllFavoriteItem", False) # always false
id_list: List[Dict] = []
if user_id:
if kind == 1:
fav_music = await self.data.item.get_fav_music(user_id)
if fav_music:
for fav in fav_music:
id_list.append({"orderId": 0, "id": fav["musicId"]})
if len(id_list) >= 100: # Lazy but whatever
break
elif kind == 2:
rivals = await self.data.profile.get_rivals_game(user_id)
if rivals:
for rival in rivals:
id_list.append({"orderId": 0, "id": rival["rival"]})
return {
"userId": user_id,
"kind": kind,
"nextIndex": 0,
"userFavoriteItemList": id_list,
}
async def handle_get_user_recommend_rate_music_api_request(self, data: Dict) -> Dict:
"""
userRecommendRateMusicIdList: list[int]
"""
return {"userId": data["userId"], "userRecommendRateMusicIdList": []}
async def handle_get_user_recommend_select_music_api_request(self, data: Dict) -> Dict:
"""
userRecommendSelectionMusicIdList: list[int]
"""
return {"userId": data["userId"], "userRecommendSelectionMusicIdList": []}
async def handle_get_user_new_item_api_request(self, data: Dict) -> Dict:
# TODO: Added in 1.41, implement this?
user_id = data["userId"]
version = data.get("version", 1041000)
user_playlog_list = data.get("userPlaylogList", [])
return {
"userId": user_id,
"itemKind": -1,
"itemId": -1,
}
# CardMaker support added in Universe
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
p = await self.data.profile.get_profile_detail(data["userId"], self.version)
if p is None:
return {}
return {
"userName": p["userName"],
"rating": p["playerRating"],
# hardcode lastDataVersion for CardMaker
"lastDataVersion": "1.20.00", # Future versiohs should replace this with the correct version
# checks if the user is still logged in
"isLogin": False,
"isExistSellingCard": True,
}
async def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
# user already exists, because the preview checks that already
p = await self.data.profile.get_profile_detail(data["userId"], self.version)
cards = await self.data.card.get_user_cards(data["userId"])
if cards is None or len(cards) == 0:
# This should never happen
self.logger.error(
f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}"
)
return {}
# get the dict representation of the row so we can modify values
user_data = p._asdict()
# remove the values the game doesn't want
user_data.pop("id")
user_data.pop("user")
user_data.pop("version")
return {"userId": data["userId"], "userData": user_data}
async def handle_cm_login_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1}
async def handle_cm_logout_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1}
async def handle_cm_get_selling_card_api_request(self, data: Dict) -> Dict:
selling_cards = await self.data.static.get_enabled_cards(self.version)
if selling_cards is None:
return {"length": 0, "sellingCardList": []}
selling_card_list = []
for card in selling_cards:
tmp = card._asdict()
tmp.pop("id")
tmp.pop("version")
tmp.pop("cardName")
tmp.pop("enabled")
tmp["startDate"] = datetime.strftime(
tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT
)
tmp["endDate"] = datetime.strftime(
tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT
)
tmp["noticeStartDate"] = datetime.strftime(
tmp["noticeStartDate"], Mai2Constants.DATE_TIME_FORMAT
)
tmp["noticeEndDate"] = datetime.strftime(
tmp["noticeEndDate"], Mai2Constants.DATE_TIME_FORMAT
)
selling_card_list.append(tmp)
return {"length": len(selling_card_list), "sellingCardList": selling_card_list}
async def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = await self.data.item.get_cards(data["userId"])
if user_cards is None:
return {"returnCode": 1, "length": 0, "nextIndex": 0, "userCardList": []}
max_ct = data["maxCount"]
next_idx = data["nextIndex"]
start_idx = next_idx
end_idx = max_ct + start_idx
if len(user_cards[start_idx:]) > max_ct:
next_idx += max_ct
else:
next_idx = 0
card_list = []
for card in user_cards:
tmp = card._asdict()
tmp.pop("id")
tmp.pop("user")
tmp["startDate"] = datetime.strftime(
tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT
)
tmp["endDate"] = datetime.strftime(
tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT
)
card_list.append(tmp)
return {
"returnCode": 1,
"length": len(card_list[start_idx:end_idx]),
"nextIndex": next_idx,
"userCardList": card_list[start_idx:end_idx],
}
async def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
await self.handle_get_user_item_api_request(data)
async def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
characters = await self.data.item.get_characters(data["userId"])
chara_list = []
for chara in characters:
chara_list.append(
{
"characterId": chara["characterId"],
# no clue why those values are even needed
"point": 0,
"count": 0,
"level": chara["level"],
"nextAwake": 0,
"nextAwakePercent": 0,
"favorite": False,
"awakening": chara["awakening"],
"useCount": chara["useCount"],
}
)
return {
"returnCode": 1,
"length": len(chara_list),
"userCharacterList": chara_list,
}
async def handle_cm_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
return {"length": 0, "userPrintDetailList": []}
async def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
upsert = data["userPrintDetail"]
# set a random card serial number
serial_id = "".join([str(randint(0, 9)) for _ in range(20)])
# calculate start and end date of the card
start_date = datetime.utcnow()
end_date = datetime.utcnow() + timedelta(days=15)
user_card = upsert["userCard"]
await self.data.item.put_card(
user_id,
user_card["cardId"],
user_card["cardTypeId"],
user_card["charaId"],
user_card["mapId"],
# add the correct start date and also the end date in 15 days
start_date,
end_date,
)
# get the profile extend to save the new bought card
extend = await self.data.profile.get_profile_extend(user_id, self.version)
if extend:
extend = extend._asdict()
# parse the selectedCardList
# 6 = Freedom Pass, 4 = Gold Pass (cardTypeId)
selected_cards: List = extend["selectedCardList"]
# if no pass is already added, add the corresponding pass
if not user_card["cardTypeId"] in selected_cards:
selected_cards.insert(0, user_card["cardTypeId"])
extend["selectedCardList"] = selected_cards
await self.data.profile.put_profile_extend(user_id, self.version, extend)
# properly format userPrintDetail for the database
upsert.pop("userCard")
upsert.pop("serialId")
upsert["printDate"] = datetime.strptime(upsert["printDate"], "%Y-%m-%d")
await self.data.item.put_user_print_detail(user_id, serial_id, upsert)
return {
"returnCode": 1,
"orderId": 0,
"serialId": serial_id,
"startDate": datetime.strftime(start_date, Mai2Constants.DATE_TIME_FORMAT),
"endDate": datetime.strftime(end_date, Mai2Constants.DATE_TIME_FORMAT),
}
async def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
return {
"returnCode": 1,
"orderId": 0,
"serialId": data["userPrintlog"]["serialId"],
}
async def handle_cm_upsert_buy_card_api_request(self, data: Dict) -> Dict:
return {"returnCode": 1}

View File

@ -134,6 +134,20 @@ favorite = Table(
mysql_charset="utf8mb4",
)
fav_music = Table(
"mai2_item_favorite_music",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("musicId", Integer, nullable=False),
UniqueConstraint("user", "musicId", name="mai2_item_favorite_music_uk"),
mysql_charset="utf8mb4",
)
charge = Table(
"mai2_item_charge",
metadata,
@ -451,6 +465,30 @@ class Mai2ItemData(BaseData):
return None
return result.fetchall()
async def get_fav_music(self, user_id: int) -> Optional[List[Row]]:
result = await self.execute(fav_music.select(fav_music.c.user == user_id))
if result:
return result.fetchall()
async def add_fav_music(self, user_id: int, music_id: int) -> Optional[int]:
sql = insert(fav_music).values(
user = user_id,
musicId = music_id
)
conflict = sql.on_duplicate_key_do_nothing()
result = await self.execute(conflict)
if result:
return result.lastrowid
self.logger.error(f"Failed to add music {music_id} as favorite for user {user_id}!")
async def remove_fav_music(self, user_id: int, music_id: int) -> None:
result = await self.execute(fav_music.delete(and_(fav_music.c.user == user_id, fav_music.c.musicId == music_id)))
if not result:
self.logger.error(f"Failed to remove music {music_id} as favorite for user {user_id}!")
async def put_card(
self,
user_id: int,

View File

@ -187,14 +187,14 @@ ghost = Table(
Column("shopId", Integer),
Column("regionCode", Integer),
Column("typeId", Integer),
Column("musicId", Integer),
Column("rival", Integer),
Column("difficulty", Integer),
Column("version", Integer),
Column("resultBitList", JSON),
Column("resultNum", Integer),
Column("achievement", Integer),
UniqueConstraint(
"user", "version", "musicId", "difficulty", name="mai2_profile_ghost_uk"
"user", "version", "rival", "difficulty", name="mai2_profile_ghost_uk"
),
mysql_charset="utf8mb4",
)
@ -209,7 +209,7 @@ extend = Table(
nullable=False,
),
Column("version", Integer, nullable=False),
Column("selectMusicId", Integer),
Column("selectrival", Integer),
Column("selectDifficultyId", Integer),
Column("categoryIndex", Integer),
Column("musicIndex", Integer),
@ -239,7 +239,7 @@ option = Table(
nullable=False,
),
Column("version", Integer, nullable=False),
Column("selectMusicId", Integer),
Column("selectrival", Integer),
Column("optionKind", Integer),
Column("noteSpeed", Integer),
Column("slideSpeed", Integer),
@ -491,6 +491,24 @@ consec_logins = Table(
mysql_charset="utf8mb4",
)
rival = Table(
"mai2_user_rival",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
Column(
"user",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column(
"rival",
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False,
),
Column("show", Boolean, nullable=False, server_default="0"),
UniqueConstraint("user", "rival", name="mai2_user_rival_uk"),
mysql_charset="utf8mb4",
)
class Mai2ProfileData(BaseData):
async def put_profile_detail(
@ -843,3 +861,41 @@ class Mai2ProfileData(BaseData):
if result is None:
return None
return result.fetchone()
async def get_rivals(self, user_id: int) -> Optional[List[Row]]:
result = await self.execute(rival.select(rival.c.user == user_id))
if result:
return result.fetchall()
async def get_rivals_game(self, user_id: int) -> Optional[List[Row]]:
result = await self.execute(rival.select(and_(rival.c.user == user_id, rival.c.show == True)).limit(3))
if result:
return result.fetchall()
async def set_rival_shown(self, user_id: int, rival_id: int, is_shown: bool) -> None:
sql = rival.update(and_(rival.c.user == user_id, rival.c.rival == rival_id)).values(
show = is_shown
)
result = await self.execute(sql)
if not result:
self.logger.error(f"Failed to set rival {rival_id} shown status to {is_shown} for user {user_id}")
async def add_rival(self, user_id: int, rival_id: int) -> Optional[int]:
sql = insert(rival).values(
user = user_id,
rival = rival_id
)
conflict = sql.on_duplicate_key_do_nothing()
result = await self.execute(conflict)
if result:
return result.lastrowid
self.logger.error(f"Failed to add music {rival_id} as favorite for user {user_id}!")
async def remove_rival(self, user_id: int, rival_id: int) -> None:
result = await self.execute(rival.delete(and_(rival.c.user == user_id, rival.c.rival == rival_id)))
if not result:
self.logger.error(f"Failed to remove rival {rival_id} for user {user_id}!")