Compare commits

...

6 Commits

17 changed files with 432 additions and 212 deletions

View File

@ -0,0 +1,31 @@
ALTER TABLE mai2_profile_option
DROP COLUMN tapSe;
ALTER TABLE mai2_score_best
DROP COLUMN extNum1;
ALTER TABLE mai2_profile_extend
DROP COLUMN playStatusSetting;
ALTER TABLE mai2_playlog
DROP COLUMN extNum4;
ALTER TABLE mai2_static_event
DROP COLUMN startDate;
ALTER TABLE mai2_item_map
CHANGE COLUMN mapId map_id INT NOT NULL,
CHANGE COLUMN isLock is_lock BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isClear is_clear BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isComplete is_complete BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE mai2_item_friend_season_ranking
CHANGE COLUMN seasonId season_id INT NOT NULL,
CHANGE COLUMN rewardGet reward_get BOOLEAN NOT NULL,
CHANGE COLUMN userName user_name VARCHAR(8) NOT NULL,
CHANGE COLUMN recordDate record_date VARCHAR(255) NOT NULL;
ALTER TABLE mai2_item_login_bonus
CHANGE COLUMN bonusId bonus_id INT NOT NULL,
CHANGE COLUMN isCurrent is_current BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN isComplete is_complete BOOLEAN NOT NULL DEFAULT 0;

View File

@ -0,0 +1,31 @@
ALTER TABLE mai2_profile_option
ADD COLUMN tapSe INT NOT NULL DEFAULT 0 AFTER tapDesign;
ALTER TABLE mai2_score_best
ADD COLUMN extNum1 INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_profile_extend
ADD COLUMN playStatusSetting INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_playlog
ADD COLUMN extNum4 INT NOT NULL DEFAULT 0;
ALTER TABLE mai2_static_event
ADD COLUMN startDate TIMESTAMP NOT NULL DEFAULT current_timestamp();
ALTER TABLE mai2_item_map
CHANGE COLUMN map_id mapId INT NOT NULL,
CHANGE COLUMN is_lock isLock BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_clear isClear BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_complete isComplete BOOLEAN NOT NULL DEFAULT 0;
ALTER TABLE mai2_item_friend_season_ranking
CHANGE COLUMN season_id seasonId INT NOT NULL,
CHANGE COLUMN reward_get rewardGet BOOLEAN NOT NULL,
CHANGE COLUMN user_name userName VARCHAR(8) NOT NULL,
CHANGE COLUMN record_date recordDate TIMESTAMP NOT NULL;
ALTER TABLE mai2_item_login_bonus
CHANGE COLUMN bonus_id bonusId INT NOT NULL,
CHANGE COLUMN is_current isCurrent BOOLEAN NOT NULL DEFAULT 0,
CHANGE COLUMN is_complete isComplete BOOLEAN NOT NULL DEFAULT 0;

View File

@ -113,6 +113,7 @@ Config file is located in `config/cxb.yaml`.
| 3 | maimai DX Splash PLUS | | 3 | maimai DX Splash PLUS |
| 4 | maimai DX Universe | | 4 | maimai DX Universe |
| 5 | maimai DX Universe PLUS | | 5 | maimai DX Universe PLUS |
| 6 | maimai DX Festival |
### Importer ### Importer
@ -125,7 +126,7 @@ python read.py --series SDEZ --version <version ID> --binfolder /path/to/game/fo
The importer for maimai DX will import Events, Music and Tickets. The importer for maimai DX will import Events, Music and Tickets.
**NOTE: It is required to use the importer because the game will **NOTE: It is required to use the importer because the game will
crash without it!** crash without Events!**
### Database upgrade ### Database upgrade

View File

@ -9,8 +9,8 @@ Games listed below have been tested and confirmed working. Only game versions ol
+ Crossbeats Rev + Crossbeats Rev
+ All versions + omnimix + All versions + omnimix
+ Maimai + maimai DX
+ All versions up to Universe Plus + All versions up to Festival
+ Hatsune Miku Arcade + Hatsune Miku Arcade
+ All versions + All versions

View File

@ -12,6 +12,7 @@ from twisted.web.http import Request
from logging.handlers import TimedRotatingFileHandler from logging.handlers import TimedRotatingFileHandler
from core.config import CoreConfig from core.config import CoreConfig
from core.utils import Utils
from titles.cm.config import CardMakerConfig from titles.cm.config import CardMakerConfig
from titles.cm.const import CardMakerConstants from titles.cm.const import CardMakerConstants
from titles.cm.base import CardMakerBase from titles.cm.base import CardMakerBase
@ -82,6 +83,7 @@ class CardMakerServlet:
url_split = url_path.split("/") url_split = url_path.split("/")
internal_ver = 0 internal_ver = 0
endpoint = url_split[len(url_split) - 1] endpoint = url_split[len(url_split) - 1]
client_ip = Utils.get_ip_addr(request)
print(f"version: {version}") print(f"version: {version}")
@ -107,7 +109,10 @@ class CardMakerServlet:
req_data = json.loads(unzip) req_data = json.loads(unzip)
self.logger.info(f"v{version} {endpoint} request - {req_data}") self.logger.info(
f"v{version} {endpoint} request from {client_ip}"
)
self.logger.debug(req_data)
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"

View File

@ -80,7 +80,7 @@ class CardMakerReader(BaseReader):
for dir in data_dirs: for dir in data_dirs:
self.read_chuni_card(f"{dir}/CHU/card") self.read_chuni_card(f"{dir}/CHU/card")
self.read_chuni_gacha(f"{dir}/CHU/gacha") self.read_chuni_gacha(f"{dir}/CHU/gacha")
self.read_mai2_card(f"{dir}/MAI/card")
self.read_ongeki_gacha(f"{dir}/MU3/gacha") self.read_ongeki_gacha(f"{dir}/MU3/gacha")
def read_chuni_card(self, base_dir: str) -> None: def read_chuni_card(self, base_dir: str) -> None:
@ -206,6 +206,7 @@ class CardMakerReader(BaseReader):
"1.15": Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS, "1.15": Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS,
"1.20": Mai2Constants.VER_MAIMAI_DX_UNIVERSE, "1.20": Mai2Constants.VER_MAIMAI_DX_UNIVERSE,
"1.25": Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS, "1.25": Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS,
"1.30": Mai2Constants.VER_MAIMAI_DX_FESTIVAL,
} }
for root, dirs, files in os.walk(base_dir): for root, dirs, files in os.walk(base_dir):

View File

@ -7,4 +7,4 @@ index = Mai2Servlet
database = Mai2Data database = Mai2Data
reader = Mai2Reader reader = Mai2Reader
game_codes = [Mai2Constants.GAME_CODE] game_codes = [Mai2Constants.GAME_CODE]
current_schema_version = 3 current_schema_version = 4

View File

@ -1,5 +1,5 @@
from datetime import datetime, date, timedelta from datetime import datetime, date, timedelta
from typing import Dict from typing import Any, Dict
import logging import logging
from core.config import CoreConfig from core.config import CoreConfig
@ -52,6 +52,7 @@ class Mai2Base:
events = self.data.static.get_enabled_events(self.version) events = self.data.static.get_enabled_events(self.version)
events_lst = [] events_lst = []
if events is None: if events is None:
self.logger.warn("No enabled events, did you run the reader?")
return {"type": data["type"], "length": 0, "gameEventList": []} return {"type": data["type"], "length": 0, "gameEventList": []}
for event in events: for event in events:
@ -59,7 +60,11 @@ class Mai2Base:
{ {
"type": event["type"], "type": event["type"],
"id": event["eventId"], "id": event["eventId"],
"startDate": "2017-12-05 07:00:00.0", # actually use the startDate from the import so it
# properly shows all the events when new ones are imported
"startDate": datetime.strftime(
event["startDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
),
"endDate": "2099-12-31 00:00:00.0", "endDate": "2099-12-31 00:00:00.0",
} }
) )
@ -79,12 +84,12 @@ class Mai2Base:
return {"length": 0, "gameChargeList": []} return {"length": 0, "gameChargeList": []}
charge_list = [] charge_list = []
for x in range(len(game_charge_list)): for i, charge in enumerate(game_charge_list):
charge_list.append( charge_list.append(
{ {
"orderId": x, "orderId": i,
"chargeId": game_charge_list[x]["ticketId"], "chargeId": charge["ticketId"],
"price": game_charge_list[x]["price"], "price": charge["price"],
"startDate": "2017-12-05 07:00:00.0", "startDate": "2017-12-05 07:00:00.0",
"endDate": "2099-12-31 00:00:00.0", "endDate": "2099-12-31 00:00:00.0",
} }
@ -93,16 +98,16 @@ class Mai2Base:
return {"length": len(charge_list), "gameChargeList": charge_list} return {"length": len(charge_list), "gameChargeList": charge_list}
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict: def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
pass return {"returnCode": 1, "apiName": "UpsertClientSettingApi"}
def handle_upsert_client_upload_api_request(self, data: Dict) -> Dict: def handle_upsert_client_upload_api_request(self, data: Dict) -> Dict:
pass return {"returnCode": 1, "apiName": "UpsertClientUploadApi"}
def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict: def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
pass return {"returnCode": 1, "apiName": "UpsertClientBookkeepingApi"}
def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict: def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
pass return {"returnCode": 1, "apiName": "UpsertClientTestmodeApi"}
def handle_get_user_preview_api_request(self, data: Dict) -> Dict: def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
p = self.data.profile.get_profile_detail(data["userId"], self.version) p = self.data.profile.get_profile_detail(data["userId"], self.version)
@ -167,6 +172,24 @@ class Mai2Base:
self.data.score.put_playlog(user_id, playlog) self.data.score.put_playlog(user_id, playlog)
return {"returnCode": 1, "apiName": "UploadUserPlaylogApi"}
def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
user_id = data["userId"]
charge = data["userCharge"]
# remove the ".0" from the date string, festival only?
charge["purchaseDate"] = charge["purchaseDate"].replace(".0", "")
self.data.item.put_charge(
user_id,
charge["chargeId"],
charge["stock"],
datetime.strptime(charge["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT),
datetime.strptime(charge["validDate"], Mai2Constants.DATE_TIME_FORMAT),
)
return {"returnCode": 1, "apiName": "UpsertUserChargelogApi"}
def handle_upsert_user_all_api_request(self, data: Dict) -> Dict: def handle_upsert_user_all_api_request(self, data: Dict) -> Dict:
user_id = data["userId"] user_id = data["userId"]
upsert = data["upsertUserAll"] upsert = data["upsertUserAll"]
@ -204,15 +227,21 @@ class Mai2Base:
if "userChargeList" in upsert and len(upsert["userChargeList"]) > 0: if "userChargeList" in upsert and len(upsert["userChargeList"]) > 0:
for charge in upsert["userChargeList"]: for charge in upsert["userChargeList"]:
# remove the ".0" from the date string, festival only?
charge["purchaseDate"] = charge["purchaseDate"].replace(".0", "")
self.data.item.put_charge( self.data.item.put_charge(
user_id, user_id,
charge["chargeId"], charge["chargeId"],
charge["stock"], charge["stock"],
datetime.strptime(charge["purchaseDate"], "%Y-%m-%d %H:%M:%S"), datetime.strptime(
datetime.strptime(charge["validDate"], "%Y-%m-%d %H:%M:%S") charge["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT
),
datetime.strptime(
charge["validDate"], Mai2Constants.DATE_TIME_FORMAT
),
) )
if upsert["isNewCharacterList"] and int(upsert["isNewCharacterList"]) > 0: if "userCharacterList" in upsert and len(upsert["userCharacterList"]) > 0:
for char in upsert["userCharacterList"]: for char in upsert["userCharacterList"]:
self.data.item.put_character( self.data.item.put_character(
user_id, user_id,
@ -222,7 +251,7 @@ class Mai2Base:
char["useCount"], char["useCount"],
) )
if upsert["isNewItemList"] and int(upsert["isNewItemList"]) > 0: if "userItemList" in upsert and len(upsert["userItemList"]) > 0:
for item in upsert["userItemList"]: for item in upsert["userItemList"]:
self.data.item.put_item( self.data.item.put_item(
user_id, user_id,
@ -232,7 +261,7 @@ class Mai2Base:
item["isValid"], item["isValid"],
) )
if upsert["isNewLoginBonusList"] and int(upsert["isNewLoginBonusList"]) > 0: if "userLoginBonusList" in upsert and len(upsert["userLoginBonusList"]) > 0:
for login_bonus in upsert["userLoginBonusList"]: for login_bonus in upsert["userLoginBonusList"]:
self.data.item.put_login_bonus( self.data.item.put_login_bonus(
user_id, user_id,
@ -242,7 +271,7 @@ class Mai2Base:
login_bonus["isComplete"], login_bonus["isComplete"],
) )
if upsert["isNewMapList"] and int(upsert["isNewMapList"]) > 0: if "userMapList" in upsert and len(upsert["userMapList"]) > 0:
for map in upsert["userMapList"]: for map in upsert["userMapList"]:
self.data.item.put_map( self.data.item.put_map(
user_id, user_id,
@ -253,24 +282,32 @@ class Mai2Base:
map["isComplete"], map["isComplete"],
) )
if upsert["isNewMusicDetailList"] and int(upsert["isNewMusicDetailList"]) > 0: if "userMusicDetailList" in upsert and len(upsert["userMusicDetailList"]) > 0:
for music in upsert["userMusicDetailList"]: for music in upsert["userMusicDetailList"]:
self.data.score.put_best_score(user_id, music) self.data.score.put_best_score(user_id, music)
if upsert["isNewCourseList"] and int(upsert["isNewCourseList"]) > 0: if "userCourseList" in upsert and len(upsert["userCourseList"]) > 0:
for course in upsert["userCourseList"]: for course in upsert["userCourseList"]:
self.data.score.put_course(user_id, course) self.data.score.put_course(user_id, course)
if upsert["isNewFavoriteList"] and int(upsert["isNewFavoriteList"]) > 0: if "userFavoriteList" in upsert and len(upsert["userFavoriteList"]) > 0:
for fav in upsert["userFavoriteList"]: for fav in upsert["userFavoriteList"]:
self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"]) self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"])
# if "isNewFriendSeasonRankingList" in upsert and int(upsert["isNewFriendSeasonRankingList"]) > 0: if (
# for fsr in upsert["userFriendSeasonRankingList"]: "userFriendSeasonRankingList" in upsert
# pass and len(upsert["userFriendSeasonRankingList"]) > 0
):
for fsr in upsert["userFriendSeasonRankingList"]:
fsr["recordDate"] = (
datetime.strptime(fsr["recordDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"),
)
self.data.item.put_friend_season_ranking(user_id, fsr)
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
def handle_user_logout_api_request(self, data: Dict) -> Dict: def handle_user_logout_api_request(self, data: Dict) -> Dict:
pass return {"returnCode": 1}
def handle_get_user_data_api_request(self, data: Dict) -> Dict: def handle_get_user_data_api_request(self, data: Dict) -> Dict:
profile = self.data.profile.get_profile_detail(data["userId"], self.version) profile = self.data.profile.get_profile_detail(data["userId"], self.version)
@ -311,11 +348,7 @@ class Mai2Base:
def handle_get_user_card_api_request(self, data: Dict) -> Dict: def handle_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = self.data.item.get_cards(data["userId"]) user_cards = self.data.item.get_cards(data["userId"])
if user_cards is None: if user_cards is None:
return { return {"userId": data["userId"], "nextIndex": 0, "userCardList": []}
"userId": data["userId"],
"nextIndex": 0,
"userCardList": []
}
max_ct = data["maxCount"] max_ct = data["maxCount"]
next_idx = data["nextIndex"] next_idx = data["nextIndex"]
@ -333,25 +366,23 @@ class Mai2Base:
tmp.pop("id") tmp.pop("id")
tmp.pop("user") tmp.pop("user")
tmp["startDate"] = datetime.strftime( tmp["startDate"] = datetime.strftime(
tmp["startDate"], "%Y-%m-%d %H:%M:%S") tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT
)
tmp["endDate"] = datetime.strftime( tmp["endDate"] = datetime.strftime(
tmp["endDate"], "%Y-%m-%d %H:%M:%S") tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT
)
card_list.append(tmp) card_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"nextIndex": next_idx, "nextIndex": next_idx,
"userCardList": card_list[start_idx:end_idx] "userCardList": card_list[start_idx:end_idx],
} }
def handle_get_user_charge_api_request(self, data: Dict) -> Dict: def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
user_charges = self.data.item.get_charges(data["userId"]) user_charges = self.data.item.get_charges(data["userId"])
if user_charges is None: if user_charges is None:
return { return {"userId": data["userId"], "length": 0, "userChargeList": []}
"userId": data["userId"],
"length": 0,
"userChargeList": []
}
user_charge_list = [] user_charge_list = []
for charge in user_charges: for charge in user_charges:
@ -359,45 +390,46 @@ class Mai2Base:
tmp.pop("id") tmp.pop("id")
tmp.pop("user") tmp.pop("user")
tmp["purchaseDate"] = datetime.strftime( tmp["purchaseDate"] = datetime.strftime(
tmp["purchaseDate"], "%Y-%m-%d %H:%M:%S") tmp["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT
)
tmp["validDate"] = datetime.strftime( tmp["validDate"] = datetime.strftime(
tmp["validDate"], "%Y-%m-%d %H:%M:%S") tmp["validDate"], Mai2Constants.DATE_TIME_FORMAT
)
user_charge_list.append(tmp) user_charge_list.append(tmp)
return { return {
"userId": data["userId"], "userId": data["userId"],
"length": len(user_charge_list), "length": len(user_charge_list),
"userChargeList": user_charge_list "userChargeList": user_charge_list,
} }
def handle_get_user_item_api_request(self, data: Dict) -> Dict: def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = int(data["nextIndex"] / 10000000000) kind = int(data["nextIndex"] / 10000000000)
next_idx = int(data["nextIndex"] % 10000000000) next_idx = int(data["nextIndex"] % 10000000000)
user_items = self.data.item.get_items(data["userId"], kind) user_item_list = self.data.item.get_items(data["userId"], kind)
user_item_list = []
next_idx = 0
for x in range(next_idx, data["maxCount"]): items: list[Dict[str, Any]] = []
try: for i in range(next_idx, len(user_item_list)):
user_item_list.append({ tmp = user_item_list[i]._asdict()
"itemKind": user_items[x]["itemKind"], tmp.pop("user")
"itemId": user_items[x]["itemId"], tmp.pop("id")
"stock": user_items[x]["stock"], items.append(tmp)
"isValid": user_items[x]["isValid"] if len(items) >= int(data["maxCount"]):
})
except IndexError:
break break
if len(user_item_list) == data["maxCount"]: xout = kind * 10000000000 + next_idx + len(items)
next_idx = data["nextIndex"] + data["maxCount"] + 1
break if len(items) < int(data["maxCount"]):
next_idx = 0
else:
next_idx = xout
return { return {
"userId": data["userId"], "userId": data["userId"],
"nextIndex": next_idx, "nextIndex": next_idx,
"itemKind": kind, "itemKind": kind,
"userItemList": user_item_list "userItemList": items,
} }
def handle_get_user_character_api_request(self, data: Dict) -> Dict: def handle_get_user_character_api_request(self, data: Dict) -> Dict:
@ -479,21 +511,12 @@ class Mai2Base:
tmp.pop("user") tmp.pop("user")
mlst.append(tmp) mlst.append(tmp)
return { return {"userActivity": {"playList": plst, "musicList": mlst}}
"userActivity": {
"playList": plst,
"musicList": mlst
}
}
def handle_get_user_course_api_request(self, data: Dict) -> Dict: def handle_get_user_course_api_request(self, data: Dict) -> Dict:
user_courses = self.data.score.get_courses(data["userId"]) user_courses = self.data.score.get_courses(data["userId"])
if user_courses is None: if user_courses is None:
return { return {"userId": data["userId"], "nextIndex": 0, "userCourseList": []}
"userId": data["userId"],
"nextIndex": 0,
"userCourseList": []
}
course_list = [] course_list = []
for course in user_courses: for course in user_courses:
@ -502,11 +525,7 @@ class Mai2Base:
tmp.pop("id") tmp.pop("id")
course_list.append(tmp) course_list.append(tmp)
return { return {"userId": data["userId"], "nextIndex": 0, "userCourseList": course_list}
"userId": data["userId"],
"nextIndex": 0,
"userCourseList": course_list
}
def handle_get_user_portrait_api_request(self, data: Dict) -> Dict: def handle_get_user_portrait_api_request(self, data: Dict) -> Dict:
# No support for custom pfps # No support for custom pfps
@ -514,96 +533,103 @@ class Mai2Base:
def handle_get_user_friend_season_ranking_api_request(self, data: Dict) -> Dict: def handle_get_user_friend_season_ranking_api_request(self, data: Dict) -> Dict:
friend_season_ranking = self.data.item.get_friend_season_ranking(data["userId"]) friend_season_ranking = self.data.item.get_friend_season_ranking(data["userId"])
friend_season_ranking_list = [] if friend_season_ranking is None:
next_index = 0 return {
"userId": data["userId"],
"nextIndex": 0,
"userFriendSeasonRankingList": [],
}
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]): friend_season_ranking_list = []
try: next_idx = int(data["nextIndex"])
friend_season_ranking_list.append( max_ct = int(data["maxCount"])
{
"mapId": friend_season_ranking_list[x]["map_id"], for x in range(next_idx, len(friend_season_ranking)):
"distance": friend_season_ranking_list[x]["distance"], tmp = friend_season_ranking[x]._asdict()
"isLock": friend_season_ranking_list[x]["is_lock"], tmp.pop("user")
"isClear": friend_season_ranking_list[x]["is_clear"], tmp.pop("id")
"isComplete": friend_season_ranking_list[x]["is_complete"], tmp["recordDate"] = datetime.strftime(
} tmp["recordDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
) )
except: friend_season_ranking_list.append(tmp)
if len(friend_season_ranking_list) >= max_ct:
break break
# We're capped and still have some left to go if len(friend_season_ranking) >= next_idx + max_ct:
if ( next_idx += max_ct
len(friend_season_ranking_list) == data["maxCount"] else:
and len(friend_season_ranking) > data["maxCount"] + data["nextIndex"] next_idx = 0
):
next_index = data["maxCount"] + data["nextIndex"]
return { return {
"userId": data["userId"], "userId": data["userId"],
"nextIndex": next_index, "nextIndex": next_idx,
"userFriendSeasonRankingList": friend_season_ranking_list, "userFriendSeasonRankingList": friend_season_ranking_list,
} }
def handle_get_user_map_api_request(self, data: Dict) -> Dict: def handle_get_user_map_api_request(self, data: Dict) -> Dict:
maps = self.data.item.get_maps(data["userId"]) maps = self.data.item.get_maps(data["userId"])
map_list = [] if maps is None:
next_index = 0 return {
"userId": data["userId"],
"nextIndex": 0,
"userMapList": [],
}
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]): map_list = []
try: next_idx = int(data["nextIndex"])
map_list.append( max_ct = int(data["maxCount"])
{
"mapId": maps[x]["map_id"], for x in range(next_idx, len(maps)):
"distance": maps[x]["distance"], tmp = maps[x]._asdict()
"isLock": maps[x]["is_lock"], tmp.pop("user")
"isClear": maps[x]["is_clear"], tmp.pop("id")
"isComplete": maps[x]["is_complete"], map_list.append(tmp)
}
) if len(map_list) >= max_ct:
except:
break break
# We're capped and still have some left to go if len(maps) >= next_idx + max_ct:
if ( next_idx += max_ct
len(map_list) == data["maxCount"] else:
and len(maps) > data["maxCount"] + data["nextIndex"] next_idx = 0
):
next_index = data["maxCount"] + data["nextIndex"]
return { return {
"userId": data["userId"], "userId": data["userId"],
"nextIndex": next_index, "nextIndex": next_idx,
"userMapList": map_list, "userMapList": map_list,
} }
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict: def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
login_bonuses = self.data.item.get_login_bonuses(data["userId"]) login_bonuses = self.data.item.get_login_bonuses(data["userId"])
login_bonus_list = [] if login_bonuses is None:
next_index = 0 return {
"userId": data["userId"],
"nextIndex": 0,
"userLoginBonusList": [],
}
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]): login_bonus_list = []
try: next_idx = int(data["nextIndex"])
login_bonus_list.append( max_ct = int(data["maxCount"])
{
"bonusId": login_bonuses[x]["bonus_id"], for x in range(next_idx, len(login_bonuses)):
"point": login_bonuses[x]["point"], tmp = login_bonuses[x]._asdict()
"isCurrent": login_bonuses[x]["is_current"], tmp.pop("user")
"isComplete": login_bonuses[x]["is_complete"], tmp.pop("id")
} login_bonus_list.append(tmp)
)
except: if len(login_bonus_list) >= max_ct:
break break
# We're capped and still have some left to go if len(login_bonuses) >= next_idx + max_ct:
if ( next_idx += max_ct
len(login_bonus_list) == data["maxCount"] else:
and len(login_bonuses) > data["maxCount"] + data["nextIndex"] next_idx = 0
):
next_index = data["maxCount"] + data["nextIndex"]
return { return {
"userId": data["userId"], "userId": data["userId"],
"nextIndex": next_index, "nextIndex": next_idx,
"userLoginBonusList": login_bonus_list, "userLoginBonusList": login_bonus_list,
} }
@ -629,5 +655,5 @@ class Mai2Base:
return { return {
"userId": data["userId"], "userId": data["userId"],
"nextIndex": next_index, "nextIndex": next_index,
"userMusicList": [{"userMusicDetailList": music_detail_list}] "userMusicList": [{"userMusicDetailList": music_detail_list}],
} }

View File

@ -30,14 +30,16 @@ class Mai2Constants:
VER_MAIMAI_DX_SPLASH_PLUS = 3 VER_MAIMAI_DX_SPLASH_PLUS = 3
VER_MAIMAI_DX_UNIVERSE = 4 VER_MAIMAI_DX_UNIVERSE = 4
VER_MAIMAI_DX_UNIVERSE_PLUS = 5 VER_MAIMAI_DX_UNIVERSE_PLUS = 5
VER_MAIMAI_DX_FESTIVAL = 6
VERSION_STRING = ( VERSION_STRING = (
"maimai Delux", "maimai DX",
"maimai Delux PLUS", "maimai DX PLUS",
"maimai Delux Splash", "maimai DX Splash",
"maimai Delux Splash PLUS", "maimai DX Splash PLUS",
"maimai Delux Universe", "maimai DX Universe",
"maimai Delux Universe PLUS", "maimai DX Universe PLUS",
"maimai DX Festival"
) )
@classmethod @classmethod

31
titles/mai2/festival.py Normal file
View File

@ -0,0 +1,31 @@
from typing import Dict
from core.config import CoreConfig
from titles.mai2.universeplus import Mai2UniversePlus
from titles.mai2.const import Mai2Constants
from titles.mai2.config import Mai2Config
class Mai2Festival(Mai2UniversePlus):
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
super().__init__(cfg, game_cfg)
self.version = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
user_data = super().handle_cm_get_user_preview_api_request(data)
# hardcode lastDataVersion for CardMaker 1.36
user_data["lastDataVersion"] = "1.30.00"
return user_data
def handle_user_login_api_request(self, data: Dict) -> Dict:
user_login = super().handle_user_login_api_request(data)
# useless?
user_login["Bearer"] = "ARTEMiSTOKEN"
return user_login
def handle_get_user_recommend_rate_music_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "userRecommendRateMusicIdList": []}
def handle_get_user_recommend_select_music_api_request(self, data: Dict) -> Dict:
return {"userId": data["userId"], "userRecommendSelectionMusicIdList": []}

View File

@ -10,6 +10,7 @@ from os import path
from typing import Tuple from typing import Tuple
from core.config import CoreConfig from core.config import CoreConfig
from core.utils import Utils
from titles.mai2.config import Mai2Config from titles.mai2.config import Mai2Config
from titles.mai2.const import Mai2Constants from titles.mai2.const import Mai2Constants
from titles.mai2.base import Mai2Base from titles.mai2.base import Mai2Base
@ -18,6 +19,7 @@ from titles.mai2.splash import Mai2Splash
from titles.mai2.splashplus import Mai2SplashPlus from titles.mai2.splashplus import Mai2SplashPlus
from titles.mai2.universe import Mai2Universe from titles.mai2.universe import Mai2Universe
from titles.mai2.universeplus import Mai2UniversePlus from titles.mai2.universeplus import Mai2UniversePlus
from titles.mai2.festival import Mai2Festival
class Mai2Servlet: class Mai2Servlet:
@ -30,12 +32,13 @@ class Mai2Servlet:
) )
self.versions = [ self.versions = [
Mai2Base(core_cfg, self.game_cfg), Mai2Base,
Mai2Plus(core_cfg, self.game_cfg), Mai2Plus,
Mai2Splash(core_cfg, self.game_cfg), Mai2Splash,
Mai2SplashPlus(core_cfg, self.game_cfg), Mai2SplashPlus,
Mai2Universe(core_cfg, self.game_cfg), Mai2Universe,
Mai2UniversePlus(core_cfg, self.game_cfg), Mai2UniversePlus,
Mai2Festival
] ]
self.logger = logging.getLogger("mai2") self.logger = logging.getLogger("mai2")
@ -97,6 +100,7 @@ class Mai2Servlet:
url_split = url_path.split("/") url_split = url_path.split("/")
internal_ver = 0 internal_ver = 0
endpoint = url_split[len(url_split) - 1] endpoint = url_split[len(url_split) - 1]
client_ip = Utils.get_ip_addr(request)
if version < 105: # 1.0 if version < 105: # 1.0
internal_ver = Mai2Constants.VER_MAIMAI_DX internal_ver = Mai2Constants.VER_MAIMAI_DX
@ -108,8 +112,10 @@ class Mai2Servlet:
internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS
elif version >= 120 and version < 125: # Universe elif version >= 120 and version < 125: # Universe
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
elif version >= 125: # Universe Plus elif version >= 125 and version < 130: # Universe Plus
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
elif version >= 130: # Festival
internal_ver = Mai2Constants.VER_MAIMAI_DX_FESTIVAL
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32: if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
# If we get a 32 character long hex string, it's a hash and we're # If we get a 32 character long hex string, it's a hash and we're
@ -128,25 +134,30 @@ class Mai2Servlet:
req_data = json.loads(unzip) req_data = json.loads(unzip)
self.logger.info(f"v{version} {endpoint} request - {req_data}") self.logger.info(
f"v{version} {endpoint} request from {client_ip}"
)
self.logger.debug(req_data)
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request" func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
handler_cls = self.versions[internal_ver](self.core_cfg, self.game_cfg)
if not hasattr(self.versions[internal_ver], func_to_find): if not hasattr(handler_cls, func_to_find):
self.logger.warning(f"Unhandled v{version} request {endpoint}") self.logger.warning(f"Unhandled v{version} request {endpoint}")
return zlib.compress(b'{"returnCode": 1}') resp = {"returnCode": 1}
try: else:
handler = getattr(self.versions[internal_ver], func_to_find) try:
resp = handler(req_data) handler = getattr(handler_cls, func_to_find)
resp = handler(req_data)
except Exception as e: except Exception as e:
self.logger.error(f"Error handling v{version} method {endpoint} - {e}") self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
return zlib.compress(b'{"stat": "0"}') return zlib.compress(b'{"stat": "0"}')
if resp == None: if resp == None:
resp = {"returnCode": 1} resp = {"returnCode": 1}
self.logger.info(f"Response {resp}") self.logger.debug(f"Response {resp}")
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8")) return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))

View File

@ -29,7 +29,7 @@ class Mai2Reader(BaseReader):
f"Start importer for {Mai2Constants.game_ver_to_string(version)}" f"Start importer for {Mai2Constants.game_ver_to_string(version)}"
) )
except IndexError: except IndexError:
self.logger.error(f"Invalid maidx version {version}") self.logger.error(f"Invalid maimai DX version {version}")
exit(1) exit(1)
def read(self) -> None: def read(self) -> None:
@ -43,6 +43,7 @@ class Mai2Reader(BaseReader):
for dir in data_dirs: for dir in data_dirs:
self.logger.info(f"Read from {dir}") self.logger.info(f"Read from {dir}")
self.get_events(f"{dir}/event") self.get_events(f"{dir}/event")
self.disable_events(f"{dir}/information", f"{dir}/scoreRanking")
self.read_music(f"{dir}/music") self.read_music(f"{dir}/music")
self.read_tickets(f"{dir}/ticket") self.read_tickets(f"{dir}/ticket")
@ -64,6 +65,64 @@ class Mai2Reader(BaseReader):
) )
self.logger.info(f"Added event {id}...") self.logger.info(f"Added event {id}...")
def disable_events(
self, base_information_dir: str, base_score_ranking_dir: str
) -> None:
self.logger.info(f"Reading disabled events from {base_information_dir}...")
for root, dirs, files in os.walk(base_information_dir):
for dir in dirs:
if os.path.exists(f"{root}/{dir}/Information.xml"):
with open(f"{root}/{dir}/Information.xml", encoding="utf-8") as f:
troot = ET.fromstring(f.read())
event_id = int(troot.find("name").find("id").text)
self.data.static.toggle_game_event(
self.version, event_id, toggle=False
)
self.logger.info(f"Disabled event {event_id}...")
for root, dirs, files in os.walk(base_score_ranking_dir):
for dir in dirs:
if os.path.exists(f"{root}/{dir}/ScoreRanking.xml"):
with open(f"{root}/{dir}/ScoreRanking.xml", encoding="utf-8") as f:
troot = ET.fromstring(f.read())
event_id = int(troot.find("eventName").find("id").text)
self.data.static.toggle_game_event(
self.version, event_id, toggle=False
)
self.logger.info(f"Disabled event {event_id}...")
# manually disable events wich are known to be problematic
for event_id in [
1,
10,
220311,
220312,
220313,
220314,
220315,
220316,
220317,
220318,
20121821,
21121651,
22091511,
22091512,
22091513,
22091514,
22091515,
22091516,
22091517,
22091518,
22091519,
]:
self.data.static.toggle_game_event(self.version, event_id, toggle=False)
self.logger.info(f"Disabled event {event_id}...")
def read_music(self, base_dir: str) -> None: def read_music(self, base_dir: str) -> None:
self.logger.info(f"Reading music from {base_dir}...") self.logger.info(f"Reading music from {base_dir}...")

View File

@ -71,12 +71,12 @@ map = Table(
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False, nullable=False,
), ),
Column("map_id", Integer, nullable=False), Column("mapId", Integer, nullable=False),
Column("distance", Integer, nullable=False), Column("distance", Integer, nullable=False),
Column("is_lock", Boolean, nullable=False, server_default="0"), Column("isLock", Boolean, nullable=False, server_default="0"),
Column("is_clear", Boolean, nullable=False, server_default="0"), Column("isClear", Boolean, nullable=False, server_default="0"),
Column("is_complete", Boolean, nullable=False, server_default="0"), Column("isComplete", Boolean, nullable=False, server_default="0"),
UniqueConstraint("user", "map_id", name="mai2_item_map_uk"), UniqueConstraint("user", "mapId", name="mai2_item_map_uk"),
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )
@ -89,11 +89,11 @@ login_bonus = Table(
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False, nullable=False,
), ),
Column("bonus_id", Integer, nullable=False), Column("bonusId", Integer, nullable=False),
Column("point", Integer, nullable=False), Column("point", Integer, nullable=False),
Column("is_current", Boolean, nullable=False, server_default="0"), Column("isCurrent", Boolean, nullable=False, server_default="0"),
Column("is_complete", Boolean, nullable=False, server_default="0"), Column("isComplete", Boolean, nullable=False, server_default="0"),
UniqueConstraint("user", "bonus_id", name="mai2_item_login_bonus_uk"), UniqueConstraint("user", "bonusId", name="mai2_item_login_bonus_uk"),
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )
@ -106,13 +106,15 @@ friend_season_ranking = Table(
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
nullable=False, nullable=False,
), ),
Column("season_id", Integer, nullable=False), Column("seasonId", Integer, nullable=False),
Column("point", Integer, nullable=False), Column("point", Integer, nullable=False),
Column("rank", Integer, nullable=False), Column("rank", Integer, nullable=False),
Column("reward_get", Boolean, nullable=False), Column("rewardGet", Boolean, nullable=False),
Column("user_name", String(8), nullable=False), Column("userName", String(8), nullable=False),
Column("record_date", String(255), nullable=False), Column("recordDate", TIMESTAMP, nullable=False),
UniqueConstraint("user", "season_id", "user_name", name="mai2_item_login_bonus_uk"), UniqueConstraint(
"user", "seasonId", "userName", name="mai2_item_friend_season_ranking_uk"
),
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )
@ -244,16 +246,16 @@ class Mai2ItemData(BaseData):
) -> None: ) -> None:
sql = insert(login_bonus).values( sql = insert(login_bonus).values(
user=user_id, user=user_id,
bonus_id=bonus_id, bonusId=bonus_id,
point=point, point=point,
is_current=is_current, isCurrent=is_current,
is_complete=is_complete, isComplete=is_complete,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(
point=point, point=point,
is_current=is_current, isCurrent=is_current,
is_complete=is_complete, isComplete=is_complete,
) )
result = self.execute(conflict) result = self.execute(conflict)
@ -293,18 +295,18 @@ class Mai2ItemData(BaseData):
) -> None: ) -> None:
sql = insert(map).values( sql = insert(map).values(
user=user_id, user=user_id,
map_id=map_id, mapId=map_id,
distance=distance, distance=distance,
is_lock=is_lock, isLock=is_lock,
is_clear=is_clear, isClear=is_clear,
is_complete=is_complete, isComplete=is_complete,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(
distance=distance, distance=distance,
is_lock=is_lock, isLock=is_lock,
is_clear=is_clear, isClear=is_clear,
is_complete=is_complete, isComplete=is_complete,
) )
result = self.execute(conflict) result = self.execute(conflict)
@ -324,7 +326,7 @@ class Mai2ItemData(BaseData):
return result.fetchall() return result.fetchall()
def get_map(self, user_id: int, map_id: int) -> Optional[Row]: def get_map(self, user_id: int, map_id: int) -> Optional[Row]:
sql = map.select(and_(map.c.user == user_id, map.c.map_id == map_id)) sql = map.select(and_(map.c.user == user_id, map.c.mapId == map_id))
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
@ -341,16 +343,16 @@ class Mai2ItemData(BaseData):
) -> None: ) -> None:
sql = insert(character).values( sql = insert(character).values(
user=user_id, user=user_id,
character_id=character_id, characterId=character_id,
level=level, level=level,
awakening=awakening, awakening=awakening,
use_count=use_count, useCount=use_count,
) )
conflict = sql.on_duplicate_key_update( conflict = sql.on_duplicate_key_update(
level=level, level=level,
awakening=awakening, awakening=awakening,
use_count=use_count, useCount=use_count,
) )
result = self.execute(conflict) result = self.execute(conflict)
@ -385,7 +387,25 @@ class Mai2ItemData(BaseData):
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
return None return None
return result.fetchone() return result.fetchall()
def put_friend_season_ranking(
self, aime_id: int, friend_season_ranking_data: Dict
) -> Optional[int]:
sql = insert(friend_season_ranking).values(
user=aime_id, **friend_season_ranking_data
)
conflict = sql.on_duplicate_key_update(**friend_season_ranking_data)
result = self.execute(conflict)
if result is None:
self.logger.warn(
f"put_friend_season_ranking: failed to insert",
f"friend_season_ranking! aime_id: {aime_id}"
)
return None
return result.lastrowid
def put_favorite( def put_favorite(
self, user_id: int, kind: int, item_id_list: List[int] self, user_id: int, kind: int, item_id_list: List[int]

View File

@ -158,6 +158,7 @@ extend = Table(
Column("sortMusicSetting", Integer), Column("sortMusicSetting", Integer),
Column("selectedCardList", JSON), Column("selectedCardList", JSON),
Column("encountMapNpcList", JSON), Column("encountMapNpcList", JSON),
Column("playStatusSetting", Integer, server_default="0"),
UniqueConstraint("user", "version", name="mai2_profile_extend_uk"), UniqueConstraint("user", "version", name="mai2_profile_extend_uk"),
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )
@ -178,6 +179,7 @@ option = Table(
Column("slideSpeed", Integer), Column("slideSpeed", Integer),
Column("touchSpeed", Integer), Column("touchSpeed", Integer),
Column("tapDesign", Integer), Column("tapDesign", Integer),
Column("tapSe", Integer, server_default="0"),
Column("holdDesign", Integer), Column("holdDesign", Integer),
Column("slideDesign", Integer), Column("slideDesign", Integer),
Column("starType", Integer), Column("starType", Integer),
@ -298,8 +300,8 @@ class Mai2ProfileData(BaseData):
def get_profile_detail(self, user_id: int, version: int) -> Optional[Row]: def get_profile_detail(self, user_id: int, version: int) -> Optional[Row]:
sql = select(detail).where( sql = select(detail).where(
and_(detail.c.user == user_id, detail.c.version == version) and_(detail.c.user == user_id, detail.c.version <= version)
) ).order_by(detail.c.version.desc())
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
@ -323,8 +325,8 @@ class Mai2ProfileData(BaseData):
def get_profile_ghost(self, user_id: int, version: int) -> Optional[Row]: def get_profile_ghost(self, user_id: int, version: int) -> Optional[Row]:
sql = select(ghost).where( sql = select(ghost).where(
and_(ghost.c.user == user_id, ghost.c.version_int == version) and_(ghost.c.user == user_id, ghost.c.version_int <= version)
) ).order_by(ghost.c.version.desc())
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
@ -348,8 +350,8 @@ class Mai2ProfileData(BaseData):
def get_profile_extend(self, user_id: int, version: int) -> Optional[Row]: def get_profile_extend(self, user_id: int, version: int) -> Optional[Row]:
sql = select(extend).where( sql = select(extend).where(
and_(extend.c.user == user_id, extend.c.version == version) and_(extend.c.user == user_id, extend.c.version <= version)
) ).order_by(extend.c.version.desc())
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
@ -373,8 +375,8 @@ class Mai2ProfileData(BaseData):
def get_profile_option(self, user_id: int, version: int) -> Optional[Row]: def get_profile_option(self, user_id: int, version: int) -> Optional[Row]:
sql = select(option).where( sql = select(option).where(
and_(option.c.user == user_id, option.c.version == version) and_(option.c.user == user_id, option.c.version <= version)
) ).order_by(option.c.version.desc())
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
@ -398,8 +400,8 @@ class Mai2ProfileData(BaseData):
def get_profile_rating(self, user_id: int, version: int) -> Optional[Row]: def get_profile_rating(self, user_id: int, version: int) -> Optional[Row]:
sql = select(rating).where( sql = select(rating).where(
and_(rating.c.user == user_id, rating.c.version == version) and_(rating.c.user == user_id, rating.c.version <= version)
) ).order_by(rating.c.version.desc())
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:

View File

@ -25,6 +25,7 @@ best_score = Table(
Column("syncStatus", Integer), Column("syncStatus", Integer),
Column("deluxscoreMax", Integer), Column("deluxscoreMax", Integer),
Column("scoreRank", Integer), Column("scoreRank", Integer),
Column("extNum1", Integer, server_default="0"),
UniqueConstraint("user", "musicId", "level", name="mai2_score_best_uk"), UniqueConstraint("user", "musicId", "level", name="mai2_score_best_uk"),
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )
@ -143,6 +144,7 @@ playlog = Table(
Column("isNewFree", Boolean), Column("isNewFree", Boolean),
Column("extNum1", Integer), Column("extNum1", Integer),
Column("extNum2", Integer), Column("extNum2", Integer),
Column("extNum4", Integer, server_default="0"),
Column("trialPlayAchievement", Integer), Column("trialPlayAchievement", Integer),
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
) )

View File

@ -16,6 +16,7 @@ event = Table(
Column("eventId", Integer), Column("eventId", Integer),
Column("type", Integer), Column("type", Integer),
Column("name", String(255)), Column("name", String(255)),
Column("startDate", TIMESTAMP, server_default=func.now()),
Column("enabled", Boolean, server_default="1"), Column("enabled", Boolean, server_default="1"),
UniqueConstraint("version", "eventId", "type", name="mai2_static_event_uk"), UniqueConstraint("version", "eventId", "type", name="mai2_static_event_uk"),
mysql_charset="utf8mb4", mysql_charset="utf8mb4",
@ -108,17 +109,17 @@ class Mai2StaticData(BaseData):
return None return None
return result.fetchall() return result.fetchall()
def toggle_game_events( def toggle_game_event(
self, version: int, event_id: int, toggle: bool self, version: int, event_id: int, toggle: bool
) -> Optional[List]: ) -> Optional[List]:
sql = event.update( sql = event.update(
and_(event.c.version == version, event.c.event_id == event_id) and_(event.c.version == version, event.c.eventId == event_id)
).values(enabled=int(toggle)) ).values(enabled=int(toggle))
result = self.execute(sql) result = self.execute(sql)
if result is None: if result is None:
self.logger.warning( self.logger.warning(
f"toggle_game_events: Failed to update event! event_id {event_id} toggle {toggle}" f"toggle_game_event: Failed to update event! event_id {event_id} toggle {toggle}"
) )
return result.last_updated_params() return result.last_updated_params()

View File

@ -1,7 +1,4 @@
from typing import Any, List, Dict from typing import Dict
from datetime import datetime, timedelta
import pytz
import json
from core.config import CoreConfig from core.config import CoreConfig
from titles.mai2.universe import Mai2Universe from titles.mai2.universe import Mai2Universe