forked from Hay1tsme/artemis
[mai2] add buddies plus support (#177)
Adds favorite music support (there's an option in the results screen to star a song), handlers for new methods and fixes upsert failures for `userFavoriteList`. The `UserIntimateApi` has been added but didn't seem to add any data during testing, and `CreateTokenApi`/`RemoveTokenApi` have also been added but I think they're only used during guest play. --- Tested on 1.45 with no errors/game crashes (see logs). Card Maker hasn't been tested as I don't have a setup to play with. Reviewed-on: Hay1tsme/artemis#177 Co-authored-by: ppc <albie@ppc.moe> Co-committed-by: ppc <albie@ppc.moe>
This commit is contained in:
28
core/data/alembic/versions/28443e2da5b8_mai2_buddies_plus.py
Normal file
28
core/data/alembic/versions/28443e2da5b8_mai2_buddies_plus.py
Normal file
@ -0,0 +1,28 @@
|
||||
"""mai2_buddies_plus
|
||||
|
||||
Revision ID: 28443e2da5b8
|
||||
Revises: 5ea73f89d982
|
||||
Create Date: 2024-09-15 20:44:02.351819
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '28443e2da5b8'
|
||||
down_revision = '5ea73f89d982'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('mai2_profile_detail', sa.Column('point', sa.Integer()))
|
||||
op.add_column('mai2_profile_detail', sa.Column('totalPoint', sa.Integer()))
|
||||
op.add_column('mai2_profile_detail', sa.Column('friendRegistSkip', sa.SmallInteger()))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('mai2_profile_detail', 'point')
|
||||
op.drop_column('mai2_profile_detail', 'totalPoint')
|
||||
op.drop_column('mai2_profile_detail', 'friendRegistSkip')
|
43
core/data/alembic/versions/54a84103b84e_mai2_intimacy.py
Normal file
43
core/data/alembic/versions/54a84103b84e_mai2_intimacy.py
Normal file
@ -0,0 +1,43 @@
|
||||
"""mai2_intimacy
|
||||
|
||||
Revision ID: 54a84103b84e
|
||||
Revises: bc91c1206dca
|
||||
Create Date: 2024-09-16 17:47:49.164546
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy import Column, Integer, UniqueConstraint
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '54a84103b84e'
|
||||
down_revision = 'bc91c1206dca'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
"mai2_user_intimate",
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", Integer, nullable=False),
|
||||
Column("partnerId", Integer, nullable=False),
|
||||
Column("intimateLevel", Integer, nullable=False),
|
||||
Column("intimateCountRewarded", Integer, nullable=False),
|
||||
UniqueConstraint("user", "partnerId", name="mai2_user_intimate_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
op.create_foreign_key(
|
||||
None,
|
||||
"mai2_user_intimate",
|
||||
"aime_user",
|
||||
["user"],
|
||||
["id"],
|
||||
ondelete="cascade",
|
||||
onupdate="cascade",
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_table("mai2_user_intimate")
|
@ -0,0 +1,24 @@
|
||||
"""mai2_favorite_song_ordering
|
||||
|
||||
Revision ID: bc91c1206dca
|
||||
Revises: 28443e2da5b8
|
||||
Create Date: 2024-09-16 14:24:56.714066
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'bc91c1206dca'
|
||||
down_revision = '28443e2da5b8'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.add_column('mai2_item_favorite_music', sa.Column('orderId', sa.Integer(), nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_column('mai2_item_favorite_music', 'orderId')
|
@ -218,6 +218,7 @@ Presents are items given to the user when they login, with a little animation (f
|
||||
| SDEZ | 19 | maimai DX FESTiVAL |
|
||||
| SDEZ | 20 | maimai DX FESTiVAL PLUS |
|
||||
| SDEZ | 21 | maimai DX BUDDiES |
|
||||
| SDEZ | 22 | maimai DX BUDDiES PLUS |
|
||||
|
||||
### Importer
|
||||
|
||||
|
@ -50,6 +50,7 @@ Games listed below have been tested and confirmed working. Only game versions ol
|
||||
+ FESTiVAL
|
||||
+ FESTiVAL PLUS
|
||||
+ BUDDiES
|
||||
+ BUDDiES PLUS
|
||||
|
||||
+ O.N.G.E.K.I.
|
||||
+ SUMMER
|
||||
|
@ -207,6 +207,7 @@ class CardMakerReader(BaseReader):
|
||||
"1.30": Mai2Constants.VER_MAIMAI_DX_FESTIVAL,
|
||||
"1.35": Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS,
|
||||
"1.40": Mai2Constants.VER_MAIMAI_DX_BUDDIES,
|
||||
"1.45": Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS
|
||||
}
|
||||
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
|
@ -922,7 +922,7 @@ class Mai2Base:
|
||||
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"]})
|
||||
id_list.append({"orderId": fav["orderId"] or 0, "id": fav["musicId"]})
|
||||
if len(id_list) >= 100: # Lazy but whatever
|
||||
break
|
||||
|
||||
|
60
titles/mai2/buddiesplus.py
Normal file
60
titles/mai2/buddiesplus.py
Normal file
@ -0,0 +1,60 @@
|
||||
from typing import Dict
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.buddies import Mai2Buddies
|
||||
from titles.mai2.const import Mai2Constants
|
||||
from titles.mai2.config import Mai2Config
|
||||
|
||||
|
||||
class Mai2BuddiesPlus(Mai2Buddies):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS
|
||||
|
||||
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||
user_data = await super().handle_cm_get_user_preview_api_request(data)
|
||||
|
||||
# hardcode lastDataVersion for CardMaker
|
||||
user_data["lastDataVersion"] = "1.45.00"
|
||||
return user_data
|
||||
|
||||
async def handle_get_game_weekly_data_api_request(self, data: Dict) -> Dict:
|
||||
return {
|
||||
"gameWeeklyData": {
|
||||
"missionCategory": 0,
|
||||
"updateDate": "2024-03-21 09:00:00",
|
||||
"beforeDate": "2099-12-31 00:00:00"
|
||||
}
|
||||
}
|
||||
|
||||
async def handle_create_token_api_request(self, data: Dict) -> Dict:
|
||||
return {
|
||||
"Bearer": "ARTEMiSTOKEN" # duplicate of handle_user_login_api_request from Mai2Festival
|
||||
}
|
||||
|
||||
async def handle_remove_token_api_request(self, data: Dict) -> Dict:
|
||||
return {}
|
||||
|
||||
async def handle_get_user_friend_bonus_api_request(self, data: Dict) -> Dict:
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"returnCode": 1,
|
||||
"getMiles": 0
|
||||
}
|
||||
|
||||
async def handle_get_user_shop_stock_api_request(self, data: Dict) -> Dict:
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userShopStockList": []
|
||||
}
|
||||
|
||||
async def handle_get_user_mission_data_api_request(self, data: Dict) -> Dict:
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userMissionDataList": [],
|
||||
"userWeeklyData": {
|
||||
"lastLoginWeek": "2024-03-21 09:00:00",
|
||||
"beforeLoginWeek": "2099-12-31 00:00:00",
|
||||
"friendBonusFlag": False
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@ class Mai2Constants:
|
||||
VER_MAIMAI_DX_FESTIVAL = 19
|
||||
VER_MAIMAI_DX_FESTIVAL_PLUS = 20
|
||||
VER_MAIMAI_DX_BUDDIES = 21
|
||||
VER_MAIMAI_DX_BUDDIES_PLUS = 22
|
||||
|
||||
VERSION_STRING = (
|
||||
"maimai",
|
||||
@ -78,7 +79,8 @@ class Mai2Constants:
|
||||
"maimai DX UNiVERSE PLUS",
|
||||
"maimai DX FESTiVAL",
|
||||
"maimai DX FESTiVAL PLUS",
|
||||
"maimai DX BUDDiES"
|
||||
"maimai DX BUDDiES",
|
||||
"maimai DX BUDDiES PLUS"
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
@ -242,7 +242,13 @@ class Mai2DX(Mai2Base):
|
||||
|
||||
if "userFavoriteList" in upsert and len(upsert["userFavoriteList"]) > 0:
|
||||
for fav in upsert["userFavoriteList"]:
|
||||
await self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"])
|
||||
kind_id = fav.get("kind", fav.get("itemKind")) # itemKind key used in BUDDiES+
|
||||
if kind_id is not None:
|
||||
await self.data.item.put_favorite(user_id, kind_id, fav["itemIdList"])
|
||||
|
||||
if "userFavoritemusicList" in upsert and len(upsert["userFavoritemusicList"]) > 0:
|
||||
for fav in upsert["userFavoritemusicList"]:
|
||||
await self.data.item.add_fav_music(user_id, fav["id"], fav["orderId"])
|
||||
|
||||
if (
|
||||
"userFriendSeasonRankingList" in upsert
|
||||
@ -259,6 +265,11 @@ class Mai2DX(Mai2Base):
|
||||
if "user2pPlaylog" in upsert:
|
||||
await self.data.score.put_playlog_2p(user_id, upsert["user2pPlaylog"])
|
||||
|
||||
# added in BUDDiES+
|
||||
if "userIntimateList" in upsert and len(upsert["userIntimateList"]) > 0:
|
||||
for intimate in upsert["userIntimateList"]:
|
||||
await self.data.profile.put_intimacy(user_id, intimate["partnerId"], intimate["intimateLevel"], intimate["intimateCountRewarded"])
|
||||
|
||||
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
|
||||
|
||||
async def handle_get_user_data_api_request(self, data: Dict) -> Dict:
|
||||
@ -708,6 +719,24 @@ class Mai2DX(Mai2Base):
|
||||
ret['loginId'] = ret.get('loginCount', 0)
|
||||
return ret
|
||||
|
||||
# Intimate api added in BUDDiES+
|
||||
async def handle_get_user_intimate_api_request(self, data: Dict) -> Dict:
|
||||
intimate = await self.data.profile.get_intimacy(data["userId"])
|
||||
if intimate is None:
|
||||
return {}
|
||||
|
||||
partner_list = [{
|
||||
"partnerId": i["partnerId"],
|
||||
"intimateLevel": i["intimateLevel"],
|
||||
"intimateCountRewarded": i["intimateCountRewarded"]
|
||||
} for i in intimate]
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"length": len(partner_list),
|
||||
"userIntimateList": partner_list
|
||||
}
|
||||
|
||||
# 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)
|
||||
|
@ -30,6 +30,7 @@ from .universeplus import Mai2UniversePlus
|
||||
from .festival import Mai2Festival
|
||||
from .festivalplus import Mai2FestivalPlus
|
||||
from .buddies import Mai2Buddies
|
||||
from .buddiesplus import Mai2BuddiesPlus
|
||||
|
||||
|
||||
class Mai2Servlet(BaseServlet):
|
||||
@ -64,7 +65,8 @@ class Mai2Servlet(BaseServlet):
|
||||
Mai2UniversePlus,
|
||||
Mai2Festival,
|
||||
Mai2FestivalPlus,
|
||||
Mai2Buddies
|
||||
Mai2Buddies,
|
||||
Mai2BuddiesPlus
|
||||
]
|
||||
|
||||
self.logger = logging.getLogger("mai2")
|
||||
@ -302,8 +304,10 @@ class Mai2Servlet(BaseServlet):
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
|
||||
elif version >= 135 and version < 140: # FESTiVAL PLUS
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL_PLUS
|
||||
elif version >= 140: # BUDDiES
|
||||
elif version >= 140 and version < 145: # BUDDiES
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES
|
||||
elif version >= 145: # BUDDiES PLUS
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_BUDDIES_PLUS
|
||||
elif game_code == "SDGA": # Int
|
||||
if version < 105: # 1.0
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX
|
||||
|
@ -144,6 +144,7 @@ fav_music = Table(
|
||||
nullable=False,
|
||||
),
|
||||
Column("musicId", Integer, nullable=False),
|
||||
Column("orderId", Integer, nullable=True),
|
||||
UniqueConstraint("user", "musicId", name="mai2_item_favorite_music_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
@ -453,10 +454,10 @@ class Mai2ItemData(BaseData):
|
||||
self, user_id: int, kind: int, item_id_list: List[int]
|
||||
) -> Optional[int]:
|
||||
sql = insert(favorite).values(
|
||||
user=user_id, kind=kind, item_id_list=item_id_list
|
||||
user=user_id, itemKind=kind, itemIdList=item_id_list
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(item_id_list=item_id_list)
|
||||
conflict = sql.on_duplicate_key_update(itemIdList=item_id_list)
|
||||
|
||||
result = await self.execute(conflict)
|
||||
if result is None:
|
||||
@ -484,13 +485,14 @@ class Mai2ItemData(BaseData):
|
||||
if result:
|
||||
return result.fetchall()
|
||||
|
||||
async def add_fav_music(self, user_id: int, music_id: int) -> Optional[int]:
|
||||
async def add_fav_music(self, user_id: int, music_id: int, order_id: Optional[int] = None) -> Optional[int]:
|
||||
sql = insert(fav_music).values(
|
||||
user = user_id,
|
||||
musicId = music_id
|
||||
musicId = music_id,
|
||||
orderId = order_id
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(musicId = music_id)
|
||||
conflict = sql.on_duplicate_key_update(orderId = order_id)
|
||||
|
||||
result = await self.execute(conflict)
|
||||
if result:
|
||||
|
@ -3,7 +3,7 @@ from titles.mai2.const import Mai2Constants
|
||||
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger, SmallInteger
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
@ -43,6 +43,8 @@ detail = Table(
|
||||
Column("currentPlayCount", Integer), # new with buddies
|
||||
Column("renameCredit", Integer), # new with buddies
|
||||
Column("mapStock", Integer), # new with fes+
|
||||
Column("point", Integer), # new with buddies+
|
||||
Column("totalPoint", Integer), # new with buddies+
|
||||
Column("eventWatchedDate", String(25)),
|
||||
Column("lastGameId", String(25)),
|
||||
Column("lastRomVersion", String(25)),
|
||||
@ -97,6 +99,7 @@ detail = Table(
|
||||
Column("playerOldRating", BigInteger),
|
||||
Column("playerNewRating", BigInteger),
|
||||
Column("dateTime", BigInteger),
|
||||
Column("friendRegistSkip", SmallInteger), # new with buddies+
|
||||
Column("banState", Integer), # new with uni+
|
||||
UniqueConstraint("user", "version", name="mai2_profile_detail_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
@ -510,6 +513,22 @@ rival = Table(
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
intimacy = Table(
|
||||
"mai2_user_intimate",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("partnerId", Integer, nullable=False),
|
||||
Column("intimateLevel", Integer, nullable=False),
|
||||
Column("intimateCountRewarded", Integer, nullable=False),
|
||||
UniqueConstraint("user", "partnerId", name="mai2_user_intimate_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
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))
|
||||
@ -905,6 +924,27 @@ class Mai2ProfileData(BaseData):
|
||||
if not result:
|
||||
self.logger.error(f"Failed to remove rival {rival_id} for user {user_id}!")
|
||||
|
||||
async def get_intimacy(self, user_id: int) -> Optional[List[Row]]:
|
||||
result = await self.execute(intimacy.select(intimacy.c.user == user_id))
|
||||
if result:
|
||||
return result.fetchall()
|
||||
|
||||
async def put_intimacy(self, user_id: int, partner_id: int, level: int, count_rewarded: int) -> Optional[int]:
|
||||
sql = insert(intimacy).values(
|
||||
user = user_id,
|
||||
partnerId = partner_id,
|
||||
intimateLevel = level,
|
||||
intimateCountRewarded = count_rewarded
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(intimateLevel = level, intimateCountRewarded = count_rewarded)
|
||||
|
||||
result = await self.execute(conflict)
|
||||
if result:
|
||||
return result.lastrowid
|
||||
|
||||
self.logger.error(f"Failed to update intimacy for user {user_id} and partner {partner_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
|
||||
|
Reference in New Issue
Block a user