From f8db1e21494bcd157872083356b5ccbbeef510e7 Mon Sep 17 00:00:00 2001 From: akanyan Date: Mon, 27 May 2024 06:42:08 +0900 Subject: [PATCH 01/25] ongeki: fix clearstatus type --- titles/ongeki/schema/score.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/ongeki/schema/score.py b/titles/ongeki/schema/score.py index f5ab4e1..6867133 100644 --- a/titles/ongeki/schema/score.py +++ b/titles/ongeki/schema/score.py @@ -30,7 +30,7 @@ score_best = Table( Column("isFullCombo", Boolean, nullable=False), Column("isAllBreake", Boolean, nullable=False), Column("isLock", Boolean, nullable=False), - Column("clearStatus", Boolean, nullable=False), + Column("clearStatus", Integer, nullable=False), Column("isStoryWatched", Boolean, nullable=False), Column("platinumScoreMax", Integer), UniqueConstraint("user", "musicId", "level", name="ongeki_best_score_uk"), From df2a4d3074f35c97ece456a017a5be91e8b4267b Mon Sep 17 00:00:00 2001 From: akanyan Date: Wed, 29 May 2024 19:15:22 +0900 Subject: [PATCH 02/25] ongeki: clearstatus db migration --- .../8ad40a6e7be2_ongeki_fix_clearstatus.py | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 core/data/alembic/versions/8ad40a6e7be2_ongeki_fix_clearstatus.py diff --git a/core/data/alembic/versions/8ad40a6e7be2_ongeki_fix_clearstatus.py b/core/data/alembic/versions/8ad40a6e7be2_ongeki_fix_clearstatus.py new file mode 100644 index 0000000..8b6ec51 --- /dev/null +++ b/core/data/alembic/versions/8ad40a6e7be2_ongeki_fix_clearstatus.py @@ -0,0 +1,30 @@ +"""ongeki: fix clearStatus + +Revision ID: 8ad40a6e7be2 +Revises: 7dc13e364e53 +Create Date: 2024-05-29 19:03:30.062157 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '8ad40a6e7be2' +down_revision = '7dc13e364e53' +branch_labels = None +depends_on = None + + +def upgrade(): + op.alter_column('ongeki_score_best', 'clearStatus', + existing_type=mysql.TINYINT(display_width=1), + type_=sa.Integer(), + existing_nullable=False) + + +def downgrade(): + op.alter_column('ongeki_score_best', 'clearStatus', + existing_type=sa.Integer(), + type_=mysql.TINYINT(display_width=1), + existing_nullable=False) From d57aa934017c1272bb9f940504bf968a699b8dc0 Mon Sep 17 00:00:00 2001 From: ThatzOkay Date: Thu, 30 May 2024 09:28:07 +0200 Subject: [PATCH 03/25] Fix for diva reader when trying to read modded content. When it can't parse a number. So instead of crashing give a friendly error and continue --- changelog.md | 5 +++++ titles/diva/read.py | 6 +++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 0e8b39a..3f8c6ba 100644 --- a/changelog.md +++ b/changelog.md @@ -1,7 +1,12 @@ # Changelog Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to. +## 20240530 +### DIVA ++ Fix reader for when dificulty is not a int + ## 20240526 +### DIVA + Fixed missing awaits causing coroutine error ## 20240524 diff --git a/titles/diva/read.py b/titles/diva/read.py index 97c9481..64603ca 100644 --- a/titles/diva/read.py +++ b/titles/diva/read.py @@ -183,7 +183,11 @@ class DivaReader(BaseReader): pv_list[pv_id] = self.add_branch(pv_list[pv_id], key_args, val) for pv_id, pv_data in pv_list.items(): - song_id = int(pv_id.split("_")[1]) + try: + song_id = int(pv_id.split("_")[1]) + except ValueError: + self.logger.error(f"Invalid song ID format: {pv_id}") + continue if "songinfo" not in pv_data: continue if "illustrator" not in pv_data["songinfo"]: From efd249d808f5091adda58537850829f63790ce2a Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 17:26:51 -0400 Subject: [PATCH 04/25] maimai: some housekeeping --- titles/mai2/base.py | 249 ++++++++++++++++++++++++++++++++++++ titles/mai2/buddies.py | 13 -- titles/mai2/festival.py | 14 +- titles/mai2/festivalplus.py | 19 --- titles/mai2/universe.py | 206 ----------------------------- 5 files changed, 250 insertions(+), 251 deletions(-) diff --git a/titles/mai2/base.py b/titles/mai2/base.py index 90597d1..ee46ae9 100644 --- a/titles/mai2/base.py +++ b/titles/mai2/base.py @@ -4,6 +4,7 @@ import logging from base64 import b64decode from os import path, stat, remove from PIL import ImageFile +from random import randint import pytz from core.config import CoreConfig @@ -886,3 +887,251 @@ 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", 2) # 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 + + """ + class userFavoriteItemList: + orderId: int, never checked + id: int, either song ID for kind 1, or rival user ID for kind 2 + """ + return { + "userId": user_id, + "kind": kind, + "nextIndex": 0, + "userFavoriteItemList": [], + } + + 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} diff --git a/titles/mai2/buddies.py b/titles/mai2/buddies.py index f04b215..38049a1 100644 --- a/titles/mai2/buddies.py +++ b/titles/mai2/buddies.py @@ -17,16 +17,3 @@ class Mai2Buddies(Mai2FestivalPlus): # hardcode lastDataVersion for CardMaker user_data["lastDataVersion"] = "1.40.00" return user_data - - 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, - } - diff --git a/titles/mai2/festival.py b/titles/mai2/festival.py index 94ce3ec..451f0ba 100644 --- a/titles/mai2/festival.py +++ b/titles/mai2/festival.py @@ -20,18 +20,6 @@ class Mai2Festival(Mai2UniversePlus): async def handle_user_login_api_request(self, data: Dict) -> Dict: user_login = await super().handle_user_login_api_request(data) - # useless? + # TODO: Make use of this user_login["Bearer"] = "ARTEMiSTOKEN" return user_login - - 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": []} diff --git a/titles/mai2/festivalplus.py b/titles/mai2/festivalplus.py index 375d546..3314e34 100644 --- a/titles/mai2/festivalplus.py +++ b/titles/mai2/festivalplus.py @@ -17,22 +17,3 @@ class Mai2FestivalPlus(Mai2Festival): # hardcode lastDataVersion for CardMaker user_data["lastDataVersion"] = "1.35.00" return user_data - - async def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict: - user_id = data.get("userId", 0) - kind = data.get("kind", 2) - next_index = data.get("nextIndex", 0) - max_ct = data.get("maxCount", 100) - is_all = data.get("isAllFavoriteItem", False) - - """ - class userFavoriteItemList: - orderId: int - id: int - """ - return { - "userId": user_id, - "kind": kind, - "nextIndex": 0, - "userFavoriteItemList": [], - } diff --git a/titles/mai2/universe.py b/titles/mai2/universe.py index 00353d3..8ed0d88 100644 --- a/titles/mai2/universe.py +++ b/titles/mai2/universe.py @@ -1,8 +1,6 @@ from typing import Any, List, Dict from random import randint from datetime import datetime, timedelta -import pytz -import json from core.config import CoreConfig from titles.mai2.splashplus import Mai2SplashPlus @@ -14,207 +12,3 @@ class Mai2Universe(Mai2SplashPlus): def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None: super().__init__(cfg, game_cfg) self.version = Mai2Constants.VER_MAIMAI_DX_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", - # 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 super().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} From f94d22ab0dc5af97d55cd4ce1713efb19e29acb7 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 19:04:27 -0400 Subject: [PATCH 05/25] mai2: add tables for rivals and favorite music --- .../4a02e623e5e6_mai2_add_favs_rivals.py | 48 ++++++++++++++ .../81e44dd6047a_mai2_buddies_support.py | 2 +- titles/mai2/base.py | 25 ++++++-- titles/mai2/schema/item.py | 38 +++++++++++ titles/mai2/schema/profile.py | 64 +++++++++++++++++-- 5 files changed, 165 insertions(+), 12 deletions(-) create mode 100644 core/data/alembic/versions/4a02e623e5e6_mai2_add_favs_rivals.py diff --git a/core/data/alembic/versions/4a02e623e5e6_mai2_add_favs_rivals.py b/core/data/alembic/versions/4a02e623e5e6_mai2_add_favs_rivals.py new file mode 100644 index 0000000..d221bb6 --- /dev/null +++ b/core/data/alembic/versions/4a02e623e5e6_mai2_add_favs_rivals.py @@ -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 ### diff --git a/core/data/alembic/versions/81e44dd6047a_mai2_buddies_support.py b/core/data/alembic/versions/81e44dd6047a_mai2_buddies_support.py index 04d2217..0a2267c 100644 --- a/core/data/alembic/versions/81e44dd6047a_mai2_buddies_support.py +++ b/core/data/alembic/versions/81e44dd6047a_mai2_buddies_support.py @@ -1,7 +1,7 @@ """mai2_buddies_support Revision ID: 81e44dd6047a -Revises: d8950c7ce2fc +Revises: 6a7e8277763b Create Date: 2024-03-12 19:10:37.063907 """ diff --git a/titles/mai2/base.py b/titles/mai2/base.py index ee46ae9..b2386c1 100644 --- a/titles/mai2/base.py +++ b/titles/mai2/base.py @@ -890,21 +890,32 @@ class Mai2Base: async def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict: user_id = data.get("userId", 0) - kind = data.get("kind", 2) # 1 is fav music, 2 is rival user IDs + 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"]}) - """ - class userFavoriteItemList: - orderId: int, never checked - id: int, either song ID for kind 1, or rival user ID for kind 2 - """ return { "userId": user_id, "kind": kind, "nextIndex": 0, - "userFavoriteItemList": [], + "userFavoriteItemList": id_list, } async def handle_get_user_recommend_rate_music_api_request(self, data: Dict) -> Dict: diff --git a/titles/mai2/schema/item.py b/titles/mai2/schema/item.py index 9aaf592..f22cccd 100644 --- a/titles/mai2/schema/item.py +++ b/titles/mai2/schema/item.py @@ -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, diff --git a/titles/mai2/schema/profile.py b/titles/mai2/schema/profile.py index 8f1d5f3..97dcaad 100644 --- a/titles/mai2/schema/profile.py +++ b/titles/mai2/schema/profile.py @@ -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}!") From 8e6e5ea903090021e229684d64af7c433b3c0e6d Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 19:10:24 -0400 Subject: [PATCH 06/25] chuni: fix frontend if no chunithm profiles are loaded --- titles/chuni/templates/chuni_index.jinja | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/chuni/templates/chuni_index.jinja b/titles/chuni/templates/chuni_index.jinja index 69248e8..1854a89 100644 --- a/titles/chuni/templates/chuni_index.jinja +++ b/titles/chuni/templates/chuni_index.jinja @@ -5,7 +5,7 @@
{% include 'titles/chuni/templates/chuni_header.jinja' %} - {% if profile is defined and profile is not none and profile.id > 0 %} + {% if profile is defined and profile is not none and profile|length > 0 %}
From 79f511c83794d65d06bcd531c5c0074f52bce7b4 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 19:18:15 -0400 Subject: [PATCH 07/25] frontend: add username login --- core/data/schema/card.py | 2 +- core/data/schema/user.py | 4 ++++ core/frontend.py | 8 ++++++-- core/templates/gate/gate.jinja | 12 ++++++------ 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/core/data/schema/card.py b/core/data/schema/card.py index c31e1b2..cd5c647 100644 --- a/core/data/schema/card.py +++ b/core/data/schema/card.py @@ -121,7 +121,7 @@ class CardData(BaseData): result = await self.execute(sql) if result is None: self.logger.warn(f"Failed to update last login time for {access_code}") - + def to_access_code(self, luid: str) -> str: """ Given a felica cards internal 16 hex character luid, convert it to a 0-padded 20 digit access code as a string diff --git a/core/data/schema/user.py b/core/data/schema/user.py index 5f1ccf2..8c3695c 100644 --- a/core/data/schema/user.py +++ b/core/data/schema/user.py @@ -120,3 +120,7 @@ class UserData(BaseData): result = await self.execute(sql) return result is not None + + async def get_user_by_username(self, username: str) -> Optional[Row]: + result = await self.execute(aime_user.select(aime_user.c.username == username)) + if result: return result.fetchone() diff --git a/core/frontend.py b/core/frontend.py index bb3e9aa..b9d1604 100644 --- a/core/frontend.py +++ b/core/frontend.py @@ -308,8 +308,12 @@ class FE_Gate(FE_Base): uid = await self.data.card.get_user_id_from_card(access_code) if uid is None: - self.logger.debug(f"Failed to find user for card {access_code}") - return RedirectResponse("/gate/?e=1", 303) + user = await self.data.user.get_user_by_username(access_code) # Lookup as username + if not user: + self.logger.debug(f"Failed to find user for card/username {access_code}") + return RedirectResponse("/gate/?e=1", 303) + + uid = user['id'] user = await self.data.user.get_user(uid) if user is None: diff --git a/core/templates/gate/gate.jinja b/core/templates/gate/gate.jinja index ca3e2eb..d398cbd 100644 --- a/core/templates/gate/gate.jinja +++ b/core/templates/gate/gate.jinja @@ -15,18 +15,18 @@ -moz-appearance: textfield; } -
+
-
- +
+ +
20 Digit access code from a card registered to your account, or your account username. (NOT your username from a game!)

- + +
Leave blank if registering for the webui. Your card must have been used on a game connected to this server to register.

-
*To register for the webui, type in the access code of your card, as shown in a game, and leave the password field blank.
-
*If you have not registered a card with this server, you cannot create a webui account.
{% endblock content %} \ No newline at end of file From 123ec99a97d4f7045a082a01d9a15b2e27c6f9de Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 20:55:41 -0400 Subject: [PATCH 08/25] mai2: fix aggressive find and replace --- titles/mai2/schema/profile.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/titles/mai2/schema/profile.py b/titles/mai2/schema/profile.py index 97dcaad..d45ee16 100644 --- a/titles/mai2/schema/profile.py +++ b/titles/mai2/schema/profile.py @@ -187,14 +187,14 @@ ghost = Table( Column("shopId", Integer), Column("regionCode", Integer), Column("typeId", Integer), - Column("rival", Integer), + Column("musicId", Integer), Column("difficulty", Integer), Column("version", Integer), Column("resultBitList", JSON), Column("resultNum", Integer), Column("achievement", Integer), UniqueConstraint( - "user", "version", "rival", "difficulty", name="mai2_profile_ghost_uk" + "user", "version", "musicId", "difficulty", name="mai2_profile_ghost_uk" ), mysql_charset="utf8mb4", ) @@ -209,7 +209,7 @@ extend = Table( nullable=False, ), Column("version", Integer, nullable=False), - Column("selectrival", Integer), + Column("selectMusicId", Integer), Column("selectDifficultyId", Integer), Column("categoryIndex", Integer), Column("musicIndex", Integer), @@ -239,7 +239,7 @@ option = Table( nullable=False, ), Column("version", Integer, nullable=False), - Column("selectrival", Integer), + Column("selectMusicId", Integer), Column("optionKind", Integer), Column("noteSpeed", Integer), Column("slideSpeed", Integer), From eccbd1ad812a02a1e193a6186b7fbeab879554b7 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 21:25:48 -0400 Subject: [PATCH 09/25] mai2: add rivals support --- titles/mai2/base.py | 58 +++++++++++++++++++++++++++++++++++++ titles/mai2/schema/score.py | 8 ++--- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/titles/mai2/base.py b/titles/mai2/base.py index b2386c1..974c94c 100644 --- a/titles/mai2/base.py +++ b/titles/mai2/base.py @@ -942,6 +942,64 @@ class Mai2Base: "itemId": -1, } + async def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict: + user_id = data.get("userId", 0) + rival_id = data.get("rivalId", 0) + + if not user_id or not rival_id: return {} + + rival_pf = await self.data.profile.get_profile_detail(rival_id) + if not rival_pf: return {} + + return { + "userId": user_id, + "userRivalData": { + "rivalId": rival_id, + "rivalName": rival_pf['userName'] + } + } + + async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict: + user_id = data.get("userId", 0) + rival_id = data.get("rivalId", 0) + next_index = data.get("nextIndex", 0) + max_ct = 100 + upper_lim = next_index + max_ct + rival_music_list: Dict[int, List] = {} + + songs = await self.data.score.get_best_scores(rival_id) + if songs is None: + self.logger.debug("handle_get_user_rival_music_api_request: get_best_scores returned None!") + return { + "userId": user_id, + "rivalId": rival_id, + "nextIndex": 0, + "userRivalMusicList": [] # musicId userRivalMusicDetailList -> level achievement deluxscoreMax + } + + num_user_songs = len(songs) + + for x in range(next_index, upper_lim): + + tmp = songs[x]._asdict() + if tmp['musicId'] in rival_music_list: + rival_music_list[tmp['musicId']].append([{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}]) + + else: + if len(rival_music_list) >= max_ct: + break + rival_music_list[tmp['musicId']] = [{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}] + + next_index = 0 if len(rival_music_list) < max_ct or num_user_songs == upper_lim else upper_lim + self.logger.info(f"Send rival {rival_id} songs {next_index}-{upper_lim} ({len(rival_music_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})") + + return { + "userId": user_id, + "rivalId": rival_id, + "nextIndex": next_index, + "userRivalMusicList": [{"musicId": x, "userRivalMusicDetailList": y} for x, y in rival_music_list.items()] + } + # 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) diff --git a/titles/mai2/schema/score.py b/titles/mai2/schema/score.py index d13faae..59b61b6 100644 --- a/titles/mai2/schema/score.py +++ b/titles/mai2/schema/score.py @@ -319,16 +319,16 @@ class Mai2ScoreData(BaseData): sql = best_score.select( and_( best_score.c.user == user_id, - (best_score.c.song_id == song_id) if song_id is not None else True, + (best_score.c.musicId == song_id) if song_id is not None else True, ) - ) + ).order_by(best_score.c.musicId).order_by(best_score.c.level) else: sql = best_score_old.select( and_( best_score_old.c.user == user_id, - (best_score_old.c.song_id == song_id) if song_id is not None else True, + (best_score_old.c.musicId == song_id) if song_id is not None else True, ) - ) + ).order_by(best_score.c.musicId).order_by(best_score.c.level) result = await self.execute(sql) if result is None: From 8b03f1a4f13487779f4b6036a5ca02e950ddbf5d Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 22:02:31 -0400 Subject: [PATCH 10/25] mai2: fix rival data load failing due to inheritance --- titles/mai2/base.py | 275 ----------------------------------------- titles/mai2/dx.py | 292 ++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 269 insertions(+), 298 deletions(-) diff --git a/titles/mai2/base.py b/titles/mai2/base.py index 974c94c..4d31213 100644 --- a/titles/mai2/base.py +++ b/titles/mai2/base.py @@ -929,278 +929,3 @@ class Mai2Base: 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, - } - - async def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict: - user_id = data.get("userId", 0) - rival_id = data.get("rivalId", 0) - - if not user_id or not rival_id: return {} - - rival_pf = await self.data.profile.get_profile_detail(rival_id) - if not rival_pf: return {} - - return { - "userId": user_id, - "userRivalData": { - "rivalId": rival_id, - "rivalName": rival_pf['userName'] - } - } - - async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict: - user_id = data.get("userId", 0) - rival_id = data.get("rivalId", 0) - next_index = data.get("nextIndex", 0) - max_ct = 100 - upper_lim = next_index + max_ct - rival_music_list: Dict[int, List] = {} - - songs = await self.data.score.get_best_scores(rival_id) - if songs is None: - self.logger.debug("handle_get_user_rival_music_api_request: get_best_scores returned None!") - return { - "userId": user_id, - "rivalId": rival_id, - "nextIndex": 0, - "userRivalMusicList": [] # musicId userRivalMusicDetailList -> level achievement deluxscoreMax - } - - num_user_songs = len(songs) - - for x in range(next_index, upper_lim): - - tmp = songs[x]._asdict() - if tmp['musicId'] in rival_music_list: - rival_music_list[tmp['musicId']].append([{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}]) - - else: - if len(rival_music_list) >= max_ct: - break - rival_music_list[tmp['musicId']] = [{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}] - - next_index = 0 if len(rival_music_list) < max_ct or num_user_songs == upper_lim else upper_lim - self.logger.info(f"Send rival {rival_id} songs {next_index}-{upper_lim} ({len(rival_music_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})") - - return { - "userId": user_id, - "rivalId": rival_id, - "nextIndex": next_index, - "userRivalMusicList": [{"musicId": x, "userRivalMusicDetailList": y} for x, y in rival_music_list.items()] - } - - # 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} diff --git a/titles/mai2/dx.py b/titles/mai2/dx.py index 4423824..2c7d960 100644 --- a/titles/mai2/dx.py +++ b/titles/mai2/dx.py @@ -563,33 +563,74 @@ class Mai2DX(Mai2Base): return {"userId": data["userId"], "length": 0, "userRegionList": []} async def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict: - user_id = data["userId"] - rival_id = data["rivalId"] + user_id = data.get("userId", 0) + rival_id = data.get("rivalId", 0) - """ - class UserRivalData: - rivalId: int - rivalName: str - """ - return {"userId": user_id, "userRivalData": {}} + if not user_id or not rival_id: return {} + + rival_pf = await self.data.profile.get_profile_detail(rival_id) + if not rival_pf: return {} + + return { + "userId": user_id, + "userRivalData": { + "rivalId": rival_id, + "rivalName": rival_pf['userName'] + } + } async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict: + user_id = data.get("userId", 0) + rival_id = data.get("rivalId", 0) + next_index = data.get("nextIndex", 0) + max_ct = 100 + upper_lim = next_index + max_ct + rival_music_list: Dict[int, List] = {} + + songs = await self.data.score.get_best_scores(rival_id) + if songs is None: + self.logger.debug("handle_get_user_rival_music_api_request: get_best_scores returned None!") + return { + "userId": user_id, + "rivalId": rival_id, + "nextIndex": 0, + "userRivalMusicList": [] # musicId userRivalMusicDetailList -> level achievement deluxscoreMax + } + + num_user_songs = len(songs) + + for x in range(next_index, upper_lim): + + tmp = songs[x]._asdict() + if tmp['musicId'] in rival_music_list: + rival_music_list[tmp['musicId']].append([{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}]) + + else: + if len(rival_music_list) >= max_ct: + break + rival_music_list[tmp['musicId']] = [{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}] + + next_index = 0 if len(rival_music_list) < max_ct or num_user_songs == upper_lim else upper_lim + self.logger.info(f"Send rival {rival_id} songs {next_index}-{upper_lim} ({len(rival_music_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})") + + return { + "userId": user_id, + "rivalId": rival_id, + "nextIndex": next_index, + "userRivalMusicList": [{"musicId": x, "userRivalMusicDetailList": y} for x, y in rival_music_list.items()] + } + + async def handle_get_user_new_item_api_request(self, data: Dict) -> Dict: + # TODO: Added in 1.41, implement this? user_id = data["userId"] - rival_id = data["rivalId"] - next_idx = data["nextIndex"] - rival_music_levels = data["userRivalMusicLevelList"] - - """ - class UserRivalMusicList: - class UserRivalMusicDetailList: - level: int - achievement: int - deluxscoreMax: int - - musicId: int - userRivalMusicDetailList: list[UserRivalMusicDetailList] - """ - return {"userId": user_id, "nextIndex": 0, "userRivalMusicList": []} + version = data.get("version", 1041000) + user_playlog_list = data.get("userPlaylogList", []) + + return { + "userId": user_id, + "itemKind": -1, + "itemId": -1, + } async def handle_get_user_music_api_request(self, data: Dict) -> Dict: user_id = data.get("userId", 0) @@ -636,3 +677,208 @@ class Mai2DX(Mai2Base): return ret ret['loginId'] = ret.get('loginCount', 0) return ret + + # 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} From 319aea098f1dbe587f4aea15997cd1e071bc948c Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 22:19:59 -0400 Subject: [PATCH 11/25] dx: fix GetUserRivalMusicApi list index out of range --- titles/mai2/dx.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/titles/mai2/dx.py b/titles/mai2/dx.py index 2c7d960..3c5c319 100644 --- a/titles/mai2/dx.py +++ b/titles/mai2/dx.py @@ -600,6 +600,8 @@ class Mai2DX(Mai2Base): num_user_songs = len(songs) for x in range(next_index, upper_lim): + if x >= num_user_songs: + break tmp = songs[x]._asdict() if tmp['musicId'] in rival_music_list: From e7ddfcda2e25ca30868908bf55426958eadd6ea6 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sat, 8 Jun 2024 22:29:49 -0400 Subject: [PATCH 12/25] mai2: oops, forgot version --- titles/mai2/dx.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/mai2/dx.py b/titles/mai2/dx.py index 3c5c319..04c9172 100644 --- a/titles/mai2/dx.py +++ b/titles/mai2/dx.py @@ -568,7 +568,7 @@ class Mai2DX(Mai2Base): if not user_id or not rival_id: return {} - rival_pf = await self.data.profile.get_profile_detail(rival_id) + rival_pf = await self.data.profile.get_profile_detail(rival_id, self.version) if not rival_pf: return {} return { From b4b8650acc846cff59092f9e95218ab50d162549 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 9 Jun 2024 03:05:57 -0400 Subject: [PATCH 13/25] mai2: add basic webui --- core/frontend.py | 16 +- titles/mai2/__init__.py | 2 + titles/mai2/frontend.py | 190 +++++++++++++++++++ titles/mai2/schema/profile.py | 16 ++ titles/mai2/schema/score.py | 20 ++ titles/mai2/templates/css/mai2_style.css | 195 ++++++++++++++++++++ titles/mai2/templates/mai2_header.jinja | 17 ++ titles/mai2/templates/mai2_index.jinja | 134 ++++++++++++++ titles/mai2/templates/mai2_playlog.jinja | 225 +++++++++++++++++++++++ 9 files changed, 813 insertions(+), 2 deletions(-) create mode 100644 titles/mai2/frontend.py create mode 100644 titles/mai2/templates/css/mai2_style.css create mode 100644 titles/mai2/templates/mai2_header.jinja create mode 100644 titles/mai2/templates/mai2_index.jinja create mode 100644 titles/mai2/templates/mai2_playlog.jinja diff --git a/core/frontend.py b/core/frontend.py index b9d1604..31a6254 100644 --- a/core/frontend.py +++ b/core/frontend.py @@ -44,12 +44,13 @@ class ShopOwner(): self.permissions = perms class UserSession(): - def __init__(self, usr_id: int = 0, ip: str = "", perms: int = 0, ongeki_ver: int = 7, chunithm_ver: int = -1): + def __init__(self, usr_id: int = 0, ip: str = "", perms: int = 0, ongeki_ver: int = 7, chunithm_ver: int = -1, maimai_version: int = -1): self.user_id = usr_id self.current_ip = ip self.permissions = perms self.ongeki_version = ongeki_ver self.chunithm_version = chunithm_ver + self.maimai_version = maimai_version class FrontendServlet(): def __init__(self, cfg: CoreConfig, config_dir: str) -> None: @@ -216,6 +217,7 @@ class FE_Base(): sesh.current_ip = tk['current_ip'] sesh.permissions = tk['permissions'] sesh.chunithm_version = tk['chunithm_version'] + sesh.maimai_version = tk['maimai_version'] if sesh.user_id <= 0: self.logger.error("User session failed to validate due to an invalid ID!") @@ -260,7 +262,17 @@ class FE_Base(): def encode_session(self, sesh: UserSession, exp_seconds: int = 86400) -> str: try: - return jwt.encode({ "user_id": sesh.user_id, "current_ip": sesh.current_ip, "permissions": sesh.permissions, "ongeki_version": sesh.ongeki_version, "chunithm_version": sesh.chunithm_version, "exp": int(datetime.now(tz=timezone.utc).timestamp()) + exp_seconds }, b64decode(self.core_config.frontend.secret), algorithm="HS256") + return jwt.encode({ + "user_id": sesh.user_id, + "current_ip": sesh.current_ip, + "permissions": sesh.permissions, + "ongeki_version": sesh.ongeki_version, + "chunithm_version": sesh.chunithm_version, + "maimai_version": sesh.maimai_version, + "exp": int(datetime.now(tz=timezone.utc).timestamp()) + exp_seconds }, + b64decode(self.core_config.frontend.secret), + algorithm="HS256" + ) except jwt.InvalidKeyError: self.logger.error("Failed to encode User session because the secret is invalid!") return "" diff --git a/titles/mai2/__init__.py b/titles/mai2/__init__.py index 74cfddf..234e864 100644 --- a/titles/mai2/__init__.py +++ b/titles/mai2/__init__.py @@ -2,10 +2,12 @@ from titles.mai2.index import Mai2Servlet from titles.mai2.const import Mai2Constants from titles.mai2.database import Mai2Data from titles.mai2.read import Mai2Reader +from .frontend import Mai2Frontend index = Mai2Servlet database = Mai2Data reader = Mai2Reader +frontend = Mai2Frontend game_codes = [ Mai2Constants.GAME_CODE_DX, Mai2Constants.GAME_CODE_FINALE, diff --git a/titles/mai2/frontend.py b/titles/mai2/frontend.py new file mode 100644 index 0000000..6ce8828 --- /dev/null +++ b/titles/mai2/frontend.py @@ -0,0 +1,190 @@ +from typing import List +from starlette.routing import Route, Mount +from starlette.requests import Request +from starlette.responses import Response, RedirectResponse +from os import path +import yaml +import jinja2 + +from core.frontend import FE_Base, UserSession +from core.config import CoreConfig +from .database import Mai2Data +from .config import Mai2Config +from .const import Mai2Constants + +class Mai2Frontend(FE_Base): + def __init__( + self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str + ) -> None: + super().__init__(cfg, environment) + self.data = Mai2Data(cfg) + self.game_cfg = Mai2Config() + if path.exists(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"): + self.game_cfg.update( + yaml.safe_load(open(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}")) + ) + self.nav_name = "maimai" + + def get_routes(self) -> List[Route]: + return [ + Route("/", self.render_GET, methods=['GET']), + Mount("/playlog", routes=[ + Route("/", self.render_GET_playlog, methods=['GET']), + Route("/{index}", self.render_GET_playlog, methods=['GET']), + ]), + Route("/update.name", self.update_name, methods=['POST']), + Route("/version.change", self.version_change, methods=['POST']), + ] + + async def render_GET(self, request: Request) -> bytes: + template = self.environment.get_template( + "titles/mai2/templates/mai2_index.jinja" + ) + usr_sesh = self.validate_session(request) + if not usr_sesh: + usr_sesh = UserSession() + + if usr_sesh.user_id > 0: + versions = await self.data.profile.get_all_profile_versions(usr_sesh.user_id) + profile = [] + if versions: + # maimai_version is -1 means it is not initialized yet, select a default version from existing. + if usr_sesh.maimai_version < 0: + usr_sesh.maimai_version = versions[0]['version'] + profile = await self.data.profile.get_profile_detail(usr_sesh.user_id, usr_sesh.maimai_version) + versions = [x['version'] for x in versions] + + resp = Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + user_id=usr_sesh.user_id, + profile=profile, + version_list=Mai2Constants.VERSION_STRING, + versions=versions, + cur_version=usr_sesh.maimai_version + ), media_type="text/html; charset=utf-8") + + if usr_sesh.maimai_version >= 0: + encoded_sesh = self.encode_session(usr_sesh) + resp.delete_cookie("DIANA_SESH") + resp.set_cookie("DIANA_SESH", encoded_sesh) + return resp + + else: + return RedirectResponse("/gate/", 303) + + async def render_GET_playlog(self, request: Request) -> bytes: + template = self.environment.get_template( + "titles/mai2/templates/mai2_playlog.jinja" + ) + usr_sesh = self.validate_session(request) + if not usr_sesh: + print("wtf") + usr_sesh = UserSession() + + if usr_sesh.user_id > 0: + if usr_sesh.maimai_version < 0: + print(usr_sesh.maimai_version) + return RedirectResponse("/game/mai2/", 303) + path_index = request.path_params.get('index') + if not path_index or int(path_index) < 1: + index = 0 + else: + index = int(path_index) - 1 # 0 and 1 are 1st page + user_id = usr_sesh.user_id + playlog_count = await self.data.score.get_user_playlogs_count(user_id) + if playlog_count < index * 20 : + return Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + playlog_count=0 + ), media_type="text/html; charset=utf-8") + playlog = await self.data.score.get_playlogs(user_id, index, 20) + playlog_with_title = [] + for record in playlog: + music_chart = await self.data.static.get_music_chart(usr_sesh.maimai_version, record.musicId, record.level) + if music_chart: + difficultyNum=music_chart.chartId + difficulty=music_chart.difficulty + artist=music_chart.artist + title=music_chart.title + else: + difficultyNum=0 + difficulty=0 + artist="unknown" + title="musicid: " + str(record.musicId) + playlog_with_title.append({ + "raw": record, + "title": title, + "difficultyNum": difficultyNum, + "difficulty": difficulty, + "artist": artist, + }) + return Response(template.render( + title=f"{self.core_config.server.name} | {self.nav_name}", + game_list=self.environment.globals["game_list"], + sesh=vars(usr_sesh), + user_id=usr_sesh.user_id, + playlog=playlog_with_title, + playlog_count=playlog_count + ), media_type="text/html; charset=utf-8") + else: + return RedirectResponse("/gate/", 303) + + async def update_name(self, request: Request) -> bytes: + usr_sesh = self.validate_session(request) + if not usr_sesh: + return RedirectResponse("/gate/", 303) + + form_data = await request.form() + new_name: str = form_data.get("new_name") + new_name_full = "" + + if not new_name: + return RedirectResponse("/gate/?e=4", 303) + + if len(new_name) > 8: + return RedirectResponse("/gate/?e=8", 303) + + for x in new_name: # FIXME: This will let some invalid characters through atm + o = ord(x) + try: + if o == 0x20: + new_name_full += chr(0x3000) + elif o < 0x7F and o > 0x20: + new_name_full += chr(o + 0xFEE0) + elif o <= 0x7F: + self.logger.warn(f"Invalid ascii character {o:02X}") + return RedirectResponse("/gate/?e=4", 303) + else: + new_name_full += x + + except Exception as e: + self.logger.error(f"Something went wrong parsing character {o:04X} - {e}") + return RedirectResponse("/gate/?e=4", 303) + + if not await self.data.profile.update_name(usr_sesh.user_id, new_name_full): + return RedirectResponse("/gate/?e=999", 303) + + return RedirectResponse("/game/mai2/?s=1", 303) + + async def version_change(self, request: Request): + usr_sesh = self.validate_session(request) + if not usr_sesh: + usr_sesh = UserSession() + + if usr_sesh.user_id > 0: + form_data = await request.form() + maimai_version = form_data.get("version") + self.logger.info(f"version change to: {maimai_version}") + if(maimai_version.isdigit()): + usr_sesh.maimai_version=int(maimai_version) + encoded_sesh = self.encode_session(usr_sesh) + self.logger.info(f"Created session with JWT {encoded_sesh}") + resp = RedirectResponse("/game/mai2/", 303) + resp.set_cookie("DIANA_SESH", encoded_sesh) + return resp + else: + return RedirectResponse("/gate/", 303) \ No newline at end of file diff --git a/titles/mai2/schema/profile.py b/titles/mai2/schema/profile.py index d45ee16..1b76b07 100644 --- a/titles/mai2/schema/profile.py +++ b/titles/mai2/schema/profile.py @@ -511,6 +511,11 @@ rival = Table( ) class Mai2ProfileData(BaseData): + async def get_all_profile_versions(self, user_id: int) -> Optional[List[Row]]: + result = await self.execute(detail.select(detail.c.user == user_id)) + if result: + return result.fetchall() + async def put_profile_detail( self, user_id: int, version: int, detail_data: Dict, is_dx: bool = True ) -> Optional[Row]: @@ -899,3 +904,14 @@ class Mai2ProfileData(BaseData): 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}!") + + async def update_name(self, user_id: int, new_name: str) -> bool: + sql = detail.update(detail.c.user == user_id).values( + userName=new_name + ) + result = await self.execute(sql) + + if result is None: + self.logger.warning(f"Failed to set user {user_id} name to {new_name}") + return False + return True diff --git a/titles/mai2/schema/score.py b/titles/mai2/schema/score.py index 59b61b6..d4ea5b9 100644 --- a/titles/mai2/schema/score.py +++ b/titles/mai2/schema/score.py @@ -398,3 +398,23 @@ class Mai2ScoreData(BaseData): if result is None: return None return result.fetchall() + + async def get_playlogs(self, user_id: int, idx: int = 0, limit: int = 0) -> Optional[List[Row]]: + sql = playlog.select(playlog.c.user == user_id) + + if limit: + sql = sql.limit(limit) + if idx: + sql = sql.offset(idx * limit) + + result = await self.execute(sql) + if result: + return result.fetchall() + + async def get_user_playlogs_count(self, aime_id: int) -> Optional[Row]: + sql = select(func.count()).where(playlog.c.user == aime_id) + result = await self.execute(sql) + if result is None: + self.logger.warning(f"aime_id {aime_id} has no playlog ") + return None + return result.scalar() diff --git a/titles/mai2/templates/css/mai2_style.css b/titles/mai2/templates/css/mai2_style.css new file mode 100644 index 0000000..4aceab8 --- /dev/null +++ b/titles/mai2/templates/css/mai2_style.css @@ -0,0 +1,195 @@ +.mai2-header { + text-align: center; +} + +ul.mai2-navi { + list-style-type: none; + padding: 0; + overflow: hidden; + background-color: #333; + text-align: center; + display: inline-block; +} + +ul.mai2-navi li { + display: inline-block; +} + +ul.mai2-navi li a { + display: block; + color: white; + text-align: center; + padding: 14px 16px; + text-decoration: none; +} + +ul.mai2-navi li a:hover:not(.active) { + background-color: #111; +} + +ul.mai2-navi li a.active { + background-color: #4CAF50; +} + +ul.mai2-navi li.right { + float: right; +} + +@media screen and (max-width: 600px) { + + ul.mai2-navi li.right, + ul.mai2-navi li { + float: none; + display: block; + text-align: center; + } +} + +table { + border-collapse: collapse; + border-spacing: 0; + border-collapse: separate; + overflow: hidden; + background-color: #555555; + +} + +th, td { + text-align: left; + border: none; + +} + +th { + color: white; +} + +.table-rowdistinct tr:nth-child(even) { + background-color: #303030; +} + +.table-rowdistinct tr:nth-child(odd) { + background-color: #555555; +} + +caption { + text-align: center; + color: white; + font-size: 18px; + font-weight: bold; +} + +.table-large { + margin: 16px; +} + +.table-large th, +.table-large td { + padding: 8px; +} + +.table-small { + width: 100%; + margin: 4px; +} + +.table-small th, +.table-small td { + padding: 2px; +} + +.bg-card { + background-color: #555555; +} + +.card-hover { + transition: all 0.2s ease-in-out; +} + +.card-hover:hover { + transform: scale(1.02); +} + +.basic { + color: #28a745; + font-weight: bold; +} + +.hard { + color: #ffc107; + + font-weight: bold; +} + +.expert { + color: #dc3545; + font-weight: bold; +} + +.master { + color: #dd09e8; + font-weight: bold; +} + +.ultimate { + color: #000000; + font-weight: bold; +} + +.score { + color: #ffffff; + font-weight: bold; +} + +.rainbow { + background: linear-gradient(to right, red, yellow, lime, aqua, blue, fuchsia) 0 / 5em; + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + font-weight: bold; +} + +.platinum { + color: #FFFF00; + font-weight: bold; +} + +.gold { + color: #FFFF00; + font-weight: bold; +} + +.scrolling-text { + overflow: hidden; +} + +.scrolling-text p { + white-space: nowrap; + display: inline-block; + +} + +.scrolling-text h6 { + white-space: nowrap; + display: inline-block; + +} + +.scrolling-text h5 { + white-space: nowrap; + display: inline-block; + +} + +.scrolling { + animation: scroll 10s linear infinite; +} + +@keyframes scroll { + 0% { + transform: translateX(100%); + } + 100% { + transform: translateX(-100%); + } +} \ No newline at end of file diff --git a/titles/mai2/templates/mai2_header.jinja b/titles/mai2/templates/mai2_header.jinja new file mode 100644 index 0000000..4a4cb86 --- /dev/null +++ b/titles/mai2/templates/mai2_header.jinja @@ -0,0 +1,17 @@ +
+

maimai

+ +
+ \ No newline at end of file diff --git a/titles/mai2/templates/mai2_index.jinja b/titles/mai2/templates/mai2_index.jinja new file mode 100644 index 0000000..6490fdc --- /dev/null +++ b/titles/mai2/templates/mai2_index.jinja @@ -0,0 +1,134 @@ +{% extends "core/templates/index.jinja" %} +{% block content %} + +
+ {% include 'titles/mai2/templates/mai2_header.jinja' %} + {% if profile is defined and profile is not none and profile|length > 0 %} +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
OVERVIEW
{{ profile.userName }} + +
version: + + {% if versions | length > 1 %} +

You have {{ versions | length }} versions.

+ {% endif %} +
Rating: + + {{ profile.playerRating }} + + + (highest: {{ profile.highestRating }}) + +
Play Counts:{{ profile.playCount }}
Last Play Date:{{ profile.lastPlayDate }}
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
SCORE
Total Delux Score:{{ profile.totalDeluxscore }}
Total Basic Delux Score:{{ profile.totalBasicDeluxscore }}
Total Advanced Delux Score:{{ profile.totalAdvancedDeluxscore }}
Total Expert Delux Score:{{ profile.totalExpertDeluxscore }}
Total Master Delux Score:{{ profile.totalMasterDeluxscore }}
Total ReMaster Delux Score:{{ profile.totalReMasterDeluxscore }}
+
+
+
+ {% if error is defined %} + {% include "core/templates/widgets/err_banner.jinja" %} + {% endif %} + {% elif sesh is defined and sesh is not none and sesh.user_id > 0 %} + No profile information found for this account. + {% else %} + Login to view profile information. + {% endif %} +
+ + +{% endblock content %} \ No newline at end of file diff --git a/titles/mai2/templates/mai2_playlog.jinja b/titles/mai2/templates/mai2_playlog.jinja new file mode 100644 index 0000000..3e1d5fd --- /dev/null +++ b/titles/mai2/templates/mai2_playlog.jinja @@ -0,0 +1,225 @@ +{% extends "core/templates/index.jinja" %} +{% block content %} + +
+ {% include 'titles/mai2/templates/mai2_header.jinja' %} + {% if playlog is defined and playlog is not none %} +
+

Playlog counts: {{ playlog_count }}

+ {% set rankName = ['D', 'C', 'B', 'BB', 'BBB', 'A', 'AA', 'AAA', 'S', 'S+', 'SS', 'SS+', 'SSS', 'SSS+'] %} + {% set difficultyName = ['basic', 'hard', 'expert', 'master', 'ultimate'] %} + {% for record in playlog %} +
+
+
+
+
{{ record.title }}
+
+
{{ record.artist }}
+
+
+
{{ record.raw.userPlayDate }}
+
TRACK {{ record.raw.trackNo }}
+
+
+
+
+

{{ record.raw.deluxscore }}

+

{{ rankName[record.raw.rank] }}

+
+ {{ difficultyName[record.raw.level] }}  {{ record.difficulty }} +
+
+
+ + + + + + + + + + + + + + + + + + + + + +
CRITICAL PERFECT + Tap: {{ record.raw.tapCriticalPerfect }}
+ Hold: {{ record.raw.holdCriticalPerfect }}
+ Slide: {{ record.raw.slideCriticalPerfect }}
+ Touch: {{ record.raw.touchCriticalPerfect }}
+ Break: {{ record.raw.breakCriticalPerfect }} +
PERFECT + Tap: {{ record.raw.tapPerfect }}
+ Hold: {{ record.raw.holdPerfect }}
+ Slide: {{ record.raw.slidePerfect }}
+ Touch: {{ record.raw.touchPerfect }}
+ Break: {{ record.raw.breakPerfect }} +
GREAT + Tap: {{ record.raw.tapGreat }}
+ Hold: {{ record.raw.holdGreat }}
+ Slide: {{ record.raw.slideGreat }}
+ Touch: {{ record.raw.touchGreat }}
+ Break: {{ record.raw.breakGreat }} +
GOOD + Tap: {{ record.raw.tapGood }}
+ Hold: {{ record.raw.holdGood }}
+ Slide: {{ record.raw.slideGood }}
+ Touch: {{ record.raw.touchGood }}
+ Break: {{ record.raw.breakGood }} +
MISS + Tap: {{ record.raw.tapMiss }}
+ Hold: {{ record.raw.holdMiss }}
+ Slide: {{ record.raw.slideMiss }}
+ Touch: {{ record.raw.touchMiss }}
+ Break: {{ record.raw.breakMiss }} +
+
+
+ {%if record.raw.comboStatus == 1 %} +
FULL COMBO
+ {% endif %} + {%if record.raw.comboStatus == 2 %} +
FULL COMBO +
+ {% endif %} + {%if record.raw.comboStatus == 3 %} +
ALL PERFECT
+ {% endif %} + {%if record.raw.comboStatus == 4 %} +
ALL PERFECT +
+ {% endif %} + {%if record.raw.syncStatus == 1 %} +
FULL SYNC
+ {% endif %} + {%if record.raw.syncStatus == 2 %} +
FULL SYNC +
+ {% endif %} + {%if record.raw.syncStatus == 3 %} +
FULL SYNC DX
+ {% endif %} + {%if record.raw.syncStatus == 4 %} +
FULL SYNC DX +
+ {% endif %} + {%if record.raw.isAchieveNewRecord == 1 or record.raw.isDeluxscoreNewRecord == 1 %} +
NEW RECORD
+ {% endif %} +
+
+
+
+ {% endfor %} +
+ {% set playlog_pages = playlog_count // 20 + 1 %} + {% elif sesh is defined and sesh is not none and sesh.user_id > 0 %} + No Playlog information found for this account. + {% else %} + Login to view profile information. + {% endif %} +
+ + + +{% endblock content %} \ No newline at end of file From e69922d91b06e8ae5046755889999ad7defb9f19 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 9 Jun 2024 03:14:43 -0400 Subject: [PATCH 14/25] ongeki: fix frontend versions --- core/allnet.py | 2 +- core/frontend.py | 15 ++++++++------- titles/chuni/frontend.py | 4 ++-- titles/mai2/frontend.py | 6 +++--- titles/ongeki/frontend.py | 18 ++++++++++++------ 5 files changed, 26 insertions(+), 19 deletions(-) diff --git a/core/allnet.py b/core/allnet.py index 861a603..9eb6595 100644 --- a/core/allnet.py +++ b/core/allnet.py @@ -960,7 +960,7 @@ class DLReport: return True -cfg_dir = environ.get("DIANA_CFG_DIR", "config") +cfg_dir = environ.get("ARTEMIS_CFG_DIR", "config") cfg: CoreConfig = CoreConfig() if path.exists(f"{cfg_dir}/core.yaml"): cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml"))) diff --git a/core/frontend.py b/core/frontend.py index 31a6254..f15f58a 100644 --- a/core/frontend.py +++ b/core/frontend.py @@ -193,7 +193,7 @@ class FE_Base(): ), media_type="text/html; charset=utf-8") if sesh is None: - resp.delete_cookie("DIANA_SESH") + resp.delete_cookie("ARTEMIS_SESH") return resp def get_routes(self) -> List[Route]: @@ -218,6 +218,7 @@ class FE_Base(): sesh.permissions = tk['permissions'] sesh.chunithm_version = tk['chunithm_version'] sesh.maimai_version = tk['maimai_version'] + sesh.ongeki_version = tk['ongeki_version'] if sesh.user_id <= 0: self.logger.error("User session failed to validate due to an invalid ID!") @@ -243,7 +244,7 @@ class FE_Base(): return UserSession() def validate_session(self, request: Request) -> Optional[UserSession]: - sesh = request.cookies.get('DIANA_SESH', "") + sesh = request.cookies.get('ARTEMIS_SESH', "") if not sesh: return None @@ -304,7 +305,7 @@ class FE_Gate(FE_Base): error=err, sesh=vars(UserSession()), ), media_type="text/html; charset=utf-8") - resp.delete_cookie("DIANA_SESH") + resp.delete_cookie("ARTEMIS_SESH") return resp async def render_login(self, request: Request): @@ -354,7 +355,7 @@ class FE_Gate(FE_Base): usr_sesh = self.encode_session(sesh) self.logger.debug(f"Created session with JWT {usr_sesh}") resp = RedirectResponse("/user/", 303) - resp.set_cookie("DIANA_SESH", usr_sesh) + resp.set_cookie("ARTEMIS_SESH", usr_sesh) return resp @@ -393,7 +394,7 @@ class FE_Gate(FE_Base): usr_sesh = self.encode_session(sesh) self.logger.debug(f"Created session with JWT {usr_sesh}") resp = RedirectResponse("/user/", 303) - resp.set_cookie("DIANA_SESH", usr_sesh) + resp.set_cookie("ARTEMIS_SESH", usr_sesh) return resp @@ -511,7 +512,7 @@ class FE_User(FE_Base): async def render_logout(self, request: Request): resp = RedirectResponse("/gate/", 303) - resp.delete_cookie("DIANA_SESH") + resp.delete_cookie("ARTEMIS_SESH") return resp async def edit_card(self, request: Request) -> RedirectResponse: @@ -895,7 +896,7 @@ class FE_Machine(FE_Base): arcade={} ), media_type="text/html; charset=utf-8") -cfg_dir = environ.get("DIANA_CFG_DIR", "config") +cfg_dir = environ.get("ARTEMIS_CFG_DIR", "config") cfg: CoreConfig = CoreConfig() if path.exists(f"{cfg_dir}/core.yaml"): cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml"))) diff --git a/titles/chuni/frontend.py b/titles/chuni/frontend.py index b0fa9bc..0dbefac 100644 --- a/titles/chuni/frontend.py +++ b/titles/chuni/frontend.py @@ -68,7 +68,7 @@ class ChuniFrontend(FE_Base): if usr_sesh.chunithm_version >= 0: encoded_sesh = self.encode_session(usr_sesh) - resp.set_cookie("DIANA_SESH", encoded_sesh) + resp.set_cookie("ARTEMIS_SESH", encoded_sesh) return resp else: @@ -240,7 +240,7 @@ class ChuniFrontend(FE_Base): encoded_sesh = self.encode_session(usr_sesh) self.logger.info(f"Created session with JWT {encoded_sesh}") resp = RedirectResponse("/game/chuni/", 303) - resp.set_cookie("DIANA_SESH", encoded_sesh) + resp.set_cookie("ARTEMIS_SESH", encoded_sesh) return resp else: return RedirectResponse("/gate/", 303) \ No newline at end of file diff --git a/titles/mai2/frontend.py b/titles/mai2/frontend.py index 6ce8828..635c2fa 100644 --- a/titles/mai2/frontend.py +++ b/titles/mai2/frontend.py @@ -67,8 +67,8 @@ class Mai2Frontend(FE_Base): if usr_sesh.maimai_version >= 0: encoded_sesh = self.encode_session(usr_sesh) - resp.delete_cookie("DIANA_SESH") - resp.set_cookie("DIANA_SESH", encoded_sesh) + resp.delete_cookie("ARTEMIS_SESH") + resp.set_cookie("ARTEMIS_SESH", encoded_sesh) return resp else: @@ -184,7 +184,7 @@ class Mai2Frontend(FE_Base): encoded_sesh = self.encode_session(usr_sesh) self.logger.info(f"Created session with JWT {encoded_sesh}") resp = RedirectResponse("/game/mai2/", 303) - resp.set_cookie("DIANA_SESH", encoded_sesh) + resp.set_cookie("ARTEMIS_SESH", encoded_sesh) return resp else: return RedirectResponse("/gate/", 303) \ No newline at end of file diff --git a/titles/ongeki/frontend.py b/titles/ongeki/frontend.py index 1cdec03..226f318 100644 --- a/titles/ongeki/frontend.py +++ b/titles/ongeki/frontend.py @@ -31,7 +31,8 @@ class OngekiFrontend(FE_Base): def get_routes(self) -> List[Route]: return [ - Route("/", self.render_GET) + Route("/", self.render_GET), + Route("/version.change", self.render_POST, methods=['POST']) ] async def render_GET(self, request: Request) -> bytes: @@ -69,29 +70,34 @@ class OngekiFrontend(FE_Base): return RedirectResponse("/gate/", 303) async def render_POST(self, request: Request): - uri = request.uri.decode() + uri = request.url.path + frm = await request.form() usr_sesh = self.validate_session(request) if not usr_sesh: usr_sesh = UserSession() if usr_sesh.user_id > 0: if uri == "/game/ongeki/rival.add": - rival_id = request.args[b"rivalUserId"][0].decode() + rival_id = frm.get("rivalUserId") await self.data.profile.put_rival(usr_sesh.user_id, rival_id) # self.logger.info(f"{usr_sesh.user_id} added a rival") return RedirectResponse(b"/game/ongeki/", 303) elif uri == "/game/ongeki/rival.delete": - rival_id = request.args[b"rivalUserId"][0].decode() + rival_id = frm.get("rivalUserId") await self.data.profile.delete_rival(usr_sesh.user_id, rival_id) # self.logger.info(f"{response}") return RedirectResponse(b"/game/ongeki/", 303) elif uri == "/game/ongeki/version.change": - ongeki_version=request.args[b"version"][0].decode() + ongeki_version=frm.get("version") if(ongeki_version.isdigit()): usr_sesh.ongeki_version=int(ongeki_version) - return RedirectResponse("/game/ongeki/", 303) + enc = self.encode_session(usr_sesh) + resp = RedirectResponse("/game/ongeki/", 303) + resp.delete_cookie('ARTEMIS_SESH') + resp.set_cookie('ARTEMIS_SESH', enc) + return resp else: Response("Something went wrong", status_code=500) From e06e316b7d7670772bb6737c6d34db57f3ea4449 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Tue, 11 Jun 2024 10:30:57 -0400 Subject: [PATCH 15/25] pokken: fix achievement flags --- titles/pokken/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/pokken/base.py b/titles/pokken/base.py index 562f3b9..c50654a 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -339,7 +339,7 @@ class PokkenBase: for ach_flg in req.achievement_flag: ach_flgs.append(ach_flg) - await self.data.profile.update_profile_tutorial_flags(user_id, ach_flg) + await self.data.profile.update_profile_achievement_flags(user_id, ach_flg) for evt_flg in req.event_achievement_flag: evt_flgs.append(evt_flg) From 3fd65da7fd8c0031008dd429fe5c0b63df1b8035 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Tue, 11 Jun 2024 12:25:45 -0400 Subject: [PATCH 16/25] pokken: backport changes from Diana --- titles/pokken/base.py | 159 +++++++++++++++++++++----------- titles/pokken/schema/profile.py | 98 ++++++++++++-------- 2 files changed, 161 insertions(+), 96 deletions(-) diff --git a/titles/pokken/base.py b/titles/pokken/base.py index c50654a..e22ad74 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -1,9 +1,7 @@ -from datetime import datetime, timedelta +from datetime import datetime import json, logging from typing import Any, Dict, List -import random -from core.data import Data from core import CoreConfig from .config import PokkenConfig from .proto import jackal_pb2 @@ -18,7 +16,6 @@ class PokkenBase: self.version = 0 self.logger = logging.getLogger("pokken") self.data = PokkenData(core_cfg) - self.SUPPORT_SET_NONE = 4294967295 async def handle_noop(self, request: Any) -> bytes: res = jackal_pb2.Response() @@ -38,7 +35,30 @@ class PokkenBase: res = jackal_pb2.Response() res.result = 1 res.type = jackal_pb2.MessageType.REGISTER_PCB - self.logger.info(f"Register PCB {request.register_pcb.pcb_id}") + pcbid = request.register_pcb.pcb_id + if not pcbid.isdigit() or len(pcbid) != 12 or \ + not pcbid.startswith(f"{PokkenConstants.SERIAL_IDENT[0]}{PokkenConstants.SERIAL_REGIONS[0]}{PokkenConstants.SERIAL_ROLES[0]}{PokkenConstants.SERIAL_CAB_IDENTS[0]}"): + self.logger.warn(f"Bad PCBID {pcbid}") + res.result = 0 + return res + + netid = PokkenConstants.NETID_PREFIX[0] + pcbid[5:] + + self.logger.info(f"Register PCB {pcbid} (netID {netid})") + + minfo = await self.data.arcade.get_machine(netid) + + if not minfo and not self.core_cfg.server.allow_unregistered_serials: + self.logger.warn(f"netID {netid} does not belong to any shop!") + res.result = 0 + return res + + elif not minfo: + self.logger.warn(f"Orphaned netID {netid} allowed to connect") + locid = 0 + + else: + locid = minfo['arcade'] regist_pcb = jackal_pb2.RegisterPcbResponseData() regist_pcb.server_time = int(datetime.now().timestamp()) @@ -46,7 +66,7 @@ class PokkenBase: "MatchingServer": { "host": f"https://{self.game_cfg.server.hostname}", "port": self.game_cfg.ports.game, - "url": "/SDAK/100/matching", + "url": "/pokken/matching", }, "StunServer": { "addr": self.game_cfg.server.stun_server_host, @@ -56,12 +76,12 @@ class PokkenBase: "addr": self.game_cfg.server.stun_server_host, "port": self.game_cfg.server.stun_server_port, }, - "AdmissionUrl": f"ws://{self.game_cfg.server.hostname}:{self.game_cfg.ports.admission}", - "locationId": 123, # FIXME: Get arcade's ID from the database - "logfilename": "JackalMatchingLibrary.log", - "biwalogfilename": "./biwa.log", + "AdmissionUrl": f"ws://{self.game_cfg.server.hostname}:{self.game_cfg.ports.admission}/pokken/admission", + "locationId": locid, + "logfilename": "J:\\JackalMatchingLibrary.log", + "biwalogfilename": "J:\\biwa_log.log", } - regist_pcb.bnp_baseuri = f"{self.core_cfg.server.hostname}/bna" + regist_pcb.bnp_baseuri = f"{self.core_cfg.mucha.hostname}/bna" regist_pcb.biwa_setting = json.dumps(biwa_setting) res.register_pcb.CopyFrom(regist_pcb) @@ -95,12 +115,11 @@ class PokkenBase: res.type = jackal_pb2.MessageType.LOAD_CLIENT_SETTINGS settings = jackal_pb2.LoadClientSettingsResponseData() - # TODO: Make configurable settings.money_magnification = 1 settings.continue_bonus_exp = 100 settings.continue_fight_money = 100 settings.event_bonus_exp = 100 - settings.level_cap = 999 + settings.level_cap = 100 settings.op_movie_flag = 0xFFFFFFFF settings.lucky_bonus_rate = 1 settings.fail_support_num = 10 @@ -132,9 +151,13 @@ class PokkenBase: res.type = jackal_pb2.MessageType.LOAD_USER access_code = request.load_user.access_code load_usr = jackal_pb2.LoadUserResponseData() - user_id = await self.data.card.get_user_id_from_card(access_code) + load_usr.load_hash = 1 + load_usr.access_code = access_code + load_usr.precedent_release_flag = 0xFFFFFFFF + load_usr.cardlock_status = False + card = await self.data.card.get_card_by_access_code(access_code) - if user_id is None and self.game_cfg.server.auto_register: + if card is None and self.game_cfg.server.auto_register: user_id = await self.data.user.create_user() card_id = await self.data.card.create_card(user_id, access_code) @@ -142,41 +165,27 @@ class PokkenBase: f"Register new card {access_code} (UserId {user_id}, CardId {card_id})" ) - elif user_id is None: + elif card is None: self.logger.info(f"Registration of card {access_code} blocked!") res.load_user.CopyFrom(load_usr) return res.SerializeToString() + + else: + user_id = card['user'] + card_id = card['id'] """ - TODO: Add repeated values - tutorial_progress_flag - rankmatch_progress + TODO: Unlock all supports? Probably support_pokemon_list - support_set_1 - support_set_2 - support_set_3 - aid_skill_list - achievement_flag - event_achievement_flag - event_achievement_param """ profile = await self.data.profile.get_profile(user_id) load_usr.commidserv_result = 1 - load_usr.load_hash = 1 - load_usr.cardlock_status = False load_usr.banapass_id = user_id - load_usr.access_code = access_code - load_usr.precedent_release_flag = 0xFFFFFFFF - if profile is None: + if profile is None or profile['trainer_name'] is None: profile_id = await self.data.profile.create_profile(user_id) profile_dict = {"id": profile_id, "user": user_id} pokemon_data = [] - tutorial_progress = [] - rankmatch_progress = [] - achievement_flag = [] - event_achievement_flag = [] - event_achievement_param = [] load_usr.new_card_flag = True else: @@ -185,11 +194,6 @@ class PokkenBase: f"Card-in user {user_id} (Trainer name {profile_dict.get('trainer_name', '')})" ) pokemon_data = await self.data.profile.get_all_pokemon_data(user_id) - tutorial_progress = [] - rankmatch_progress = [] - achievement_flag = [] - event_achievement_flag = [] - event_achievement_param = [] load_usr.new_card_flag = False load_usr.navi_newbie_flag = profile_dict.get("navi_newbie_flag", True) @@ -201,9 +205,9 @@ class PokkenBase: load_usr.trainer_name = profile_dict.get( "trainer_name", f"Newb{str(user_id).zfill(4)}" ) - load_usr.trainer_rank_point = profile_dict.get("trainer_rank_point", 0) - load_usr.wallet = profile_dict.get("wallet", 0) - load_usr.fight_money = profile_dict.get("fight_money", 0) + load_usr.trainer_rank_point = profile_dict.get("trainer_rank_point", 0) # determines rank + load_usr.wallet = profile_dict.get("wallet", 0) # pg count + load_usr.fight_money = profile_dict.get("fight_money", 0) # ? load_usr.score_point = profile_dict.get("score_point", 0) load_usr.grade_max_num = profile_dict.get("grade_max_num", 0) load_usr.extra_counter = profile_dict.get("extra_counter", 0) @@ -218,18 +222,18 @@ class PokkenBase: load_usr.rank_event = profile_dict.get("rank_event", 0) load_usr.awake_num = profile_dict.get("awake_num", 0) load_usr.use_support_num = profile_dict.get("use_support_num", 0) - load_usr.rankmatch_flag = profile_dict.get("rankmatch_flag", 0) + load_usr.rankmatch_flag = profile_dict.get("rankmatch_flag", 0) # flags that next rank match will be rank up load_usr.rankmatch_max = profile_dict.get("rankmatch_max", 0) load_usr.rankmatch_success = profile_dict.get("rankmatch_success", 0) load_usr.beat_num = profile_dict.get("beat_num", 0) - load_usr.title_text_id = profile_dict.get("title_text_id", 0) - load_usr.title_plate_id = profile_dict.get("title_plate_id", 0) - load_usr.title_decoration_id = profile_dict.get("title_decoration_id", 0) + load_usr.title_text_id = profile_dict.get("title_text_id", 2) + load_usr.title_plate_id = profile_dict.get("title_plate_id", 1) + load_usr.title_decoration_id = profile_dict.get("title_decoration_id", 1) load_usr.navi_trainer = profile_dict.get("navi_trainer", 0) load_usr.navi_version_id = profile_dict.get("navi_version_id", 0) load_usr.aid_skill = profile_dict.get("aid_skill", 0) - load_usr.comment_text_id = profile_dict.get("comment_text_id", 0) - load_usr.comment_word_id = profile_dict.get("comment_word_id", 0) + load_usr.comment_text_id = profile_dict.get("comment_text_id", 1) + load_usr.comment_word_id = profile_dict.get("comment_word_id", 1) load_usr.latest_use_pokemon = profile_dict.get("latest_use_pokemon", 0) load_usr.ex_ko_num = profile_dict.get("ex_ko_num", 0) load_usr.wko_num = profile_dict.get("wko_num", 0) @@ -237,11 +241,11 @@ class PokkenBase: load_usr.cool_ko_num = profile_dict.get("cool_ko_num", 0) load_usr.perfect_ko_num = profile_dict.get("perfect_ko_num", 0) load_usr.record_flag = profile_dict.get("record_flag", 0) - load_usr.site_register_status = profile_dict.get("site_register_status", 0) + load_usr.site_register_status = profile_dict.get("site_register_status", 1) load_usr.continue_num = profile_dict.get("continue_num", 0) load_usr.avatar_body = profile_dict.get("avatar_body", 0) - load_usr.avatar_gender = profile_dict.get("avatar_gender", 0) + load_usr.avatar_gender = profile_dict.get("avatar_gender", 1) load_usr.avatar_background = profile_dict.get("avatar_background", 0) load_usr.avatar_head = profile_dict.get("avatar_head", 0) load_usr.avatar_battleglass = profile_dict.get("avatar_battleglass", 0) @@ -283,6 +287,31 @@ class PokkenBase: pkm.bp_point_sp = pkmn_d.get('bp_point_sp', 0) load_usr.pokemon_data.append(pkm) + + for x in profile_dict.get("tutorial_progress_flag", []): + load_usr.tutorial_progress_flag.append(x) + + for x in profile_dict.get("achievement_flag", []): + load_usr.achievement_flag.append(x) + + for x in profile_dict.get("aid_skill_list", []): + load_usr.aid_skill_list.append(x) + + for x in profile_dict.get("rankmatch_progress", []): + load_usr.rankmatch_progress.append(x) + + for x in profile_dict.get("event_achievement_flag", []): + load_usr.event_achievement_flag.append(x) + + for x in profile_dict.get("event_achievement_param", []): + load_usr.event_achievement_param.append(x) + + load_usr.support_set_1.append(profile_dict.get("support_set_1_1", 4294967295)) + load_usr.support_set_1.append(profile_dict.get("support_set_1_2", 4294967295)) + load_usr.support_set_2.append(profile_dict.get("support_set_2_1", 4294967295)) + load_usr.support_set_2.append(profile_dict.get("support_set_2_2", 4294967295)) + load_usr.support_set_3.append(profile_dict.get("support_set_3_1", 4294967295)) + load_usr.support_set_3.append(profile_dict.get("support_set_3_2", 4294967295)) res.load_user.CopyFrom(load_usr) return res.SerializeToString() @@ -300,6 +329,8 @@ class PokkenBase: req = request.save_user user_id = req.banapass_id + + self.logger.info(f"Save user data for {user_id}") tut_flgs: List[int] = [] ach_flgs: List[int] = [] @@ -339,7 +370,7 @@ class PokkenBase: for ach_flg in req.achievement_flag: ach_flgs.append(ach_flg) - await self.data.profile.update_profile_achievement_flags(user_id, ach_flg) + await self.data.profile.update_profile_achievement_flags(user_id, ach_flgs) for evt_flg in req.event_achievement_flag: evt_flgs.append(evt_flg) @@ -353,18 +384,23 @@ class PokkenBase: await self.data.item.add_reward(user_id, reward.get_category_id, reward.get_content_id, reward.get_type_id) await self.data.profile.add_profile_points(user_id, get_rank_pts, get_money, get_score_pts, grade_max) + + # Inconsistant underscore use AND a typo?? + await self.data.profile.update_rankmatch_data(user_id, req.rankmatch_flag, req.rank_match_max, req.rank_match_success, req.rank_match_process) await self.data.profile.update_support_team(user_id, 1, req.support_set_1[0], req.support_set_1[1]) await self.data.profile.update_support_team(user_id, 2, req.support_set_2[0], req.support_set_2[1]) await self.data.profile.update_support_team(user_id, 3, req.support_set_3[0], req.support_set_3[1]) await self.data.profile.put_pokemon(user_id, mon.char_id, mon.illustration_book_no, mon.bp_point_atk, mon.bp_point_res, mon.bp_point_def, mon.bp_point_sp) - await self.data.profile.add_pokemon_xp(user_id, mon.char_id, mon.get_pokemon_exp) + await self.data.profile.add_pokemon_xp(user_id, mon.illustration_book_no, mon.get_pokemon_exp) + await self.data.profile.set_latest_mon(user_id, mon.illustration_book_no) for x in range(len(battle.play_mode)): + self.logger.info(f"Save {PokkenConstants.BATTLE_TYPE(battle.play_mode[x]).name} battle {PokkenConstants.BATTLE_RESULT(battle.result[x]).name} for {user_id} with mon {mon.illustration_book_no}") await self.data.profile.put_pokemon_battle_result( user_id, - mon.char_id, + mon.illustration_book_no, PokkenConstants.BATTLE_TYPE(battle.play_mode[x]), PokkenConstants.BATTLE_RESULT(battle.result[x]) ) @@ -391,7 +427,6 @@ class PokkenBase: last_evt ) - return res.SerializeToString() async def handle_save_ingame_log(self, data: jackal_pb2.Request) -> bytes: @@ -419,6 +454,13 @@ class PokkenBase: async def handle_matching_is_matching( self, data: Dict = {}, client_ip: str = "127.0.0.1" ) -> Dict: + """ + "sessionId":"12345678", + "A":{ + "pcb_id": data["data"]["must"]["pcb_id"], + "gip": client_ip + }, + """ return { "data": { "sessionId":"12345678", @@ -435,6 +477,11 @@ class PokkenBase: ) -> Dict: return {} + async def handle_matching_obtain_matching( + self, data: Dict = {}, client_ip: str = "127.0.0.1" + ) -> Dict: + return {} + async def handle_admission_noop(self, data: Dict, req_ip: str = "127.0.0.1") -> Dict: return {} diff --git a/titles/pokken/schema/profile.py b/titles/pokken/schema/profile.py index b7237de..38b93e7 100644 --- a/titles/pokken/schema/profile.py +++ b/titles/pokken/schema/profile.py @@ -1,11 +1,11 @@ from typing import Optional, Dict, List, Union from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case -from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON +from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, INTEGER from sqlalchemy.schema import ForeignKey from sqlalchemy.sql import func, select, update, delete from sqlalchemy.sql.functions import coalesce from sqlalchemy.engine import Row -from sqlalchemy.dialects.mysql import insert +from sqlalchemy.dialects.postgresql import insert from core.data.schema import BaseData, metadata from ..const import PokkenConstants @@ -16,13 +16,8 @@ profile = Table( "pokken_profile", metadata, Column("id", Integer, primary_key=True, nullable=False), - Column( - "user", - ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), - nullable=False, - unique=True, - ), - Column("trainer_name", String(16)), # optional + Column("user", Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False, unique=True), + Column("trainer_name", String(14)), # optional Column("home_region_code", Integer), Column("home_loc_name", String(255)), Column("pref_code", Integer), @@ -105,20 +100,15 @@ profile = Table( Column("battle_num_vs_cpu", Integer), # 2 Column("win_cpu", Integer), Column("battle_num_tutorial", Integer), # 1? - mysql_charset="utf8mb4", ) pokemon_data = Table( - "pokken_pokemon_data", + "pokken_pokemon", metadata, Column("id", Integer, primary_key=True, nullable=False), - Column( - "user", - ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), - nullable=False, - ), - Column("char_id", Integer, nullable=False), - Column("illustration_book_no", Integer), + Column("user", Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), + Column("char_id", Integer), + Column("illustration_book_no", Integer, nullable=False), # This is the fucking pokedex number???? Column("pokemon_exp", Integer), Column("battle_num_vs_wan", Integer), # 4? Column("win_vs_wan", Integer), @@ -132,8 +122,7 @@ pokemon_data = Table( Column("bp_point_res", Integer), Column("bp_point_def", Integer), Column("bp_point_sp", Integer), - UniqueConstraint("user", "char_id", name="pokken_pokemon_data_uk"), - mysql_charset="utf8mb4", + UniqueConstraint("user", "illustration_book_no", name="pokken_pokemon_uk"), ) @@ -157,8 +146,8 @@ class PokkenProfileData(BaseData): return result.lastrowid async def set_profile_name(self, user_id: int, new_name: str, gender: Union[int, None] = None) -> None: - sql = update(profile).where(profile.c.user == user_id).values( - trainer_name=new_name, + sql = profile.update(profile.c.user == user_id).values( + trainer_name=new_name if new_name else profile.c.trainer_name, avatar_gender=gender if gender is not None else profile.c.avatar_gender ) result = await self.execute(sql) @@ -179,12 +168,12 @@ class PokkenProfileData(BaseData): aid_skill: int, last_evt: int ) -> None: - sql = update(profile).where(profile.c.user == user_id).values( + sql = profile.update(profile.c.user == user_id).values( extra_counter=extra_counter, event_reward_get_flag=evt_reward_get_flg, - total_play_days=total_play_days, - awake_num=awake_num, - use_support_num=use_support_ct, + total_play_days=coalesce(profile.c.total_play_days, 0) + total_play_days, + awake_num=coalesce(profile.c.awake_num, 0) + awake_num, + use_support_num=coalesce(profile.c.use_support_num, 0) + use_support_ct, beat_num=beat_num, aid_skill=aid_skill, last_play_event_id=last_evt @@ -195,7 +184,7 @@ class PokkenProfileData(BaseData): self.logger.error(f"Failed to put extra data for user {user_id}") async def update_profile_tutorial_flags(self, user_id: int, tutorial_flags: List) -> None: - sql = update(profile).where(profile.c.user == user_id).values( + sql = profile.update(profile.c.user == user_id).values( tutorial_progress_flag=tutorial_flags, ) result = await self.execute(sql) @@ -205,7 +194,7 @@ class PokkenProfileData(BaseData): ) async def update_profile_achievement_flags(self, user_id: int, achievement_flags: List) -> None: - sql = update(profile).where(profile.c.user == user_id).values( + sql = profile.update(profile.c.user == user_id).values( achievement_flag=achievement_flags, ) result = await self.execute(sql) @@ -215,7 +204,7 @@ class PokkenProfileData(BaseData): ) async def update_profile_event(self, user_id: int, event_state: List, event_flags: List[int], event_param: List[int], last_evt: int = None) -> None: - sql = update(profile).where(profile.c.user == user_id).values( + sql = profile.update(profile.c.user == user_id).values( event_state=event_state, event_achievement_flag=event_flags, event_achievement_param=event_param, @@ -230,12 +219,16 @@ class PokkenProfileData(BaseData): async def add_profile_points( self, user_id: int, rank_pts: int, money: int, score_pts: int, grade_max: int ) -> None: - sql = update(profile).where(profile.c.user == user_id).values( - trainer_rank_point = profile.c.trainer_rank_point + rank_pts, - fight_money = profile.c.fight_money + money, - score_point = profile.c.score_point + score_pts, + sql = profile.update(profile.c.user == user_id).values( + trainer_rank_point = coalesce(profile.c.trainer_rank_point, 0) + rank_pts, + wallet = coalesce(profile.c.wallet, 0) + money, + score_point = coalesce(profile.c.score_point, 0) + score_pts, grade_max_num = grade_max ) + + result = await self.execute(sql) + if result is None: + return None async def get_profile(self, user_id: int) -> Optional[Row]: sql = profile.select(profile.c.user == user_id) @@ -248,7 +241,7 @@ class PokkenProfileData(BaseData): self, user_id: int, pokemon_id: int, - illust_no: int, + pokedex_number: int, atk: int, res: int, defe: int, @@ -257,7 +250,7 @@ class PokkenProfileData(BaseData): sql = insert(pokemon_data).values( user=user_id, char_id=pokemon_id, - illustration_book_no=illust_no, + illustration_book_no=pokedex_number, pokemon_exp=0, battle_num_vs_wan=0, win_vs_wan=0, @@ -274,7 +267,7 @@ class PokkenProfileData(BaseData): ) conflict = sql.on_duplicate_key_update( - illustration_book_no=illust_no, + illustration_book_no=pokedex_number, bp_point_atk=pokemon_data.c.bp_point_atk + atk, bp_point_res=pokemon_data.c.bp_point_res + res, bp_point_def=pokemon_data.c.bp_point_def + defe, @@ -293,7 +286,7 @@ class PokkenProfileData(BaseData): pokemon_id: int, xp: int ) -> None: - sql = update(pokemon_data).where(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values( + sql = pokemon_data.update(and_(pokemon_data.c.user==user_id, pokemon_data.c.illustration_book_no==pokemon_id)).values( pokemon_exp=coalesce(pokemon_data.c.pokemon_exp, 0) + xp ) @@ -315,6 +308,14 @@ class PokkenProfileData(BaseData): return None return result.fetchall() + async def set_latest_mon(self, user_id: int, pokedex_no: int) -> None: + sql = profile.update(profile.c.user == user_id).values( + latest_use_pokemon=pokedex_no + ) + result = await self.execute(sql) + if result is None: + self.logger.warning(f"Failed to update user {user_id}'s last used pokemon {pokedex_no}") + async def put_pokemon_battle_result( self, user_id: int, pokemon_id: int, match_type: PokkenConstants.BATTLE_TYPE, match_result: PokkenConstants.BATTLE_RESULT ) -> None: @@ -322,7 +323,7 @@ class PokkenProfileData(BaseData): Records the match stats (type and win/loss) for the pokemon and profile coalesce(pokemon_data.c.win_vs_wan, 0) """ - sql = update(pokemon_data).where(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values( + sql = pokemon_data.update(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values( battle_num_tutorial=coalesce(pokemon_data.c.battle_num_tutorial, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else coalesce(pokemon_data.c.battle_num_tutorial, 0), battle_all_num_tutorial=coalesce(pokemon_data.c.battle_all_num_tutorial, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else coalesce(pokemon_data.c.battle_all_num_tutorial, 0), @@ -353,7 +354,7 @@ class PokkenProfileData(BaseData): """ Records profile stats """ - sql = update(profile).where(profile.c.user==user_id).values( + sql = profile.update(profile.c.user==user_id).values( ex_ko_num=coalesce(profile.c.ex_ko_num, 0) + exkos, wko_num=coalesce(profile.c.wko_num, 0) + wkos, timeup_win_num=coalesce(profile.c.timeup_win_num, 0) + timeout_wins, @@ -367,7 +368,12 @@ class PokkenProfileData(BaseData): self.logger.warning(f"Failed to update stats for user {user_id}") async def update_support_team(self, user_id: int, support_id: int, support1: int = None, support2: int = None) -> None: - sql = update(profile).where(profile.c.user==user_id).values( + if support1 == 4294967295: + support1 = None + + if support2 == 4294967295: + support2 = None + sql = profile.update(profile.c.user==user_id).values( support_set_1_1=support1 if support_id == 1 else profile.c.support_set_1_1, support_set_1_2=support2 if support_id == 1 else profile.c.support_set_1_2, support_set_2_1=support1 if support_id == 2 else profile.c.support_set_2_1, @@ -379,3 +385,15 @@ class PokkenProfileData(BaseData): result = await self.execute(sql) if result is None: self.logger.warning(f"Failed to update support team {support_id} for user {user_id}") + + async def update_rankmatch_data(self, user_id: int, flag: int, rm_max: Optional[int], success: Optional[int], progress: List[int]) -> None: + sql = profile.update(profile.c.user==user_id).values( + rankmatch_flag=flag, + rankmatch_max=rm_max, + rankmatch_progress=progress, + rankmatch_success=success, + ) + + result = await self.execute(sql) + if result is None: + self.logger.warning(f"Failed to update rankmatch data for user {user_id}") From fe4d978f7025c6565e54f8c838ad8b00f423e3db Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Tue, 11 Jun 2024 13:56:39 -0400 Subject: [PATCH 17/25] pokken: fix bnp_baseuri --- titles/pokken/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/pokken/base.py b/titles/pokken/base.py index e22ad74..5830f43 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -81,7 +81,7 @@ class PokkenBase: "logfilename": "J:\\JackalMatchingLibrary.log", "biwalogfilename": "J:\\biwa_log.log", } - regist_pcb.bnp_baseuri = f"{self.core_cfg.mucha.hostname}/bna" + regist_pcb.bnp_baseuri = f"{self.core_cfg.server.hostname}/bna" regist_pcb.biwa_setting = json.dumps(biwa_setting) res.register_pcb.CopyFrom(regist_pcb) From eaab3728c45d36be0b6b47107cb94ec0f84c6d7f Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Tue, 11 Jun 2024 14:00:29 -0400 Subject: [PATCH 18/25] pokken: add additional logging --- titles/pokken/base.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/titles/pokken/base.py b/titles/pokken/base.py index 5830f43..199d226 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -184,15 +184,14 @@ class PokkenBase: if profile is None or profile['trainer_name'] is None: profile_id = await self.data.profile.create_profile(user_id) + self.logger.info(f"Create new profile {profile_id} for user {user_id}") profile_dict = {"id": profile_id, "user": user_id} pokemon_data = [] load_usr.new_card_flag = True else: profile_dict = {k: v for k, v in profile._asdict().items() if v is not None} - self.logger.info( - f"Card-in user {user_id} (Trainer name {profile_dict.get('trainer_name', '')})" - ) + self.logger.info(f"Card-in user {user_id} (Trainer name {profile_dict.get('trainer_name', '')})") pokemon_data = await self.data.profile.get_all_pokemon_data(user_id) load_usr.new_card_flag = False From e21568cfd96461506438fdbb24073d964d84167a Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Wed, 12 Jun 2024 23:24:45 -0400 Subject: [PATCH 19/25] pokken: fix profile loading fail --- core/utils.py | 2 +- titles/pokken/base.py | 2 +- titles/pokken/schema/profile.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/utils.py b/core/utils.py index a9d1705..24c174c 100644 --- a/core/utils.py +++ b/core/utils.py @@ -49,7 +49,7 @@ class Utils: def get_title_port_ssl(cls, cfg: CoreConfig): if cls.real_title_port_ssl is not None: return cls.real_title_port_ssl - cls.real_title_port_ssl = cfg.server.proxy_port_ssl if cfg.server.is_using_proxy and cfg.server.proxy_port_ssl else Utils.get_title_port(cfg) + cls.real_title_port_ssl = cfg.server.proxy_port_ssl if cfg.server.is_using_proxy and cfg.server.proxy_port_ssl else 443 return cls.real_title_port_ssl diff --git a/titles/pokken/base.py b/titles/pokken/base.py index 199d226..de974a3 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -385,7 +385,7 @@ class PokkenBase: await self.data.profile.add_profile_points(user_id, get_rank_pts, get_money, get_score_pts, grade_max) # Inconsistant underscore use AND a typo?? - await self.data.profile.update_rankmatch_data(user_id, req.rankmatch_flag, req.rank_match_max, req.rank_match_success, req.rank_match_process) + #await self.data.profile.update_rankmatch_data(user_id, req.rankmatch_flag, req.rank_match_max, req.rank_match_success, req.rank_match_process) await self.data.profile.update_support_team(user_id, 1, req.support_set_1[0], req.support_set_1[1]) await self.data.profile.update_support_team(user_id, 2, req.support_set_2[0], req.support_set_2[1]) diff --git a/titles/pokken/schema/profile.py b/titles/pokken/schema/profile.py index 38b93e7..f66ebb6 100644 --- a/titles/pokken/schema/profile.py +++ b/titles/pokken/schema/profile.py @@ -5,7 +5,7 @@ from sqlalchemy.schema import ForeignKey from sqlalchemy.sql import func, select, update, delete from sqlalchemy.sql.functions import coalesce from sqlalchemy.engine import Row -from sqlalchemy.dialects.postgresql import insert +from sqlalchemy.dialects.mysql import insert from core.data.schema import BaseData, metadata from ..const import PokkenConstants @@ -103,7 +103,7 @@ profile = Table( ) pokemon_data = Table( - "pokken_pokemon", + "pokken_pokemon_data", metadata, Column("id", Integer, primary_key=True, nullable=False), Column("user", Integer, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False), @@ -295,7 +295,7 @@ class PokkenProfileData(BaseData): self.logger.warning(f"Failed to add {xp} XP to pokemon ID {pokemon_id} for user {user_id}") async def get_pokemon_data(self, user_id: int, pokemon_id: int) -> Optional[Row]: - sql = pokemon_data.select(and_(pokemon_data.c.user == user_id, pokemon_data.c.char_id == pokemon_id)) + sql = pokemon_data.select(and_(pokemon_data.c.user == user_id, pokemon_data.c.illustration_book_no == pokemon_id)) result = await self.execute(sql) if result is None: return None @@ -323,7 +323,7 @@ class PokkenProfileData(BaseData): Records the match stats (type and win/loss) for the pokemon and profile coalesce(pokemon_data.c.win_vs_wan, 0) """ - sql = pokemon_data.update(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values( + sql = pokemon_data.update(and_(pokemon_data.c.user==user_id, pokemon_data.c.illustration_book_no==pokemon_id)).values( battle_num_tutorial=coalesce(pokemon_data.c.battle_num_tutorial, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else coalesce(pokemon_data.c.battle_num_tutorial, 0), battle_all_num_tutorial=coalesce(pokemon_data.c.battle_all_num_tutorial, 0) + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else coalesce(pokemon_data.c.battle_all_num_tutorial, 0), From ee7d5c4e217dae3c92d56ce6972c9a0c1b6b2f22 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 13 Jun 2024 14:14:07 -0400 Subject: [PATCH 20/25] pokken: readd mysql_charset to profile and pokemon data --- titles/pokken/data/fighters.json | 140 +++++++++++++++++++++++++++++++ titles/pokken/data/support.json | 3 + titles/pokken/schema/profile.py | 4 +- 3 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 titles/pokken/data/fighters.json create mode 100644 titles/pokken/data/support.json diff --git a/titles/pokken/data/fighters.json b/titles/pokken/data/fighters.json new file mode 100644 index 0000000..cc22c32 --- /dev/null +++ b/titles/pokken/data/fighters.json @@ -0,0 +1,140 @@ +{ + "448": { + "name_en": "Lucario", + "name_jp": "ルカリオ", + "type": 0, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/448.png" + }, + "25": { + "name_en": "Pikachu", + "name_jp": "ピカチュウ", + "type": 0, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png" + }, + "68": { + "name_en": "Machamp", + "name_jp": "カイリキー", + "type": 1, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/68.png" + }, + "282": { + "name_en": "Gardevoir", + "name_jp": "サーナイト", + "type": 2, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/282.png" + }, + "461": { + "name_en": "Weavile", + "name_jp": "マニューラ", + "type": 3, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/461.png" + }, + "245": { + "name_en": "Suicune", + "name_jp": "スイクン", + "type": 0, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/245.png" + }, + "6": { + "name_en": "Charizard", + "name_jp": "リザードン", + "type": 1, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/6.png" + }, + "94": { + "name_en": "Gengar", + "name_jp": "ゲンガー", + "type": 2, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/94.png" + }, + "257": { + "name_en": "Blaziken", + "name_jp": "バシャーモ", + "type": 0, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/257.png" + }, + "4029": { + "name_en": "Pikachu Libre", + "name_jp": "マスクド・ピカチュウ", + "type": 3, + "artwork": "https://archives.bulbagarden.net/media/upload/thumb/6/63/Pokk%C3%A9n_Pikachu_Libre.png/663px-Pokk%C3%A9n_Pikachu_Libre.png" + }, + "254": { + "name_en": "Sceptile", + "name_jp": "ジュカイン", + "type": 3, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/254.png" + }, + "609": { + "name_en": "Chandelure", + "name_jp": "シャンデラ", + "type": 1, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/609.png" + }, + "150": { + "name_en": "Mewtwo", + "name_jp": "ミュウツー", + "type": 0, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/150.png" + }, + "10150": { + "name_en": "Shadow Mewtwo", + "name_jp": "ダークミュウツー", + "type": 2, + "artwork": "https://archives.bulbagarden.net/media/upload/7/7a/Pokk%C3%A9n_Shadow_Mewtwo.png" + }, + "445": { + "name_en": "Garchomp", + "name_jp": "ガブリアス", + "type": 1, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/445.png" + }, + "654": { + "name_en": "Braixen", + "name_jp": "テールナー", + "type": 0, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/654.png" + }, + "491": { + "name_en": "Darkrai", + "name_jp": "ダークライ", + "type": 2, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/491.png" + }, + "212": { + "name_en": "Scizor", + "name_jp": "ハッサム", + "type": 1, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/212.png" + }, + "453": { + "name_en": "Croagunk", + "name_jp": "グレッグル", + "type": 3, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/453.png" + }, + "395": { + "name_en": "Empoleon", + "name_jp": "エンペルト", + "type": 0, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/395.png" + }, + "724": { + "name_en": "Decidueye", + "name_jp": "ジュナイパー", + "type": 0, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/724.png" + }, + "681": { + "name_en": "Aegislash", + "name_jp": "ギルガルド", + "type": 2, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/681.png" + }, + "9": { + "name_en": "Blastoise", + "name_jp": "カメックス", + "type": 1, + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/9.png" + } +} \ No newline at end of file diff --git a/titles/pokken/data/support.json b/titles/pokken/data/support.json new file mode 100644 index 0000000..a514dfe --- /dev/null +++ b/titles/pokken/data/support.json @@ -0,0 +1,3 @@ +{ + +} \ No newline at end of file diff --git a/titles/pokken/schema/profile.py b/titles/pokken/schema/profile.py index f66ebb6..2976ff7 100644 --- a/titles/pokken/schema/profile.py +++ b/titles/pokken/schema/profile.py @@ -61,7 +61,7 @@ profile = Table( Column("navi_trainer", Integer), Column("navi_version_id", Integer), Column("aid_skill_list", JSON), # Repeated, Integer - Column("aid_skill", Integer), + Column("aid_skill", Integer), # Cheer skill, 6 of them, unlocked by lucky bonus Column("comment_text_id", Integer), Column("comment_word_id", Integer), Column("latest_use_pokemon", Integer), @@ -100,6 +100,7 @@ profile = Table( Column("battle_num_vs_cpu", Integer), # 2 Column("win_cpu", Integer), Column("battle_num_tutorial", Integer), # 1? + mysql_charset="utf8mb4" ) pokemon_data = Table( @@ -123,6 +124,7 @@ pokemon_data = Table( Column("bp_point_def", Integer), Column("bp_point_sp", Integer), UniqueConstraint("user", "illustration_book_no", name="pokken_pokemon_uk"), + mysql_charset="utf8mb4" ) From a523c25d8421c983f72632ebc6a59f855fee665e Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 13 Jun 2024 18:06:16 -0400 Subject: [PATCH 21/25] pokken: finish filling out support list --- titles/pokken/data/support.json | 217 +++++++++++++++++++++++++++++++- 1 file changed, 216 insertions(+), 1 deletion(-) diff --git a/titles/pokken/data/support.json b/titles/pokken/data/support.json index a514dfe..ef9f079 100644 --- a/titles/pokken/data/support.json +++ b/titles/pokken/data/support.json @@ -1,3 +1,218 @@ { - + "587": { + "name_en": "Emolga", + "name_jp": "エモンガ", + "desc": "Uses Shock Wave to shock the opponent and temporarily decrease its speed.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/587.png" + }, + "653": { + "name_en": "Fennekin", + "name_jp": "フォッコ", + "desc": "Uses Ember to surround itself with fire, creating a trap.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/653.png" + }, + "495": { + "name_en": "Snivy", + "name_jp": "ツタージャ", + "desc": "Uses Leaf Tornado to perform an anti-air attack and send the opponent flying.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/495.png" + }, + "131": { + "name_en": "Lapras", + "name_jp": "ラプラス", + "desc": "Uses Surf as it enters the stage, damaging the enemy with a wave of water.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/131.png" + }, + "657": { + "name_en": "Frogadier", + "name_jp": "ゲコガシラ", + "desc": "Uses Water Pulse to attack from a distance by firing water bullets. Effective when striking from long distance.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/657.png" + }, + "133": { + "name_en": "Eevee", + "name_jp": "イーブイ", + "desc": "Uses Helping Hand to heal the user and temporarily increase their attack power.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/133.png" + }, + "385": { + "name_en": "Jirachi", + "name_jp": "ジラーチ", + "desc": "Uses Wish to restore the Synergy Gauge and temporarily strengthen the user's attack power during Synergy Burst.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/385.png" + }, + "547": { + "name_en": "Whimsicott", + "name_jp": "エルフーン", + "desc": "Uses Substitute to render attacks from opponents useless and heal the user.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/547.png" + }, + "38": { + "name_en": "Ninetales", + "name_jp": "キュウコン", + "desc": "Uses Will-O-Wisp to send small flames in front of the user. Enemy's attack power decreased temporarily when contacted.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/38.png" + }, + "429": { + "name_en": "Mismagius", + "name_jp": "ムウマージ", + "desc": "Uses Ominous Wind to attack the opponent and temporarily increase the user's attack power.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/429.png" + }, + "83": { + "name_en": "Farfetch'd", + "name_jp": "カモネギ", + "desc": "Uses Fury Cutter to perform a flurry of attacks toward the opponent.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/83.png" + }, + "101": { + "name_en": "Electrode", + "name_jp": "マルマイン", + "desc": "Uses Explosion to counter an opponent's attack upon defending.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/101.png" + }, + "479": { + "name_en": "Rotom", + "name_jp": "ロトム", + "desc": "Uses Thunder Shock to target enemies in the air and temporarily decrease their speed.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/479.png" + }, + "468": { + "name_en": "Togekiss", + "name_jp": "トゲキッス", + "desc": "Uses Tailwind to temporarily increase the user's speed and recover some health.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/468.png" + }, + "149": { + "name_en": "Dragonite", + "name_jp": "カイリュー", + "desc": "Uses Draco Meteor to attack multiple times over a wide area.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/149.png" + }, + "494": { + "name_en": "Victini", + "name_jp": "ビクティニ", + "desc": "Uses V-create to temporarily make the user's attacks critical hits, restores some of the user's health, and increases the user's Synergy Gauge. Unlike other Enhance Pokémon, Victini can actually damage the foe if they're above it when flying off the screen.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/494.png" + }, + "453": { + "name_en": "Croagunk", + "name_jp": "グレッグル", + "desc": "Uses Toxic to attack opponent and temporarily decrease its defense.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/453.png" + }, + "700": { + "name_en": "Sylveon", + "name_jp": "ニンフィア", + "desc": "Uses Reflect to heal user and temporarily increase their defense.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/700.png" + }, + "417": { + "name_en": "Pachirisu", + "name_jp": "パチリス", + "desc": "Uses Follow Me to eliminate long distance attacks. Effective when get in close.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/417.png" + }, + "129": { + "name_en": "Magikarp", + "name_jp": "コイキング", + "desc": "Uses Bounce to disrupt the enemy's attack when hit by an opponent. Effective for interrupting combos.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/129.png" + }, + "104": { + "name_en": "Cubone", + "name_jp": "カラカラ", + "desc": "Uses Bonemerang to attack from a distance and can pull an enemy in.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/104.png" + }, + "50": { + "name_en": "Diglett", + "name_jp": "ディグダ", + "desc": "Uses Dig to attack from below, making easy to aim for a combo.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/50.png" + }, + "82": { + "name_en": "Magneton", + "name_jp": "レアコイル", + "desc": "Uses Tri Attack to attack from a distance diagonally upward and inflict two random negative statuses.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/82.png" + }, + "195": { + "name_en": "Quagsire", + "name_jp": "ヌオー", + "desc": "Uses Mud Bomb to attack opponent on the ground, even when blocked.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/195.png" + }, + "196": { + "name_en": "Espeon", + "name_jp": "エーフィ", + "desc": "Uses Morning Sun to remove any statuses and recover health, with more health recovered with less time remaining in the round.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/196.png" + }, + "197": { + "name_en": "Umbreon", + "name_jp": "ブラッキー", + "desc": "Uses Snarl to absorb an opponent's Synergy Gauge and prevent them from performing any critical hits.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/197.png" + }, + "643": { + "name_en": "Reshiram", + "name_jp": "レシラム", + "desc": "Uses Blue Flare to attack straight forward with a powerful flame. In the DX version, it can only be called once per round.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/643.png" + }, + "488": { + "name_en": "Cresselia", + "name_jp": "クレセリア", + "desc": "Uses Lunar Dance to heal the user of any negative status, recovers health and Synergy Gauge, but can only be used once per round.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/488.png" + }, + "717": { + "name_en": "Yveltal", + "name_jp": "イベルタル", + "desc": "Uses Oblivion Wing to attack from the sky and seal off the opponent's Synergy Burst. In the DX version, it can only be called once per round.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/717.png" + }, + "381": { + "name_en": "Latios", + "name_jp": "ラティオス", + "desc": "Uses Luster Purge to place attacks around the enemy in order to restrict their movements. In the DX version, it can only be called once per round.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/381.png" + }, + "725": { + "name_en": "Litten", + "name_jp": "ニャビー", + "desc": "Uses Fire Fang to attack toward the enemy. Damage increases when the player's at lower HP.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/725.png" + }, + "728": { + "name_en": "Popplio", + "name_jp": "アシマリ", + "desc": "Uses Bubble Beam to temporarily increase attack and grant a double jump while in midair.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/728.png" + }, + "384": { + "name_en": "Mega Rayquaza", + "name_jp": "レックウザ", + "desc": "Uses Dragon Ascent to attack from a distance at tremendous speed. It also consumes the user's Synergy Gauge. It can only be called once per round.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/384.png" + }, + "778": { + "name_en": "Mimikyu", + "name_jp": "ミミッキュ", + "desc": "Uses Play Rough to attack continuously from behind and inflict double negative status.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/778.png" + }, + "151": { + "name_en": "Mew", + "name_jp": "ミュウ", + "desc": "Uses Miraculous Power to randomly increase the user's Synergy Gauge, temporarily makes the user's attacks critical hits, and/or gives the user additional random positive status.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/151.png" + }, + "251": { + "name_en": "Celebi", + "name_jp": "セレビィ", + "desc": "Uses Time Travel (Japanese: ときわたり Time Travel) to switch between Phases at almost any given moment, even when enemy guards an attack.", + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/251.png" + } } \ No newline at end of file From bf54969bc160609066685a25b24bc33e7617ffe6 Mon Sep 17 00:00:00 2001 From: Hay1tsme Date: Thu, 13 Jun 2024 18:15:38 -0400 Subject: [PATCH 22/25] pokken: fix some image links --- titles/pokken/data/fighters.json | 2 +- titles/pokken/data/support.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/titles/pokken/data/fighters.json b/titles/pokken/data/fighters.json index cc22c32..90681ec 100644 --- a/titles/pokken/data/fighters.json +++ b/titles/pokken/data/fighters.json @@ -57,7 +57,7 @@ "name_en": "Pikachu Libre", "name_jp": "マスクド・ピカチュウ", "type": 3, - "artwork": "https://archives.bulbagarden.net/media/upload/thumb/6/63/Pokk%C3%A9n_Pikachu_Libre.png/663px-Pokk%C3%A9n_Pikachu_Libre.png" + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/10084.png" }, "254": { "name_en": "Sceptile", diff --git a/titles/pokken/data/support.json b/titles/pokken/data/support.json index ef9f079..ef5368e 100644 --- a/titles/pokken/data/support.json +++ b/titles/pokken/data/support.json @@ -191,11 +191,11 @@ "desc": "Uses Bubble Beam to temporarily increase attack and grant a double jump while in midair.", "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/728.png" }, - "384": { + "10079": { "name_en": "Mega Rayquaza", "name_jp": "レックウザ", "desc": "Uses Dragon Ascent to attack from a distance at tremendous speed. It also consumes the user's Synergy Gauge. It can only be called once per round.", - "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/384.png" + "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/10079.png" }, "778": { "name_en": "Mimikyu", From 00224585bb8772046061a36d4c6fab6eab2ccde4 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Fri, 14 Jun 2024 00:06:39 -0400 Subject: [PATCH 23/25] pokken: fix pokemon_data uk --- .../3657efefc5a4_pokken_fix_pokemon_uk.py | 54 +++++++++++++++++++ titles/pokken/base.py | 12 ++--- titles/pokken/data/fighters.json | 2 +- 3 files changed, 61 insertions(+), 7 deletions(-) create mode 100644 core/data/alembic/versions/3657efefc5a4_pokken_fix_pokemon_uk.py diff --git a/core/data/alembic/versions/3657efefc5a4_pokken_fix_pokemon_uk.py b/core/data/alembic/versions/3657efefc5a4_pokken_fix_pokemon_uk.py new file mode 100644 index 0000000..2867fb7 --- /dev/null +++ b/core/data/alembic/versions/3657efefc5a4_pokken_fix_pokemon_uk.py @@ -0,0 +1,54 @@ +"""pokken_fix_pokemon_uk + +Revision ID: 3657efefc5a4 +Revises: 4a02e623e5e6 +Create Date: 2024-06-13 23:50:57.611998 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision = '3657efefc5a4' +down_revision = '4a02e623e5e6' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('pokken_pokemon_data', 'char_id', + existing_type=mysql.INTEGER(display_width=11), + nullable=True) + op.alter_column('pokken_pokemon_data', 'illustration_book_no', + existing_type=mysql.INTEGER(display_width=11), + nullable=False) + op.drop_constraint('pokken_pokemon_data_ibfk_1', table_name='pokken_pokemon_data', type_='foreignkey') + op.drop_index('pokken_pokemon_data_uk', table_name='pokken_pokemon_data') + op.create_unique_constraint('pokken_pokemon_uk', 'pokken_pokemon_data', ['user', 'illustration_book_no']) + op.create_foreign_key("pokken_pokemon_data_ibfk_1", "pokken_pokemon_data", "aime_user", ['user'], ['id']) + op.alter_column('pokken_profile', 'trainer_name', + existing_type=mysql.VARCHAR(length=16), + type_=sa.String(length=14), + existing_nullable=True) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.alter_column('pokken_profile', 'trainer_name', + existing_type=sa.String(length=14), + type_=mysql.VARCHAR(length=16), + existing_nullable=True) + op.drop_constraint('pokken_pokemon_data_ibfk_1', table_name='pokken_pokemon_data', type_='foreignkey') + op.drop_constraint('pokken_pokemon_uk', 'pokken_pokemon_data', type_='unique') + op.create_index('pokken_pokemon_data_uk', 'pokken_pokemon_data', ['user', 'char_id'], unique=True) + op.create_foreign_key("pokken_pokemon_data_ibfk_1", "pokken_pokemon_data", "aime_user", ['user'], ['id']) + op.alter_column('pokken_pokemon_data', 'illustration_book_no', + existing_type=mysql.INTEGER(display_width=11), + nullable=True) + op.alter_column('pokken_pokemon_data', 'char_id', + existing_type=mysql.INTEGER(display_width=11), + nullable=False) + # ### end Alembic commands ### diff --git a/titles/pokken/base.py b/titles/pokken/base.py index de974a3..fe11e99 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -305,12 +305,12 @@ class PokkenBase: for x in profile_dict.get("event_achievement_param", []): load_usr.event_achievement_param.append(x) - load_usr.support_set_1.append(profile_dict.get("support_set_1_1", 4294967295)) - load_usr.support_set_1.append(profile_dict.get("support_set_1_2", 4294967295)) - load_usr.support_set_2.append(profile_dict.get("support_set_2_1", 4294967295)) - load_usr.support_set_2.append(profile_dict.get("support_set_2_2", 4294967295)) - load_usr.support_set_3.append(profile_dict.get("support_set_3_1", 4294967295)) - load_usr.support_set_3.append(profile_dict.get("support_set_3_2", 4294967295)) + load_usr.support_set_1.append(profile_dict.get("support_set_1_1", 587)) + load_usr.support_set_1.append(profile_dict.get("support_set_1_2", 653)) + load_usr.support_set_2.append(profile_dict.get("support_set_2_1", 495)) + load_usr.support_set_2.append(profile_dict.get("support_set_2_2", 131)) + load_usr.support_set_3.append(profile_dict.get("support_set_3_1", 657)) + load_usr.support_set_3.append(profile_dict.get("support_set_3_2", 133)) res.load_user.CopyFrom(load_usr) return res.SerializeToString() diff --git a/titles/pokken/data/fighters.json b/titles/pokken/data/fighters.json index 90681ec..a5ba6f8 100644 --- a/titles/pokken/data/fighters.json +++ b/titles/pokken/data/fighters.json @@ -53,7 +53,7 @@ "type": 0, "artwork": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/257.png" }, - "4029": { + "10025": { "name_en": "Pikachu Libre", "name_jp": "マスクド・ピカチュウ", "type": 3, From 784ac544f070c8c620aa7c6523d585d5dd2e6ba3 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 16 Jun 2024 14:42:23 -0400 Subject: [PATCH 24/25] fix event log ordering, paging, add timestamp --- core/data/schema/base.py | 2 +- core/templates/sys/logs.jinja | 40 ++++++++++++++++++++++++----------- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/core/data/schema/base.py b/core/data/schema/base.py index e315964..d74198b 100644 --- a/core/data/schema/base.py +++ b/core/data/schema/base.py @@ -109,7 +109,7 @@ class BaseData: return result.lastrowid async def get_event_log(self, entries: int = 100) -> Optional[List[Row]]: - sql = event_log.select().limit(entries) + sql = event_log.select().order_by(event_log.c.id.desc()).limit(entries) result = await self.execute(sql) if result is None: diff --git a/core/templates/sys/logs.jinja b/core/templates/sys/logs.jinja index 3dfe531..9b7a987 100644 --- a/core/templates/sys/logs.jinja +++ b/core/templates/sys/logs.jinja @@ -6,6 +6,7 @@ Severity + Timestamp System Name User @@ -19,7 +20,7 @@ {% if events is not defined or events|length == 0 %} - No Events + No Events {% endif %} @@ -66,7 +67,11 @@ function update_tbl() { for (var i = 0; i < per_page; i++) { let off = (page * per_page) + i; - if (off >= TBL_DATA.length ) { + if (off >= TBL_DATA.length) { + if (page != 0) { + document.getElementById("btn_next").disabled = true; + document.getElementById("btn_prev").disabled = false; + } break; } @@ -99,56 +104,59 @@ function update_tbl() { row.classList.add("table-primary"); break; } + + var cell_ts = row.insertCell(1); + cell_ts.innerHTML = data.when_logged; - var cell_mod = row.insertCell(1); + var cell_mod = row.insertCell(2); cell_mod.innerHTML = data.system; - var cell_name = row.insertCell(2); + var cell_name = row.insertCell(3); cell_name.innerHTML = data.type; - var cell_usr = row.insertCell(3); + var cell_usr = row.insertCell(4); if (data.user == 'NONE') { cell_usr.innerHTML = "---"; } else { cell_usr.innerHTML = "" + data.user + ""; } - var cell_arcade = row.insertCell(4); + var cell_arcade = row.insertCell(5); if (data.arcade == 'NONE') { cell_arcade.innerHTML = "---"; } else { cell_arcade.innerHTML = "" + data.arcade + ""; } - var cell_machine = row.insertCell(5); + var cell_machine = row.insertCell(6); if (data.arcade == 'NONE') { cell_machine.innerHTML = "---"; } else { cell_machine.innerHTML = "" + data.machine + ""; } - var cell_game = row.insertCell(6); + var cell_game = row.insertCell(7); if (data.game == 'NONE') { cell_game.innerHTML = "---"; } else { cell_game.innerHTML = data.game; } - var cell_version = row.insertCell(7); + var cell_version = row.insertCell(8); if (data.version == 'NONE') { cell_version.innerHTML = "---"; } else { cell_version.innerHTML = data.version; } - var cell_msg = row.insertCell(8); + var cell_msg = row.insertCell(9); if (data.message == '') { cell_msg.innerHTML = "---"; } else { cell_msg.innerHTML = data.message; } - var cell_deets = row.insertCell(9); + var cell_deets = row.insertCell(10); if (data.details == '{}') { cell_deets.innerHTML = "---"; } else { @@ -160,9 +168,11 @@ function update_tbl() { function chg_page(num) { var max_page = TBL_DATA.length / per_page; + console.log(max_page); page = page + num; - if (page > max_page) { + + if (page > max_page && max_page >= 1) { page = max_page; document.getElementById("btn_next").disabled = true; document.getElementById("btn_prev").disabled = false; @@ -172,6 +182,12 @@ function chg_page(num) { document.getElementById("btn_next").disabled = false; document.getElementById("btn_prev").disabled = true; return; + } else if (page == 0) { + document.getElementById("btn_next").disabled = false; + document.getElementById("btn_prev").disabled = true; + } else { + document.getElementById("btn_next").disabled = false; + document.getElementById("btn_prev").disabled = false; } update_tbl(); From 68bf3843ec0515f9c956a62b9319ffe6667de939 Mon Sep 17 00:00:00 2001 From: Kevin Trocolli Date: Sun, 16 Jun 2024 14:45:41 -0400 Subject: [PATCH 25/25] pokken: fix default title plate id --- titles/pokken/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/titles/pokken/base.py b/titles/pokken/base.py index fe11e99..f864824 100644 --- a/titles/pokken/base.py +++ b/titles/pokken/base.py @@ -226,7 +226,7 @@ class PokkenBase: load_usr.rankmatch_success = profile_dict.get("rankmatch_success", 0) load_usr.beat_num = profile_dict.get("beat_num", 0) load_usr.title_text_id = profile_dict.get("title_text_id", 2) - load_usr.title_plate_id = profile_dict.get("title_plate_id", 1) + load_usr.title_plate_id = profile_dict.get("title_plate_id", 31) load_usr.title_decoration_id = profile_dict.get("title_decoration_id", 1) load_usr.navi_trainer = profile_dict.get("navi_trainer", 0) load_usr.navi_version_id = profile_dict.get("navi_version_id", 0)