mirror of
https://gitea.tendokyu.moe/Hay1tsme/artemis.git
synced 2026-02-14 11:47:28 +08:00
add back games, conform them to new title dispatch
This commit is contained in:
18
titles/mai2/__init__.py
Normal file
18
titles/mai2/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from titles.mai2.index import Mai2Servlet
|
||||
from titles.mai2.const import Mai2Constants
|
||||
from titles.mai2.database import Mai2Data
|
||||
from titles.mai2.read import Mai2Reader
|
||||
|
||||
index = Mai2Servlet
|
||||
database = Mai2Data
|
||||
reader = Mai2Reader
|
||||
|
||||
use_default_title = True
|
||||
include_protocol = True
|
||||
title_secure = False
|
||||
game_codes = [Mai2Constants.GAME_CODE]
|
||||
trailing_slash = True
|
||||
use_default_host = False
|
||||
host = ""
|
||||
|
||||
current_schema_version = 1
|
||||
477
titles/mai2/base.py
Normal file
477
titles/mai2/base.py
Normal file
@@ -0,0 +1,477 @@
|
||||
from datetime import datetime, date, timedelta
|
||||
from typing import Dict
|
||||
import logging
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.const import Mai2Constants
|
||||
from titles.mai2.config import Mai2Config
|
||||
from titles.mai2.database import Mai2Data
|
||||
|
||||
class Mai2Base():
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||
self.core_config = cfg
|
||||
self.game_config = game_cfg
|
||||
self.game = Mai2Constants.GAME_CODE
|
||||
self.version = Mai2Constants.VER_MAIMAI_DX
|
||||
self.data = Mai2Data(cfg)
|
||||
self.logger = logging.getLogger("mai2")
|
||||
|
||||
def handle_get_game_setting_api_request(self, data: Dict):
|
||||
reboot_start = date.strftime(datetime.now() + timedelta(hours=3), Mai2Constants.DATE_TIME_FORMAT)
|
||||
reboot_end = date.strftime(datetime.now() + timedelta(hours=4), Mai2Constants.DATE_TIME_FORMAT)
|
||||
return {
|
||||
"gameSetting": {
|
||||
"isMaintenance": "false",
|
||||
"requestInterval": 10,
|
||||
"rebootStartTime": reboot_start,
|
||||
"rebootEndTime": reboot_end,
|
||||
"movieUploadLimit": 10000,
|
||||
"movieStatus": 0,
|
||||
"movieServerUri": "",
|
||||
"deliverServerUri": "",
|
||||
"oldServerUri": "",
|
||||
"usbDlServerUri": "",
|
||||
"rebootInterval": 0
|
||||
},
|
||||
"isAouAccession": "true",
|
||||
}
|
||||
|
||||
def handle_get_game_ranking_api_request(self, data: Dict) -> Dict:
|
||||
return {"length": 0, "gameRankingList": []}
|
||||
|
||||
def handle_get_game_tournament_info_api_request(self, data: Dict) -> Dict:
|
||||
# TODO: Tournament support
|
||||
return {"length": 0, "gameTournamentInfoList": []}
|
||||
|
||||
def handle_get_game_event_api_request(self, data: Dict) -> Dict:
|
||||
events = self.data.static.get_enabled_events(self.version)
|
||||
events_lst = []
|
||||
if events is None: return {"type": data["type"], "length": 0, "gameEventList": []}
|
||||
|
||||
for event in events:
|
||||
events_lst.append({
|
||||
"type": event["type"],
|
||||
"id": event["eventId"],
|
||||
"startDate": "2017-12-05 07:00:00.0",
|
||||
"endDate": "2099-12-31 00:00:00.0"
|
||||
})
|
||||
|
||||
return {"type": data["type"], "length": len(events_lst), "gameEventList": events_lst}
|
||||
|
||||
def handle_get_game_ng_music_id_api_request(self, data: Dict) -> Dict:
|
||||
return {"length": 0, "musicIdList": []}
|
||||
|
||||
def handle_get_game_charge_api_request(self, data: Dict) -> Dict:
|
||||
game_charge_list = self.data.static.get_enabled_tickets(self.version, 1)
|
||||
if game_charge_list is None: return {"length": 0, "gameChargeList": []}
|
||||
|
||||
charge_list = []
|
||||
for x in range(len(game_charge_list)):
|
||||
charge_list.append({
|
||||
"orderId": x,
|
||||
"chargeId": game_charge_list[x]["ticketId"],
|
||||
"price": game_charge_list[x]["price"],
|
||||
"startDate": "2017-12-05 07:00:00.0",
|
||||
"endDate": "2099-12-31 00:00:00.0"
|
||||
})
|
||||
|
||||
return {"length": len(charge_list), "gameChargeList": charge_list}
|
||||
|
||||
def handle_upsert_client_setting_api_request(self, data: Dict) -> Dict:
|
||||
pass
|
||||
|
||||
def handle_upsert_client_upload_api_request(self, data: Dict) -> Dict:
|
||||
pass
|
||||
|
||||
def handle_upsert_client_bookkeeping_api_request(self, data: Dict) -> Dict:
|
||||
pass
|
||||
|
||||
def handle_upsert_client_testmode_api_request(self, data: Dict) -> Dict:
|
||||
pass
|
||||
|
||||
def handle_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||
p = self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||
o = self.data.profile.get_profile_option(data["userId"], self.version)
|
||||
if p is None or o is None: return {} # Register
|
||||
profile = p._asdict()
|
||||
option = o._asdict()
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userName": profile["userName"],
|
||||
"isLogin": False,
|
||||
"lastGameId": profile["lastGameId"],
|
||||
"lastDataVersion": profile["lastDataVersion"],
|
||||
"lastRomVersion": profile["lastRomVersion"],
|
||||
"lastLoginDate": profile["lastLoginDate"],
|
||||
"lastPlayDate": profile["lastPlayDate"],
|
||||
"playerRating": profile["playerRating"],
|
||||
"nameplateId": 0, # Unused
|
||||
"iconId": profile["iconId"],
|
||||
"trophyId": 0, # Unused
|
||||
"partnerId": profile["partnerId"],
|
||||
"frameId": profile["frameId"],
|
||||
"dispRate": option["dispRate"], # 0: all/begin, 1: disprate, 2: dispDan, 3: hide, 4: end
|
||||
"totalAwake": profile["totalAwake"],
|
||||
"isNetMember": profile["isNetMember"],
|
||||
"dailyBonusDate": profile["dailyBonusDate"],
|
||||
"headPhoneVolume": option["headPhoneVolume"],
|
||||
"isInherit": False, # Not sure what this is or does??
|
||||
"banState": profile["banState"] if profile["banState"] is not None else 0 # New with uni+
|
||||
}
|
||||
|
||||
def handle_user_login_api_request(self, data: Dict) -> Dict:
|
||||
profile = self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||
|
||||
if profile is not None:
|
||||
lastLoginDate = profile["lastLoginDate"]
|
||||
loginCt = profile["playCount"]
|
||||
|
||||
if "regionId" in data:
|
||||
self.data.profile.put_profile_region(data["userId"], data["regionId"])
|
||||
else:
|
||||
loginCt = 0
|
||||
lastLoginDate = "2017-12-05 07:00:00.0"
|
||||
|
||||
return {
|
||||
"returnCode": 1,
|
||||
"lastLoginDate": lastLoginDate,
|
||||
"loginCount": loginCt,
|
||||
"consecutiveLoginCount": 0, # We don't really have a way to track this...
|
||||
"loginId": loginCt # Used with the playlog!
|
||||
}
|
||||
|
||||
def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict:
|
||||
user_id = data["userId"]
|
||||
playlog = data["userPlaylog"]
|
||||
|
||||
self.data.score.put_playlog(user_id, playlog)
|
||||
|
||||
def handle_upsert_user_all_api_request(self, data: Dict) -> Dict:
|
||||
user_id = data["userId"]
|
||||
upsert = data["upsertUserAll"]
|
||||
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
upsert["userData"][0]["isNetMember"] = 1
|
||||
upsert["userData"][0].pop("accessCode")
|
||||
self.data.profile.put_profile_detail(user_id, self.version, upsert["userData"][0])
|
||||
|
||||
if "userExtend" in upsert and len(upsert["userExtend"]) > 0:
|
||||
self.data.profile.put_profile_extend(user_id, self.version, upsert["userExtend"][0])
|
||||
|
||||
if "userGhost" in upsert:
|
||||
for ghost in upsert["userGhost"]:
|
||||
self.data.profile.put_profile_extend(user_id, self.version, ghost)
|
||||
|
||||
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
||||
self.data.profile.put_profile_option(user_id, self.version, upsert["userOption"][0])
|
||||
|
||||
if "userRatingList" in upsert and len(upsert["userRatingList"]) > 0:
|
||||
self.data.profile.put_profile_rating(user_id, self.version, upsert["userRatingList"][0])
|
||||
|
||||
if "userActivityList" in upsert and len(upsert["userActivityList"]) > 0:
|
||||
for k,v in upsert["userActivityList"][0].items():
|
||||
for act in v:
|
||||
self.data.profile.put_profile_activity(user_id, act)
|
||||
|
||||
if upsert["isNewCharacterList"] and int(upsert["isNewCharacterList"]) > 0:
|
||||
for char in upsert["userCharacterList"]:
|
||||
self.data.item.put_character(user_id, char["characterId"], char["level"], char["awakening"], char["useCount"])
|
||||
|
||||
if upsert["isNewItemList"] and int(upsert["isNewItemList"]) > 0:
|
||||
for item in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, int(item["itemKind"]), item["itemId"], item["stock"], item["isValid"])
|
||||
|
||||
if upsert["isNewLoginBonusList"] and int(upsert["isNewLoginBonusList"]) > 0:
|
||||
for login_bonus in upsert["userLoginBonusList"]:
|
||||
self.data.item.put_login_bonus(user_id, login_bonus["bonusId"], login_bonus["point"], login_bonus["isCurrent"], login_bonus["isComplete"])
|
||||
|
||||
if upsert["isNewMapList"] and int(upsert["isNewMapList"]) > 0:
|
||||
for map in upsert["userMapList"]:
|
||||
self.data.item.put_map(user_id, map["mapId"], map["distance"], map["isLock"], map["isClear"], map["isComplete"])
|
||||
|
||||
if upsert["isNewMusicDetailList"] and int(upsert["isNewMusicDetailList"]) > 0:
|
||||
for music in upsert["userMusicDetailList"]:
|
||||
self.data.score.put_best_score(user_id, music)
|
||||
|
||||
if upsert["isNewCourseList"] and int(upsert["isNewCourseList"]) > 0:
|
||||
for course in upsert["userCourseList"]:
|
||||
self.data.score.put_course(user_id, course)
|
||||
|
||||
if upsert["isNewFavoriteList"] and int(upsert["isNewFavoriteList"]) > 0:
|
||||
for fav in upsert["userFavoriteList"]:
|
||||
self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"])
|
||||
|
||||
if "isNewFriendSeasonRankingList" in upsert and int(upsert["isNewFriendSeasonRankingList"]) > 0:
|
||||
for fsr in upsert["userFriendSeasonRankingList"]:
|
||||
pass
|
||||
|
||||
def handle_user_logout_api_request(self, data: Dict) -> Dict:
|
||||
pass
|
||||
|
||||
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
|
||||
profile = self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||
if profile is None: return
|
||||
|
||||
profile_dict = profile._asdict()
|
||||
profile_dict.pop("id")
|
||||
profile_dict.pop("user")
|
||||
profile_dict.pop("version")
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userData": profile_dict
|
||||
}
|
||||
|
||||
def handle_get_user_extend_api_request(self, data: Dict) -> Dict:
|
||||
extend = self.data.profile.get_profile_extend(data["userId"], self.version)
|
||||
if extend is None: return
|
||||
|
||||
extend_dict = extend._asdict()
|
||||
extend_dict.pop("id")
|
||||
extend_dict.pop("user")
|
||||
extend_dict.pop("version")
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userExtend": extend_dict
|
||||
}
|
||||
|
||||
def handle_get_user_option_api_request(self, data: Dict) -> Dict:
|
||||
options = self.data.profile.get_profile_option(data["userId"], self.version)
|
||||
if options is None: return
|
||||
|
||||
options_dict = options._asdict()
|
||||
options_dict.pop("id")
|
||||
options_dict.pop("user")
|
||||
options_dict.pop("version")
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userOption": options_dict
|
||||
}
|
||||
|
||||
def handle_get_user_card_api_request(self, data: Dict) -> Dict:
|
||||
return {"userId": data["userId"], "nextIndex": 0, "userCardList": []}
|
||||
|
||||
def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
|
||||
return {"userId": data["userId"], "length": 0, "userChargeList": []}
|
||||
|
||||
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
|
||||
kind = int(data["nextIndex"] / 10000000000)
|
||||
next_idx = int(data["nextIndex"] % 10000000000)
|
||||
user_items = self.data.item.get_items(data["userId"], kind)
|
||||
user_item_list = []
|
||||
next_idx = 0
|
||||
|
||||
for x in range(next_idx, data["maxCount"]):
|
||||
try:
|
||||
user_item_list.append({"item_kind": user_items[x]["item_kind"], "item_id": user_items[x]["item_id"],
|
||||
"stock": user_items[x]["stock"], "isValid": user_items[x]["is_valid"]})
|
||||
except: break
|
||||
|
||||
if len(user_item_list) == data["maxCount"]:
|
||||
next_idx = data["nextIndex"] + data["maxCount"] + 1
|
||||
break
|
||||
|
||||
return {"userId": data["userId"], "nextIndex": next_idx, "itemKind": kind, "userItemList": user_item_list}
|
||||
|
||||
def handle_get_user_character_api_request(self, data: Dict) -> Dict:
|
||||
characters = self.data.item.get_characters(data["userId"])
|
||||
chara_list = []
|
||||
for chara in characters:
|
||||
chara_list.append({
|
||||
"characterId": chara["character_id"],
|
||||
"level": chara["level"],
|
||||
"awakening": chara["awakening"],
|
||||
"useCount": chara["use_count"],
|
||||
})
|
||||
|
||||
return {"userId": data["userId"], "userCharacterList": chara_list}
|
||||
|
||||
def handle_get_user_favorite_api_request(self, data: Dict) -> Dict:
|
||||
favorites = self.data.item.get_favorites(data["userId"], data["itemKind"])
|
||||
if favorites is None: return
|
||||
|
||||
userFavs = []
|
||||
for fav in favorites:
|
||||
userFavs.append({
|
||||
"userId": data["userId"],
|
||||
"itemKind": fav["itemKind"],
|
||||
"itemIdList": fav["itemIdList"]
|
||||
})
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userFavoriteData": userFavs
|
||||
}
|
||||
|
||||
def handle_get_user_ghost_api_request(self, data: Dict) -> Dict:
|
||||
ghost = self.data.profile.get_profile_ghost(data["userId"], self.version)
|
||||
if ghost is None: return
|
||||
|
||||
ghost_dict = ghost._asdict()
|
||||
ghost_dict.pop("user")
|
||||
ghost_dict.pop("id")
|
||||
ghost_dict.pop("version_int")
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userGhost": ghost_dict
|
||||
}
|
||||
|
||||
def handle_get_user_rating_api_request(self, data: Dict) -> Dict:
|
||||
rating = self.data.profile.get_profile_rating(data["userId"], self.version)
|
||||
if rating is None: return
|
||||
|
||||
rating_dict = rating._asdict()
|
||||
rating_dict.pop("user")
|
||||
rating_dict.pop("id")
|
||||
rating_dict.pop("version")
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userRating": rating_dict
|
||||
}
|
||||
|
||||
def handle_get_user_activity_api_request(self, data: Dict) -> Dict:
|
||||
"""
|
||||
kind 1 is playlist, kind 2 is music list
|
||||
"""
|
||||
playlist = self.data.profile.get_profile_activity(data["userId"], 1)
|
||||
musiclist = self.data.profile.get_profile_activity(data["userId"], 2)
|
||||
if playlist is None or musiclist is None: return
|
||||
|
||||
plst = []
|
||||
mlst = []
|
||||
|
||||
for play in playlist:
|
||||
tmp = play._asdict()
|
||||
tmp["id"] = tmp["activityId"]
|
||||
tmp.pop("activityId")
|
||||
tmp.pop("user")
|
||||
plst.append(tmp)
|
||||
|
||||
for music in musiclist:
|
||||
tmp = music._asdict()
|
||||
tmp["id"] = tmp["activityId"]
|
||||
tmp.pop("activityId")
|
||||
tmp.pop("user")
|
||||
mlst.append(tmp)
|
||||
|
||||
return {
|
||||
"userActivity": {
|
||||
"playList": plst,
|
||||
"musicList": mlst
|
||||
}
|
||||
}
|
||||
|
||||
def handle_get_user_course_api_request(self, data: Dict) -> Dict:
|
||||
user_courses = self.data.score.get_courses(data["userId"])
|
||||
|
||||
course_list = []
|
||||
for course in user_courses:
|
||||
tmp = course._asdict()
|
||||
tmp.pop("user")
|
||||
tmp.pop("id")
|
||||
course_list.append(tmp)
|
||||
|
||||
return {"userId": data["userId"], "nextIndex": 0, "userCourseList": course_list}
|
||||
|
||||
def handle_get_user_portrait_api_request(self, data: Dict) -> Dict:
|
||||
# No support for custom pfps
|
||||
return {"length": 0, "userPortraitList": []}
|
||||
|
||||
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_list = []
|
||||
next_index = 0
|
||||
|
||||
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]):
|
||||
try:
|
||||
friend_season_ranking_list.append({
|
||||
"mapId": friend_season_ranking_list[x]["map_id"],
|
||||
"distance": friend_season_ranking_list[x]["distance"],
|
||||
"isLock": friend_season_ranking_list[x]["is_lock"],
|
||||
"isClear": friend_season_ranking_list[x]["is_clear"],
|
||||
"isComplete": friend_season_ranking_list[x]["is_complete"],
|
||||
})
|
||||
except:
|
||||
break
|
||||
|
||||
# We're capped and still have some left to go
|
||||
if len(friend_season_ranking_list) == data["maxCount"] and len(friend_season_ranking) > data["maxCount"] + data["nextIndex"]:
|
||||
next_index = data["maxCount"] + data["nextIndex"]
|
||||
|
||||
return {"userId": data["userId"], "nextIndex": next_index, "userFriendSeasonRankingList": friend_season_ranking_list}
|
||||
|
||||
def handle_get_user_map_api_request(self, data: Dict) -> Dict:
|
||||
maps = self.data.item.get_maps(data["userId"])
|
||||
map_list = []
|
||||
next_index = 0
|
||||
|
||||
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]):
|
||||
try:
|
||||
map_list.append({
|
||||
"mapId": maps[x]["map_id"],
|
||||
"distance": maps[x]["distance"],
|
||||
"isLock": maps[x]["is_lock"],
|
||||
"isClear": maps[x]["is_clear"],
|
||||
"isComplete": maps[x]["is_complete"],
|
||||
})
|
||||
except:
|
||||
break
|
||||
|
||||
# We're capped and still have some left to go
|
||||
if len(map_list) == data["maxCount"] and len(maps) > data["maxCount"] + data["nextIndex"]:
|
||||
next_index = data["maxCount"] + data["nextIndex"]
|
||||
|
||||
return {"userId": data["userId"], "nextIndex": next_index, "userMapList": map_list}
|
||||
|
||||
def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
|
||||
login_bonuses = self.data.item.get_login_bonuses(data["userId"])
|
||||
login_bonus_list = []
|
||||
next_index = 0
|
||||
|
||||
for x in range(data["nextIndex"], data["maxCount"] + data["nextIndex"]):
|
||||
try:
|
||||
login_bonus_list.append({
|
||||
"bonusId": login_bonuses[x]["bonus_id"],
|
||||
"point": login_bonuses[x]["point"],
|
||||
"isCurrent": login_bonuses[x]["is_current"],
|
||||
"isComplete": login_bonuses[x]["is_complete"],
|
||||
})
|
||||
except:
|
||||
break
|
||||
|
||||
# We're capped and still have some left to go
|
||||
if len(login_bonus_list) == data["maxCount"] and len(login_bonuses) > data["maxCount"] + data["nextIndex"]:
|
||||
next_index = data["maxCount"] + data["nextIndex"]
|
||||
|
||||
return {"userId": data["userId"], "nextIndex": next_index, "userLoginBonusList": login_bonus_list}
|
||||
|
||||
def handle_get_user_region_api_request(self, data: Dict) -> Dict:
|
||||
return {"userId": data["userId"], "length": 0, "userRegionList": []}
|
||||
|
||||
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
|
||||
songs = self.data.score.get_best_scores(data["userId"])
|
||||
music_detail_list = []
|
||||
next_index = 0
|
||||
|
||||
if songs is not None:
|
||||
for song in songs:
|
||||
music_detail_list.append({
|
||||
"musicId": song["song_id"],
|
||||
"level": song["chart_id"],
|
||||
"playCount": song["play_count"],
|
||||
"achievement": song["achievement"],
|
||||
"comboStatus": song["combo_status"],
|
||||
"syncStatus": song["sync_status"],
|
||||
"deluxscoreMax": song["dx_score"],
|
||||
"scoreRank": song["score_rank"],
|
||||
})
|
||||
if len(music_detail_list) == data["maxCount"]:
|
||||
next_index = data["maxCount"] + data["nextIndex"]
|
||||
break
|
||||
|
||||
return {"userId": data["userId"], "nextIndex": next_index, "userMusicList": [{"userMusicDetailList": music_detail_list}]}
|
||||
17
titles/mai2/config.py
Normal file
17
titles/mai2/config.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from core.config import CoreConfig
|
||||
|
||||
class Mai2ServerConfig():
|
||||
def __init__(self, parent: "Mai2Config") -> None:
|
||||
self.__config = parent
|
||||
|
||||
@property
|
||||
def enable(self) -> bool:
|
||||
return CoreConfig.get_config_field(self.__config, 'mai2', 'server', 'enable', default=True)
|
||||
|
||||
@property
|
||||
def loglevel(self) -> int:
|
||||
return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'mai2', 'server', 'loglevel', default="info"))
|
||||
|
||||
class Mai2Config(dict):
|
||||
def __init__(self) -> None:
|
||||
self.server = Mai2ServerConfig(self)
|
||||
51
titles/mai2/const.py
Normal file
51
titles/mai2/const.py
Normal file
@@ -0,0 +1,51 @@
|
||||
class Mai2Constants():
|
||||
GRADE = {
|
||||
"D": 0,
|
||||
"C": 1,
|
||||
"B": 2,
|
||||
"BB": 3,
|
||||
"BBB": 4,
|
||||
"A": 5,
|
||||
"AA": 6,
|
||||
"AAA": 7,
|
||||
"S": 8,
|
||||
"S+": 9,
|
||||
"SS": 10,
|
||||
"SS+": 11,
|
||||
"SSS": 12,
|
||||
"SSS+": 13
|
||||
}
|
||||
FC = {
|
||||
"None": 0,
|
||||
"FC": 1,
|
||||
"FC+": 2,
|
||||
"AP": 3,
|
||||
"AP+": 4
|
||||
}
|
||||
SYNC = {
|
||||
"None": 0,
|
||||
"FS": 1,
|
||||
"FS+": 2,
|
||||
"FDX": 3,
|
||||
"FDX+": 4
|
||||
}
|
||||
|
||||
DATE_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
GAME_CODE = "SDEZ"
|
||||
|
||||
CONFIG_NAME = "mai2.yaml"
|
||||
|
||||
VER_MAIMAI_DX = 0
|
||||
VER_MAIMAI_DX_PLUS = 1
|
||||
VER_MAIMAI_DX_SPLASH = 2
|
||||
VER_MAIMAI_DX_SPLASH_PLUS = 3
|
||||
VER_MAIMAI_DX_UNIVERSE = 4
|
||||
VER_MAIMAI_DX_UNIVERSE_PLUS = 5
|
||||
|
||||
VERSION_STRING = ("maimai Delux", "maimai Delux PLUS", "maimai Delux Splash", "maimai Delux Splash PLUS", "maimai Delux Universe",
|
||||
"maimai Delux Universe PLUS")
|
||||
|
||||
@classmethod
|
||||
def game_ver_to_string(cls, ver: int):
|
||||
return cls.VERSION_STRING[ver]
|
||||
12
titles/mai2/database.py
Normal file
12
titles/mai2/database.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from core.data import Data
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.schema import Mai2ItemData, Mai2ProfileData, Mai2StaticData, Mai2ScoreData
|
||||
|
||||
class Mai2Data(Data):
|
||||
def __init__(self, cfg: CoreConfig) -> None:
|
||||
super().__init__(cfg)
|
||||
|
||||
self.profile = Mai2ProfileData(self.config, self.session)
|
||||
self.item = Mai2ItemData(self.config, self.session)
|
||||
self.static = Mai2StaticData(self.config, self.session)
|
||||
self.score = Mai2ScoreData(self.config, self.session)
|
||||
109
titles/mai2/index.py
Normal file
109
titles/mai2/index.py
Normal file
@@ -0,0 +1,109 @@
|
||||
from twisted.web.http import Request
|
||||
import json
|
||||
import inflection
|
||||
import yaml
|
||||
import string
|
||||
import logging, coloredlogs
|
||||
import zlib
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.config import Mai2Config
|
||||
from titles.mai2.const import Mai2Constants
|
||||
from titles.mai2.base import Mai2Base
|
||||
from titles.mai2.plus import Mai2Plus
|
||||
from titles.mai2.splash import Mai2Splash
|
||||
from titles.mai2.splashplus import Mai2SplashPlus
|
||||
from titles.mai2.universe import Mai2Universe
|
||||
from titles.mai2.universeplus import Mai2UniversePlus
|
||||
|
||||
|
||||
class Mai2Servlet():
|
||||
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
|
||||
self.core_cfg = core_cfg
|
||||
self.game_cfg = Mai2Config()
|
||||
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}")))
|
||||
|
||||
self.versions = [
|
||||
Mai2Base(core_cfg, self.game_cfg),
|
||||
Mai2Plus(core_cfg, self.game_cfg),
|
||||
Mai2Splash(core_cfg, self.game_cfg),
|
||||
Mai2SplashPlus(core_cfg, self.game_cfg),
|
||||
Mai2Universe(core_cfg, self.game_cfg),
|
||||
Mai2UniversePlus(core_cfg, self.game_cfg),
|
||||
]
|
||||
|
||||
self.logger = logging.getLogger("mai2")
|
||||
log_fmt_str = "[%(asctime)s] Mai2 | %(levelname)s | %(message)s"
|
||||
log_fmt = logging.Formatter(log_fmt_str)
|
||||
fileHandler = TimedRotatingFileHandler("{0}/{1}.log".format(self.core_cfg.server.log_dir, "mai2"), encoding='utf8',
|
||||
when="d", backupCount=10)
|
||||
|
||||
fileHandler.setFormatter(log_fmt)
|
||||
|
||||
consoleHandler = logging.StreamHandler()
|
||||
consoleHandler.setFormatter(log_fmt)
|
||||
|
||||
self.logger.addHandler(fileHandler)
|
||||
self.logger.addHandler(consoleHandler)
|
||||
|
||||
self.logger.setLevel(self.game_cfg.server.loglevel)
|
||||
coloredlogs.install(level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str)
|
||||
|
||||
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
||||
req_raw = request.content.getvalue()
|
||||
url = request.uri.decode()
|
||||
url_split = url_path.split("/")
|
||||
internal_ver = 0
|
||||
endpoint = url_split[len(url_split) - 1]
|
||||
|
||||
if version < 105: # 1.0
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX
|
||||
elif version >= 105 and version < 110: # Plus
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_PLUS
|
||||
elif version >= 110 and version < 115: # Splash
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH
|
||||
elif version >= 115 and version < 120: # Splash Plus
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS
|
||||
elif version >= 120 and version < 125: # Universe
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
||||
elif version >= 125: # Universe Plus
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
||||
|
||||
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
|
||||
# doing encrypted. The likelyhood of false positives is low but
|
||||
# technically not 0
|
||||
self.logger.error("Encryption not supported at this time")
|
||||
|
||||
try:
|
||||
unzip = zlib.decompress(req_raw)
|
||||
|
||||
except zlib.error as e:
|
||||
self.logger.error(f"Failed to decompress v{version} {endpoint} request -> {e}")
|
||||
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
|
||||
|
||||
req_data = json.loads(unzip)
|
||||
|
||||
self.logger.info(f"v{version} {endpoint} request - {req_data}")
|
||||
|
||||
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
|
||||
|
||||
try:
|
||||
handler = getattr(self.versions[internal_ver], func_to_find)
|
||||
resp = handler(req_data)
|
||||
|
||||
except AttributeError as e:
|
||||
self.logger.warning(f"Unhandled v{version} request {endpoint} - {e}")
|
||||
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error handling v{version} method {endpoint} - {e}")
|
||||
return zlib.compress("{\"stat\": \"0\"}".encode("utf-8"))
|
||||
|
||||
if resp == None:
|
||||
resp = {'returnCode': 1}
|
||||
|
||||
self.logger.info(f"Response {resp}")
|
||||
|
||||
return zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
|
||||
14
titles/mai2/plus.py
Normal file
14
titles/mai2/plus.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from typing import Any, List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
import json
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.base import Mai2Base
|
||||
from titles.mai2.config import Mai2Config
|
||||
from titles.mai2.const import Mai2Constants
|
||||
|
||||
class Mai2Plus(Mai2Base):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = Mai2Constants.VER_MAIMAI_DX_PLUS
|
||||
103
titles/mai2/read.py
Normal file
103
titles/mai2/read.py
Normal file
@@ -0,0 +1,103 @@
|
||||
from decimal import Decimal
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from core.config import CoreConfig
|
||||
from core.data import Data
|
||||
from read import BaseReader
|
||||
from titles.mai2.const import Mai2Constants
|
||||
from titles.mai2.database import Mai2Data
|
||||
|
||||
class Mai2Reader(BaseReader):
|
||||
def __init__(self, config: CoreConfig, version: int, bin_dir: Optional[str], opt_dir: Optional[str], extra: Optional[str]) -> None:
|
||||
super().__init__(config, version, bin_dir, opt_dir, extra)
|
||||
self.data = Mai2Data(config)
|
||||
|
||||
try:
|
||||
self.logger.info(f"Start importer for {Mai2Constants.game_ver_to_string(version)}")
|
||||
except IndexError:
|
||||
self.logger.error(f"Invalid maidx version {version}")
|
||||
exit(1)
|
||||
|
||||
def read(self) -> None:
|
||||
data_dirs = []
|
||||
if self.bin_dir is not None:
|
||||
data_dirs += self.get_data_directories(self.bin_dir)
|
||||
|
||||
if self.opt_dir is not None:
|
||||
data_dirs += self.get_data_directories(self.opt_dir)
|
||||
|
||||
for dir in data_dirs:
|
||||
self.logger.info(f"Read from {dir}")
|
||||
self.get_events(f"{dir}/event")
|
||||
self.read_music(f"{dir}/music")
|
||||
self.read_tickets(f"{dir}/ticket")
|
||||
|
||||
def get_events(self, base_dir: str) -> None:
|
||||
self.logger.info(f"Reading events from {base_dir}...")
|
||||
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for dir in dirs:
|
||||
if os.path.exists(f"{root}/{dir}/Event.xml"):
|
||||
with open(f"{root}/{dir}/Event.xml", encoding="utf-8") as f:
|
||||
|
||||
troot = ET.fromstring(f.read())
|
||||
|
||||
name = troot.find('name').find('str').text
|
||||
id = int(troot.find('name').find('id').text)
|
||||
event_type = int(troot.find('infoType').text)
|
||||
|
||||
self.data.static.put_game_event(self.version, event_type, id, name)
|
||||
self.logger.info(f"Added event {id}...")
|
||||
|
||||
def read_music(self, base_dir: str) -> None:
|
||||
self.logger.info(f"Reading music from {base_dir}...")
|
||||
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for dir in dirs:
|
||||
if os.path.exists(f"{root}/{dir}/Music.xml"):
|
||||
with open(f"{root}/{dir}/Music.xml", encoding="utf-8") as f:
|
||||
troot = ET.fromstring(f.read())
|
||||
|
||||
song_id = int(troot.find('name').find('id').text)
|
||||
title = troot.find('name').find('str').text
|
||||
artist = troot.find('artistName').find('str').text
|
||||
genre = troot.find('genreName').find('str').text
|
||||
bpm = int(troot.find('bpm').text)
|
||||
added_ver = troot.find('AddVersion').find('str').text
|
||||
|
||||
note_data = troot.find('notesData').findall('Notes')
|
||||
|
||||
for dif in note_data:
|
||||
path = dif.find('file').find('path').text
|
||||
if path is not None:
|
||||
if os.path.exists(f"{root}/{dir}/{path}"):
|
||||
chart_id = int(path.split(".")[0].split('_')[1])
|
||||
diff_num = float(f"{dif.find('level').text}.{dif.find('levelDecimal').text}")
|
||||
note_designer = dif.find('notesDesigner').find('str').text
|
||||
|
||||
self.data.static.put_game_music(self.version, song_id, chart_id, title, artist,
|
||||
genre, bpm, added_ver, diff_num, note_designer)
|
||||
|
||||
self.logger.info(f"Added music id {song_id} chart {chart_id}")
|
||||
|
||||
def read_tickets(self, base_dir: str) -> None:
|
||||
self.logger.info(f"Reading tickets from {base_dir}...")
|
||||
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for dir in dirs:
|
||||
if os.path.exists(f"{root}/{dir}/Ticket.xml"):
|
||||
with open(f"{root}/{dir}/Ticket.xml", encoding="utf-8") as f:
|
||||
|
||||
troot = ET.fromstring(f.read())
|
||||
|
||||
name = troot.find('name').find('str').text
|
||||
id = int(troot.find('name').find('id').text)
|
||||
ticket_type = int(troot.find('ticketKind').find('id').text)
|
||||
price = int(troot.find('creditNum').text)
|
||||
|
||||
self.data.static.put_game_ticket(self.version, id, ticket_type, price, name)
|
||||
self.logger.info(f"Added ticket {id}...")
|
||||
6
titles/mai2/schema/__init__.py
Normal file
6
titles/mai2/schema/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from titles.mai2.schema.profile import Mai2ProfileData
|
||||
from titles.mai2.schema.item import Mai2ItemData
|
||||
from titles.mai2.schema.static import Mai2StaticData
|
||||
from titles.mai2.schema.score import Mai2ScoreData
|
||||
|
||||
__all__ = [Mai2ProfileData, Mai2ItemData, Mai2StaticData, Mai2ScoreData]
|
||||
298
titles/mai2/schema/item.py
Normal file
298
titles/mai2/schema/item.py
Normal file
@@ -0,0 +1,298 @@
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
from sqlalchemy.engine import Row
|
||||
|
||||
character = Table(
|
||||
"mai2_item_character",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("character_id", Integer, nullable=False),
|
||||
Column("level", Integer, nullable=False, server_default="1"),
|
||||
Column("awakening", Integer, nullable=False, server_default="0"),
|
||||
Column("use_count", Integer, nullable=False, server_default="0"),
|
||||
UniqueConstraint("user", "character_id", name="mai2_item_character_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
card = Table(
|
||||
"mai2_item_card",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("card_kind", Integer, nullable=False),
|
||||
Column("card_id", Integer, nullable=False),
|
||||
Column("chara_id", Integer, nullable=False),
|
||||
Column("map_id", Integer, nullable=False),
|
||||
Column("start_date", String(255), nullable=False),
|
||||
Column("end_date", String(255), nullable=False),
|
||||
UniqueConstraint("user", "card_kind", "card_id", name="mai2_item_card_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
item = Table(
|
||||
"mai2_item_item",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("item_kind", Integer, nullable=False),
|
||||
Column("item_id", Integer, nullable=False),
|
||||
Column("stock", Integer, nullable=False, server_default="1"),
|
||||
Column("is_valid", Boolean, nullable=False, server_default="1"),
|
||||
UniqueConstraint("user", "item_kind", "item_id", name="mai2_item_item_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
map = Table(
|
||||
"mai2_item_map",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("map_id", Integer, nullable=False),
|
||||
Column("distance", Integer, nullable=False),
|
||||
Column("is_lock", Boolean, nullable=False, server_default="0"),
|
||||
Column("is_clear", Boolean, nullable=False, server_default="0"),
|
||||
Column("is_complete", Boolean, nullable=False, server_default="0"),
|
||||
UniqueConstraint("user", "map_id", name="mai2_item_map_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
login_bonus = Table(
|
||||
"mai2_item_login_bonus",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("bonus_id", Integer, nullable=False),
|
||||
Column("point", Integer, nullable=False),
|
||||
Column("is_current", Boolean, nullable=False, server_default="0"),
|
||||
Column("is_complete", Boolean, nullable=False, server_default="0"),
|
||||
UniqueConstraint("user", "bonus_id", name="mai2_item_login_bonus_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
friend_season_ranking = Table(
|
||||
"mai2_item_friend_season_ranking",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("season_id", Integer, nullable=False),
|
||||
Column("point", Integer, nullable=False),
|
||||
Column("rank", Integer, nullable=False),
|
||||
Column("reward_get", Boolean, nullable=False),
|
||||
Column("user_name", String(8), nullable=False),
|
||||
Column("record_date", String(255), nullable=False),
|
||||
UniqueConstraint("user", "season_id", "user_name", name="mai2_item_login_bonus_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
favorite = Table(
|
||||
"mai2_item_favorite",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("itemKind", Integer, nullable=False),
|
||||
Column("itemIdList", JSON),
|
||||
UniqueConstraint("user", "itemKind", name="mai2_item_favorite_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
charge = Table(
|
||||
"mai2_item_charge",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("charge_id", Integer, nullable=False),
|
||||
Column("stock", Integer, nullable=False),
|
||||
Column("purchase_date", String(255), nullable=False),
|
||||
Column("valid_date", String(255), nullable=False),
|
||||
UniqueConstraint("user", "charge_id", name="mai2_item_charge_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
class Mai2ItemData(BaseData):
|
||||
def put_item(self, user_id: int, item_kind: int, item_id: int, stock: int, is_valid: bool) -> None:
|
||||
sql = insert(item).values(
|
||||
user=user_id,
|
||||
item_kind=item_kind,
|
||||
item_id=item_id,
|
||||
stock=stock,
|
||||
is_valid=is_valid,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
stock=stock,
|
||||
is_valid=is_valid,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_item: failed to insert item! user_id: {user_id}, item_kind: {item_kind}, item_id: {item_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_items(self, user_id: int, item_kind: int = None) -> Optional[List[Row]]:
|
||||
if item_kind is None:
|
||||
sql = item.select(item.c.user == user_id)
|
||||
else:
|
||||
sql = item.select(and_(item.c.user == user_id, item.c.item_kind == item_kind))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_item(self, user_id: int, item_kind: int, item_id: int) -> Optional[Row]:
|
||||
sql = item.select(and_(item.c.user == user_id, item.c.item_kind == item_kind, item.c.item_id == item_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_login_bonus(self, user_id: int, bonus_id: int, point: int, is_current: bool, is_complete: bool) -> None:
|
||||
sql = insert(login_bonus).values(
|
||||
user=user_id,
|
||||
bonus_id=bonus_id,
|
||||
point=point,
|
||||
is_current=is_current,
|
||||
is_complete=is_complete,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
point=point,
|
||||
is_current=is_current,
|
||||
is_complete=is_complete,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_login_bonus: failed to insert item! user_id: {user_id}, bonus_id: {bonus_id}, point: {point}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_login_bonuses(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = login_bonus.select(login_bonus.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_login_bonus(self, user_id: int, bonus_id: int) -> Optional[Row]:
|
||||
sql = login_bonus.select(and_(login_bonus.c.user == user_id, login_bonus.c.bonus_id == bonus_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_map(self, user_id: int, map_id: int, distance: int, is_lock: bool, is_clear: bool, is_complete: bool) -> None:
|
||||
sql = insert(map).values(
|
||||
user=user_id,
|
||||
map_id=map_id,
|
||||
distance=distance,
|
||||
is_lock=is_lock,
|
||||
is_clear=is_clear,
|
||||
is_complete=is_complete,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
distance=distance,
|
||||
is_lock=is_lock,
|
||||
is_clear=is_clear,
|
||||
is_complete=is_complete,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_map: failed to insert item! user_id: {user_id}, map_id: {map_id}, distance: {distance}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_maps(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = map.select(map.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
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))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_character(self, user_id: int, character_id: int, level: int, awakening: int, use_count: int) -> None:
|
||||
sql = insert(character).values(
|
||||
user=user_id,
|
||||
character_id=character_id,
|
||||
level=level,
|
||||
awakening=awakening,
|
||||
use_count=use_count,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
level=level,
|
||||
awakening=awakening,
|
||||
use_count=use_count,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_character: failed to insert item! user_id: {user_id}, character_id: {character_id}, level: {level}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_characters(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = character.select(character.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_character(self, user_id: int, character_id: int) -> Optional[Row]:
|
||||
sql = character.select(and_(character.c.user == user_id, character.c.character_id == character_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_friend_season_ranking(self, user_id: int) -> Optional[Row]:
|
||||
sql = friend_season_ranking.select(friend_season_ranking.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_favorite(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
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
item_id_list=item_id_list
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_favorite: failed to insert item! user_id: {user_id}, kind: {kind}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_favorites(self, user_id: int, kind: int = None) -> Optional[Row]:
|
||||
if kind is None:
|
||||
sql = favorite.select(favorite.c.user == user_id)
|
||||
else:
|
||||
sql = favorite.select(and_(
|
||||
favorite.c.user == user_id,
|
||||
favorite.c.itemKind == kind
|
||||
))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:return None
|
||||
return result.fetchall()
|
||||
402
titles/mai2/schema/profile.py
Normal file
402
titles/mai2/schema/profile.py
Normal file
@@ -0,0 +1,402 @@
|
||||
from core.data.schema import BaseData, metadata
|
||||
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.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
from datetime import datetime
|
||||
|
||||
detail = Table(
|
||||
"mai2_profile_detail",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("userName", String(25)),
|
||||
Column("isNetMember", Integer),
|
||||
Column("iconId", Integer),
|
||||
Column("plateId", Integer),
|
||||
Column("titleId", Integer),
|
||||
Column("partnerId", Integer),
|
||||
Column("frameId", Integer),
|
||||
Column("selectMapId", Integer),
|
||||
Column("totalAwake", Integer),
|
||||
Column("gradeRating", Integer),
|
||||
Column("musicRating", Integer),
|
||||
Column("playerRating", Integer),
|
||||
Column("highestRating", Integer),
|
||||
Column("gradeRank", Integer),
|
||||
Column("classRank", Integer),
|
||||
Column("courseRank", Integer),
|
||||
Column("charaSlot", JSON),
|
||||
Column("charaLockSlot", JSON),
|
||||
Column("contentBit", BigInteger),
|
||||
Column("playCount", Integer),
|
||||
Column("eventWatchedDate", String(25)),
|
||||
Column("lastGameId", String(25)),
|
||||
Column("lastRomVersion", String(25)),
|
||||
Column("lastDataVersion", String(25)),
|
||||
Column("lastLoginDate", String(25)),
|
||||
Column("lastPairLoginDate", String(25)), # new with uni+
|
||||
Column("lastPlayDate", String(25)),
|
||||
Column("lastTrialPlayDate", String(25)), # new with uni+
|
||||
Column("lastPlayCredit", Integer),
|
||||
Column("lastPlayMode", Integer),
|
||||
Column("lastPlaceId", Integer),
|
||||
Column("lastPlaceName", String(25)),
|
||||
Column("lastAllNetId", Integer),
|
||||
Column("lastRegionId", Integer),
|
||||
Column("lastRegionName", String(25)),
|
||||
Column("lastClientId", String(25)),
|
||||
Column("lastCountryCode", String(25)),
|
||||
Column("lastSelectEMoney", Integer),
|
||||
Column("lastSelectTicket", Integer),
|
||||
Column("lastSelectCourse", Integer),
|
||||
Column("lastCountCourse", Integer),
|
||||
Column("firstGameId", String(25)),
|
||||
Column("firstRomVersion", String(25)),
|
||||
Column("firstDataVersion", String(25)),
|
||||
Column("firstPlayDate", String(25)),
|
||||
Column("compatibleCmVersion", String(25)),
|
||||
Column("dailyBonusDate", String(25)),
|
||||
Column("dailyCourseBonusDate", String(25)),
|
||||
Column("playVsCount", Integer),
|
||||
Column("playSyncCount", Integer),
|
||||
Column("winCount", Integer),
|
||||
Column("helpCount", Integer),
|
||||
Column("comboCount", Integer),
|
||||
Column("totalDeluxscore", BigInteger),
|
||||
Column("totalBasicDeluxscore", BigInteger),
|
||||
Column("totalAdvancedDeluxscore", BigInteger),
|
||||
Column("totalExpertDeluxscore", BigInteger),
|
||||
Column("totalMasterDeluxscore", BigInteger),
|
||||
Column("totalReMasterDeluxscore", BigInteger),
|
||||
Column("totalSync", Integer),
|
||||
Column("totalBasicSync", Integer),
|
||||
Column("totalAdvancedSync", Integer),
|
||||
Column("totalExpertSync", Integer),
|
||||
Column("totalMasterSync", Integer),
|
||||
Column("totalReMasterSync", Integer),
|
||||
Column("totalAchievement", BigInteger),
|
||||
Column("totalBasicAchievement", BigInteger),
|
||||
Column("totalAdvancedAchievement", BigInteger),
|
||||
Column("totalExpertAchievement", BigInteger),
|
||||
Column("totalMasterAchievement", BigInteger),
|
||||
Column("totalReMasterAchievement", BigInteger),
|
||||
Column("playerOldRating", BigInteger),
|
||||
Column("playerNewRating", BigInteger),
|
||||
Column("dateTime", BigInteger),
|
||||
Column("banState", Integer), # new with uni+
|
||||
UniqueConstraint("user", "version", name="mai2_profile_detail_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
ghost = Table(
|
||||
"mai2_profile_ghost",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("version_int", Integer, nullable=False),
|
||||
Column("name", String(25)),
|
||||
Column("iconId", Integer),
|
||||
Column("plateId", Integer),
|
||||
Column("titleId", Integer),
|
||||
Column("rate", Integer),
|
||||
Column("udemaeRate", Integer),
|
||||
Column("courseRank", Integer),
|
||||
Column("classRank", Integer),
|
||||
Column("classValue", Integer),
|
||||
Column("playDatetime", String(25)),
|
||||
Column("shopId", Integer),
|
||||
Column("regionCode", Integer),
|
||||
Column("typeId", Integer),
|
||||
Column("musicId", 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"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
extend = Table(
|
||||
"mai2_profile_extend",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("selectMusicId", Integer),
|
||||
Column("selectDifficultyId", Integer),
|
||||
Column("categoryIndex", Integer),
|
||||
Column("musicIndex", Integer),
|
||||
Column("extraFlag", Integer),
|
||||
Column("selectScoreType", Integer),
|
||||
Column("extendContentBit", BigInteger),
|
||||
Column("isPhotoAgree", Boolean),
|
||||
Column("isGotoCodeRead", Boolean),
|
||||
Column("selectResultDetails", Boolean),
|
||||
Column("sortCategorySetting", Integer),
|
||||
Column("sortMusicSetting", Integer),
|
||||
Column("selectedCardList", JSON),
|
||||
Column("encountMapNpcList", JSON),
|
||||
UniqueConstraint("user", "version", name="mai2_profile_extend_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
option = Table(
|
||||
"mai2_profile_option",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("selectMusicId", Integer),
|
||||
Column("optionKind", Integer),
|
||||
Column("noteSpeed", Integer),
|
||||
Column("slideSpeed", Integer),
|
||||
Column("touchSpeed", Integer),
|
||||
Column("tapDesign", Integer),
|
||||
Column("holdDesign", Integer),
|
||||
Column("slideDesign", Integer),
|
||||
Column("starType", Integer),
|
||||
Column("outlineDesign", Integer),
|
||||
Column("noteSize", Integer),
|
||||
Column("slideSize", Integer),
|
||||
Column("touchSize", Integer),
|
||||
Column("starRotate", Integer),
|
||||
Column("dispCenter", Integer),
|
||||
Column("dispChain", Integer),
|
||||
Column("dispRate", Integer),
|
||||
Column("dispBar", Integer),
|
||||
Column("touchEffect", Integer),
|
||||
Column("submonitorAnimation", Integer),
|
||||
Column("submonitorAchive", Integer),
|
||||
Column("submonitorAppeal", Integer),
|
||||
Column("matching", Integer),
|
||||
Column("trackSkip", Integer),
|
||||
Column("brightness", Integer),
|
||||
Column("mirrorMode", Integer),
|
||||
Column("dispJudge", Integer),
|
||||
Column("dispJudgePos", Integer),
|
||||
Column("dispJudgeTouchPos", Integer),
|
||||
Column("adjustTiming", Integer),
|
||||
Column("judgeTiming", Integer),
|
||||
Column("ansVolume", Integer),
|
||||
Column("tapHoldVolume", Integer),
|
||||
Column("criticalSe", Integer),
|
||||
Column("breakSe", Integer),
|
||||
Column("breakVolume", Integer),
|
||||
Column("exSe", Integer),
|
||||
Column("exVolume", Integer),
|
||||
Column("slideSe", Integer),
|
||||
Column("slideVolume", Integer),
|
||||
Column("touchHoldVolume", Integer),
|
||||
Column("damageSeVolume", Integer),
|
||||
Column("headPhoneVolume", Integer),
|
||||
Column("sortTab", Integer),
|
||||
Column("sortMusic", Integer),
|
||||
UniqueConstraint("user", "version", name="mai2_profile_option_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
rating = Table(
|
||||
"mai2_profile_rating",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("rating", Integer),
|
||||
Column("ratingList", JSON),
|
||||
Column("newRatingList", JSON),
|
||||
Column("nextRatingList", JSON),
|
||||
Column("nextNewRatingList", JSON),
|
||||
Column("udemae", JSON),
|
||||
UniqueConstraint("user", "version", name="mai2_profile_rating_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
region = Table(
|
||||
"mai2_profile_region",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("regionId", Integer),
|
||||
Column("playCount", Integer, server_default="1"),
|
||||
Column("created", String(25)),
|
||||
UniqueConstraint("user", "regionId", name="mai2_profile_region_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
activity = Table(
|
||||
"mai2_profile_activity",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("kind", Integer, nullable=False),
|
||||
Column("activityId", Integer, nullable=False),
|
||||
Column("param1", Integer, nullable=False),
|
||||
Column("param2", Integer, nullable=False),
|
||||
Column("param3", Integer, nullable=False),
|
||||
Column("param4", Integer, nullable=False),
|
||||
Column("sortNumber", Integer, nullable=False),
|
||||
UniqueConstraint("user", "kind", "activityId", name="mai2_profile_activity_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
class Mai2ProfileData(BaseData):
|
||||
def put_profile_detail(self, user_id: int, version: int, detail_data: Dict) -> Optional[Row]:
|
||||
detail_data["user"] = user_id
|
||||
detail_data["version"] = version
|
||||
sql = insert(detail).values(**detail_data)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(**detail_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_profile: Failed to create profile! user_id {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_profile_detail(self, user_id: int, version: int) -> Optional[Row]:
|
||||
sql = select(detail).where(and_(detail.c.user == user_id, detail.c.version == version))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_profile_ghost(self, user_id: int, version: int, ghost_data: Dict) -> Optional[int]:
|
||||
ghost_data["user"] = user_id
|
||||
ghost_data["version_int"] = version
|
||||
|
||||
sql = insert(ghost).values(**ghost_data)
|
||||
conflict = sql.on_duplicate_key_update(**ghost_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_profile_ghost: failed to update! {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_profile_ghost(self, user_id: int, version: int) -> Optional[Row]:
|
||||
sql = select(ghost).where(and_(ghost.c.user == user_id, ghost.c.version_int == version))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_profile_extend(self, user_id: int, version: int, extend_data: Dict) -> Optional[int]:
|
||||
extend_data["user"] = user_id
|
||||
extend_data["version"] = version
|
||||
|
||||
sql = insert(extend).values(**extend_data)
|
||||
conflict = sql.on_duplicate_key_update(**extend_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_profile_extend: failed to update! {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_profile_extend(self, user_id: int, version: int) -> Optional[Row]:
|
||||
sql = select(extend).where(and_(extend.c.user == user_id, extend.c.version == version))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_profile_option(self, user_id: int, version: int, option_data: Dict) -> Optional[int]:
|
||||
option_data["user"] = user_id
|
||||
option_data["version"] = version
|
||||
|
||||
sql = insert(option).values(**option_data)
|
||||
conflict = sql.on_duplicate_key_update(**option_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_profile_option: failed to update! {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_profile_option(self, user_id: int, version: int) -> Optional[Row]:
|
||||
sql = select(option).where(and_(option.c.user == user_id, option.c.version == version))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_profile_rating(self, user_id: int, version: int, rating_data: Dict) -> Optional[int]:
|
||||
rating_data["user"] = user_id
|
||||
rating_data["version"] = version
|
||||
|
||||
sql = insert(rating).values(**rating_data)
|
||||
conflict = sql.on_duplicate_key_update(**rating_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_profile_rating: failed to update! {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_profile_rating(self, user_id: int, version: int) -> Optional[Row]:
|
||||
sql = select(rating).where(and_(rating.c.user == user_id, rating.c.version == version))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_profile_region(self, user_id: int, region_id: int) -> Optional[int]:
|
||||
sql = insert(region).values(
|
||||
user = user_id,
|
||||
regionId = region_id,
|
||||
created = datetime.strftime(datetime.now(), Mai2Constants.DATE_TIME_FORMAT)
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
playCount = region.c.playCount + 1
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_region: failed to update! {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_regions(self, user_id: int) -> Optional[List[Dict]]:
|
||||
sql = select(region).where(region.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_profile_activity(self, user_id: int, activity_data: Dict) -> Optional[int]:
|
||||
if "id" in activity_data:
|
||||
activity_data["activityId"] = activity_data["id"]
|
||||
activity_data.pop("id")
|
||||
|
||||
activity_data["user"] = user_id
|
||||
|
||||
sql = insert(activity).values(**activity_data)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(**activity_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_profile_activity: failed to update! user_id: {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_profile_activity(self, user_id: int, kind: int = None) -> Optional[Row]:
|
||||
sql = activity.select(
|
||||
and_(
|
||||
activity.c.user == user_id,
|
||||
(activity.c.kind == kind) if kind is not None else True,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:return None
|
||||
return result.fetchone()
|
||||
226
titles/mai2/schema/score.py
Normal file
226
titles/mai2/schema/score.py
Normal file
@@ -0,0 +1,226 @@
|
||||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
best_score = Table(
|
||||
"mai2_score_best",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("achievement", Integer),
|
||||
Column("comboStatus", Integer),
|
||||
Column("syncStatus", Integer),
|
||||
Column("deluxscoreMax", Integer),
|
||||
Column("scoreRank", Integer),
|
||||
UniqueConstraint("user", "musicId", "level", name="mai2_score_best_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"mai2_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("userId", BigInteger),
|
||||
Column("orderId", Integer),
|
||||
Column("playlogId", BigInteger),
|
||||
Column("version", Integer),
|
||||
Column("placeId", Integer),
|
||||
Column("placeName", String(255)),
|
||||
Column("loginDate", BigInteger),
|
||||
Column("playDate", String(255)),
|
||||
Column("userPlayDate", String(255)),
|
||||
Column("type", Integer),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("trackNo", Integer),
|
||||
Column("vsMode", Integer),
|
||||
Column("vsUserName", String(255)),
|
||||
Column("vsStatus", Integer),
|
||||
Column("vsUserRating", Integer),
|
||||
Column("vsUserAchievement", Integer),
|
||||
Column("vsUserGradeRank", Integer),
|
||||
Column("vsRank", Integer),
|
||||
Column("playerNum", Integer),
|
||||
Column("playedUserId1", BigInteger),
|
||||
Column("playedUserName1", String(255)),
|
||||
Column("playedMusicLevel1", Integer),
|
||||
Column("playedUserId2", BigInteger),
|
||||
Column("playedUserName2", String(255)),
|
||||
Column("playedMusicLevel2", Integer),
|
||||
Column("playedUserId3", BigInteger),
|
||||
Column("playedUserName3", String(255)),
|
||||
Column("playedMusicLevel3", Integer),
|
||||
Column("characterId1", Integer),
|
||||
Column("characterLevel1", Integer),
|
||||
Column("characterAwakening1", Integer),
|
||||
Column("characterId2", Integer),
|
||||
Column("characterLevel2", Integer),
|
||||
Column("characterAwakening2", Integer),
|
||||
Column("characterId3", Integer),
|
||||
Column("characterLevel3", Integer),
|
||||
Column("characterAwakening3", Integer),
|
||||
Column("characterId4", Integer),
|
||||
Column("characterLevel4", Integer),
|
||||
Column("characterAwakening4", Integer),
|
||||
Column("characterId5", Integer),
|
||||
Column("characterLevel5", Integer),
|
||||
Column("characterAwakening5", Integer),
|
||||
Column("achievement", Integer),
|
||||
Column("deluxscore", Integer),
|
||||
Column("scoreRank", Integer),
|
||||
Column("maxCombo", Integer),
|
||||
Column("totalCombo", Integer),
|
||||
Column("maxSync", Integer),
|
||||
Column("totalSync", Integer),
|
||||
Column("tapCriticalPerfect", Integer),
|
||||
Column("tapPerfect", Integer),
|
||||
Column("tapGreat", Integer),
|
||||
Column("tapGood", Integer),
|
||||
Column("tapMiss", Integer),
|
||||
Column("holdCriticalPerfect", Integer),
|
||||
Column("holdPerfect", Integer),
|
||||
Column("holdGreat", Integer),
|
||||
Column("holdGood", Integer),
|
||||
Column("holdMiss", Integer),
|
||||
Column("slideCriticalPerfect", Integer),
|
||||
Column("slidePerfect", Integer),
|
||||
Column("slideGreat", Integer),
|
||||
Column("slideGood", Integer),
|
||||
Column("slideMiss", Integer),
|
||||
Column("touchCriticalPerfect", Integer),
|
||||
Column("touchPerfect", Integer),
|
||||
Column("touchGreat", Integer),
|
||||
Column("touchGood", Integer),
|
||||
Column("touchMiss", Integer),
|
||||
Column("breakCriticalPerfect", Integer),
|
||||
Column("breakPerfect", Integer),
|
||||
Column("breakGreat", Integer),
|
||||
Column("breakGood", Integer),
|
||||
Column("breakMiss", Integer),
|
||||
Column("isTap", Boolean),
|
||||
Column("isHold", Boolean),
|
||||
Column("isSlide", Boolean),
|
||||
Column("isTouch", Boolean),
|
||||
Column("isBreak", Boolean),
|
||||
Column("isCriticalDisp", Boolean),
|
||||
Column("isFastLateDisp", Boolean),
|
||||
Column("fastCount", Integer),
|
||||
Column("lateCount", Integer),
|
||||
Column("isAchieveNewRecord", Boolean),
|
||||
Column("isDeluxscoreNewRecord", Boolean),
|
||||
Column("comboStatus", Integer),
|
||||
Column("syncStatus", Integer),
|
||||
Column("isClear", Boolean),
|
||||
Column("beforeRating", Integer),
|
||||
Column("afterRating", Integer),
|
||||
Column("beforeGrade", Integer),
|
||||
Column("afterGrade", Integer),
|
||||
Column("afterGradeRank", Integer),
|
||||
Column("beforeDeluxRating", Integer),
|
||||
Column("afterDeluxRating", Integer),
|
||||
Column("isPlayTutorial", Boolean),
|
||||
Column("isEventMode", Boolean),
|
||||
Column("isFreedomMode", Boolean),
|
||||
Column("playMode", Integer),
|
||||
Column("isNewFree", Boolean),
|
||||
Column("extNum1", Integer),
|
||||
Column("extNum2", Integer),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
course = Table(
|
||||
"mai2_score_course",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
|
||||
Column("courseId", Integer),
|
||||
Column("isLastClear", Boolean),
|
||||
Column("totalRestlife", Integer),
|
||||
Column("totalAchievement", Integer),
|
||||
Column("totalDeluxscore", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("clearDate", String(25)),
|
||||
Column("lastPlayDate", String(25)),
|
||||
Column("bestAchievement", Integer),
|
||||
Column("bestAchievementDate", String(25)),
|
||||
Column("bestDeluxscore", Integer),
|
||||
Column("bestDeluxscoreDate", String(25)),
|
||||
UniqueConstraint("user", "courseId", name="mai2_score_best_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
class Mai2ScoreData(BaseData):
|
||||
def put_best_score(self, user_id: int, score_data: Dict) -> Optional[int]:
|
||||
sql = insert(best_score).values(**score_data)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(**score_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"put_best_score: Failed to insert best score! user_id {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_best_scores(self, user_id: int, song_id: int = None) -> Optional[List[Row]]:
|
||||
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
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_best_score(self, user_id: int, song_id: int, chart_id: int) -> Optional[Row]:
|
||||
sql = best_score.select(
|
||||
and_(
|
||||
best_score.c.user == user_id,
|
||||
best_score.c.song_id == song_id,
|
||||
best_score.c.chart_id == chart_id
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_playlog(self, user_id: int, playlog_data: Dict) -> Optional[int]:
|
||||
sql = insert(playlog).values(**playlog_data)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(**playlog_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"put_playlog: Failed to insert! user_id {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_course(self, user_id: int, course_data: Dict) -> Optional[int]:
|
||||
sql = insert(course).values(**course_data)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(**course_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"put_course: Failed to insert! user_id {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_courses(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = course.select(best_score.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
||||
178
titles/mai2/schema/static.py
Normal file
178
titles/mai2/schema/static.py
Normal file
@@ -0,0 +1,178 @@
|
||||
from core.data.schema.base import BaseData, metadata
|
||||
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
event = Table(
|
||||
"mai2_static_event",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer,nullable=False),
|
||||
Column("eventId", Integer),
|
||||
Column("type", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "eventId", "type", name="mai2_static_event_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
music = Table(
|
||||
"mai2_static_music",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer,nullable=False),
|
||||
Column("songId", Integer),
|
||||
Column("chartId", Integer),
|
||||
Column("title", String(255)),
|
||||
Column("artist", String(255)),
|
||||
Column("genre", String(255)),
|
||||
Column("bpm", Integer),
|
||||
Column("addedVersion", String(255)),
|
||||
Column("difficulty", Float),
|
||||
Column("noteDesigner", String(255)),
|
||||
UniqueConstraint("songId", "chartId", "version", name="mai2_static_music_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
ticket = Table(
|
||||
"mai2_static_ticket",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer,nullable=False),
|
||||
Column("ticketId", Integer),
|
||||
Column("kind", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("price", Integer, server_default="1"),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version","ticketId", name="mai2_static_ticket_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
class Mai2StaticData(BaseData):
|
||||
def put_game_event(self, version: int, type: int, event_id: int, name: str) -> Optional[int]:
|
||||
sql = insert(event).values(
|
||||
version = version,
|
||||
type = type,
|
||||
eventId = event_id,
|
||||
name = name,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
eventId = event_id
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warning(f"put_game_event: Failed to insert event! event_id {event_id} type {type} name {name}")
|
||||
return result.lastrowid
|
||||
|
||||
def get_game_events(self, version: int) -> Optional[List[Row]]:
|
||||
sql = event.select(event.c.version == version)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_enabled_events(self, version: int) -> Optional[List[Row]]:
|
||||
sql = select(event).where(and_(
|
||||
event.c.version == version,
|
||||
event.c.enabled == True
|
||||
))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def toggle_game_events(self, version: int, event_id: int, toggle: bool) -> Optional[List]:
|
||||
sql = event.update(and_(event.c.version == version, event.c.event_id == event_id)).values(
|
||||
enabled = int(toggle)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warning(f"toggle_game_events: Failed to update event! event_id {event_id} toggle {toggle}")
|
||||
return result.last_updated_params()
|
||||
|
||||
def put_game_music(self, version: int, song_id: int, chart_id: int, title: str, artist: str,
|
||||
genre: str, bpm: str, added_version: str, difficulty: float, note_designer: str) -> None:
|
||||
sql = insert(music).values(
|
||||
version = version,
|
||||
songId = song_id,
|
||||
chartId = chart_id,
|
||||
title = title,
|
||||
artist = artist,
|
||||
genre = genre,
|
||||
bpm = bpm,
|
||||
addedVersion = added_version,
|
||||
difficulty = difficulty,
|
||||
noteDesigner = note_designer,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
title = title,
|
||||
artist = artist,
|
||||
genre = genre,
|
||||
bpm = bpm,
|
||||
addedVersion = added_version,
|
||||
difficulty = difficulty,
|
||||
noteDesigner = note_designer,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert song {song_id} chart {chart_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_game_ticket(self, version: int, ticket_id: int, ticket_type: int, ticket_price: int, name: str) -> Optional[int]:
|
||||
sql = insert(ticket).values(
|
||||
version = version,
|
||||
ticketId = ticket_id,
|
||||
kind = ticket_type,
|
||||
price = ticket_price,
|
||||
name = name
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
price = ticket_price
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert charge {ticket_id} type {ticket_type}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_enabled_tickets(self, version: int, kind: int = None) -> Optional[List[Row]]:
|
||||
if kind is not None:
|
||||
sql = select(ticket).where(and_(
|
||||
ticket.c.version == version,
|
||||
ticket.c.enabled == True,
|
||||
ticket.c.kind == kind
|
||||
))
|
||||
else:
|
||||
sql = select(ticket).where(and_(
|
||||
ticket.c.version == version,
|
||||
ticket.c.enabled == True
|
||||
))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_music_chart(self, version: int, song_id: int, chart_id: int) -> Optional[List[Row]]:
|
||||
sql = select(music).where(and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
music.c.chartId == chart_id
|
||||
))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchone()
|
||||
14
titles/mai2/splash.py
Normal file
14
titles/mai2/splash.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from typing import Any, List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
import json
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.base import Mai2Base
|
||||
from titles.mai2.config import Mai2Config
|
||||
from titles.mai2.const import Mai2Constants
|
||||
|
||||
class Mai2Splash(Mai2Base):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH
|
||||
14
titles/mai2/splashplus.py
Normal file
14
titles/mai2/splashplus.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from typing import Any, List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
import json
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.base import Mai2Base
|
||||
from titles.mai2.config import Mai2Config
|
||||
from titles.mai2.const import Mai2Constants
|
||||
|
||||
class Mai2SplashPlus(Mai2Base):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = Mai2Constants.VER_MAIMAI_DX_SPLASH_PLUS
|
||||
14
titles/mai2/universe.py
Normal file
14
titles/mai2/universe.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from typing import Any, List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
import json
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.base import Mai2Base
|
||||
from titles.mai2.const import Mai2Constants
|
||||
from titles.mai2.config import Mai2Config
|
||||
|
||||
class Mai2Universe(Mai2Base):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
||||
14
titles/mai2/universeplus.py
Normal file
14
titles/mai2/universeplus.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from typing import Any, List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
import json
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.base import Mai2Base
|
||||
from titles.mai2.const import Mai2Constants
|
||||
from titles.mai2.config import Mai2Config
|
||||
|
||||
class Mai2UniversePlus(Mai2Base):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE_PLUS
|
||||
Reference in New Issue
Block a user