[O] Mai2 migrate

pull/108/head
Azalea 2025-01-11 20:53:59 -05:00
parent 401d182fc6
commit c5b40f64e4
3 changed files with 264 additions and 299 deletions

View File

@ -218,6 +218,7 @@ fun Str.splitLines() = replace("\r\n", "\n").split('\n')
@OptIn(ExperimentalStdlibApi::class)
fun Str.md5() = MD5.digest(toByteArray(Charsets.UTF_8)).toHexString()
fun Str.fromChusanUsername() = String(this.toByteArray(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8)
fun Str.truncate(len: Int) = if (this.length > len) this.take(len) + "..." else this
// Coroutine
suspend fun <T> async(block: suspend kotlinx.coroutines.CoroutineScope.() -> T): T = withContext(Dispatchers.IO) { block() }
@ -250,4 +251,3 @@ val <S> Pair<*, S>.r get() = component2()
// Database
val Query.exec get() = resultList.map { (it as Array<*>).toList() }

View File

@ -0,0 +1,240 @@
package icu.samnyan.aqua.sega.maimai2
import ext.*
import icu.samnyan.aqua.sega.general.PagedHandler
fun Maimai2ServletController.initApis() {
// Used because maimai does not actually require paging implementation
fun String.unpaged(key: String? = null, fn: PagedHandler) {
val k = key ?: (this.replace("get", "").firstCharLower() + "List")
this {
fn(this).let { mapOf("userId" to uid, "nextIndex" to 0, "length" to it.size, k to it) }
}
}
"GetUserExtend" { mapOf(
"uid" to uid,
"userExtend" to (db.userExtend.findSingleByUser_Card_ExtId(uid)() ?: (404 - "User not found"))
) }
"GetUserData" { mapOf(
"uid" to uid,
"userData" to (db.userData.findByCardExtId(uid)() ?: (404 - "User not found")),
"banState" to 0
) }
"GetUserLoginBonus".unpaged { db.userLoginBonus.findByUser_Card_ExtId(uid) }
"GetUserMap".unpaged { db.userMap.findByUser_Card_ExtId(uid) }
"GetUserCard".unpaged { db.userCard.findByUser_Card_ExtId(uid) }
"GetUserCharge".unpaged { db.userCharge.findByUser_Card_ExtId(uid) }
"GetUserFriendSeasonRanking".unpaged { db.userFriendSeasonRanking.findByUser_Card_ExtId(uid) }
"GetUserCourse".unpaged { db.userCourse.findByUser_Card_ExtId(uid) }
"GetUserMusic".unpaged {
ls(mapOf("userMusicDetailList" to db.userMusicDetail.findByUser_Card_ExtId(uid)))
}
"GetUserFavorite" { mapOf(
"uid" to uid,
"userFavorite" to db.userFavorite.findByUser_Card_ExtIdAndItemKind(uid, data["itemKind"] as Int)
) }
"GetUserActivity" {
db.userAct.findByUser_Card_ExtId(uid).let { act -> mapOf(
"userActivity" to mapOf(
"playList" to act.filter { it.kind == 1 },
"musicList" to act.filter { it.kind == 2 }
)
) }
}
// Maimai only request for event type 1
"GetGameEvent" static { mapOf("type" to 1, "gameEventList" to db.gameEvent.findByEnable(true)) }
"GetGameCharge" static { db.gameCharge.findAll().let { mapOf("length" to it.size, "gameChargeList" to it) } }
"GetUserRivalData" {
val rivalId = parsing { data["rivalId"]!!.long }
// rivalId should store and fetch with the id column of table rather than card_ext_id
// or user will be able to get others' ext_id by setting them as rival
mapOf(
"uid" to uid,
"userRivalData" to mapOf(
"rivalId" to rivalId,
"rivalName" to (db.userData.findById(rivalId)()?.userName ?: "")
)
)
}
"GetUserOption" { mapOf(
"uid" to uid,
"userOption" to (db.userOption.findSingleByUser_Card_ExtId(uid)() ?: (404 - "User not found"))
) }
"CreateToken" static { """{"Bearer":"meow"}""" }
"CMUpsertUserPrintlog" static { """{"returnCode":1,"orderId":"0","serialId":"FAKECARDIMAG12345678"}""" }
"CMGetSellingCard" static { db.gameSellingCard.findAll().let {
mapOf("length" to it.size, "sellingCardList" to it)
} }
"CMGetUserCharacter" { db.userCharacter.findByUser_Card_ExtId(uid).let {
mapOf(
"returnCode" to 1,
"length" to it.size,
"userCharacterList" to it
)
} }
"CMGetUserPreview" { db.userData.findByCardExtId(uid)()?.let {
mapOf(
"uid" to uid,
"userName" to it.userName,
"rating" to it.playerRating,
"lastDataVersion" to it.lastDataVersion,
"isLogin" to false,
"isExistSellingCard" to false
)
} ?: (404 - "User not found") }
"GetUserPreview" {
val d = db.userData.findByCardExtId(uid)() ?: (404 - "User not found")
val option = db.userOption.findSingleByUser_Card_ExtId(uid)()
mapOf(
"uid" to uid,
"userName" to d.userName,
"isLogin" to false,
"lastGameId" to d.lastGameId,
"lastDataVersion" to d.lastDataVersion,
"lastRomVersion" to d.lastRomVersion,
"lastLoginDate" to d.lastPlayDate,
"lastPlayDate" to d.lastPlayDate,
"playerRating" to d.playerRating,
"nameplateId" to d.plateId,
"iconId" to d.iconId,
"trophyId" to 0,
"partnerId" to d.partnerId,
"frameId" to d.frameId,
"totalAwake" to d.totalAwake,
"isNetMember" to d.isNetMember,
"dailyBonusDate" to d.dailyBonusDate,
"headPhoneVolume" to (option?.headPhoneVolume ?: 0),
"dispRate" to (option?.dispRate ?: 0),
"isInherit" to false,
"banState" to d.banState
)
}
"GetUserShopStock" {
val shopItemIdList = data["shopItemIdList"] as List<*>
mapOf(
"uid" to uid,
"userShopStockList" to shopItemIdList.map { mapOf(
"shopItemId" to it,
"tradeCount" to 0
) }
)
}
// Empty List Handlers
"GetUserRecommendRateMusic" { mapOf(
"uid" to uid,
"userRecommendRateMusicIdList" to empty
) }
"GetUserRecommendSelectMusic" { mapOf(
"uid" to uid,
"userRecommendSelectionMusicIdList" to empty
) }
"GetUserRegion".unpaged { empty }
"GetUserGhost".unpaged { empty }
"GetUserFriendBonus" { mapOf("uid" to uid, "returnCode" to 0, "getMiles" to 0) }
"GetUserIntimate" { mapOf("uid" to uid, "length" to 0, "userIntimateList" to empty) }
"GetTransferFriend" { mapOf("uid" to uid, "transferFriendList" to empty) }
"GetUserKaleidxScope" { mapOf("uid" to uid, "userKaleidxScopeList" to empty) }
"GetUserNewItem" { mapOf("uid" to uid, "itemKind" to 0, "itemId" to 0) }
"GetUserNewItemList" { mapOf("uid" to uid, "userItemList" to empty) }
"GetUserCardPrintError" static { mapOf("length" to 0, "userPrintDetailList" to empty) }
"GetUserFriendCheck" static { mapOf("returnCode" to 0) }
"UserFriendRegist" static { mapOf("returnCode1" to 0, "returnCode2" to 0) }
"GetGameNgMusicId" static { mapOf("length" to 0, "musicIdList" to empty) }
"GetGameTournamentInfo" static { mapOf("length" to 0, "gameTournamentInfoList" to empty) }
"GetGameKaleidxScope" static { mapOf("gameKaleidxScopeList" to empty) }
"GetGameSetting" static {
// The client-side implementation for reboot time is extremely cursed.
// Only hour and minute are used, date is discarded and second is set to 0.
// The time is adjusted to the next day if it's 12 hours or more from now.
// And it's using local timezone instead of treating it as UTC.
// The official maimai cabs will reboot every day, but we don't want that
// So, we need to return the hour and minute 5 hours ago
// val rebootStart = Instant.now().atZone(ZoneId.of("Asia/Tokyo")).minusHours(5)
// val rebootEnd = rebootStart.plusSeconds(60)
// Nope that didn't work
mapOf(
"isAouAccession" to true,
"gameSetting" to mapOf(
// "rebootStartTime" to GAME_SETTING_DATE_FMT.format(rebootStart),
// "rebootEndTime" to GAME_SETTING_DATE_FMT.format(rebootEnd),
"rebootStartTime" to "2020-01-01 23:59:00.0",
"rebootEndTime" to "2020-01-01 23:59:00.0",
"rebootInterval" to 0,
// Fields below doesn't seem to be used by the client at all
"isMaintenance" to false,
"requestInterval" to 10,
"movieUploadLimit" to 0,
"movieStatus" to 0,
"movieServerUri" to "",
"deliverServerUri" to "",
"oldServerUri" to "",
"usbDlServerUri" to "",
// Fields below are SDGB-specific settings (not present in SDEZ)
"pingDisable" to true,
"packetTimeout" to 20_000,
"packetTimeoutLong" to 60_000,
"packetRetryCount" to 5,
"userDataDlErrTimeout" to 300_000,
"userDataDlErrRetryCount" to 5,
"userDataDlErrSamePacketRetryCount" to 5,
"userDataUpSkipTimeout" to 0,
"userDataUpSkipRetryCount" to 0,
"iconPhotoDisable" to true,
"uploadPhotoDisable" to false,
"maxCountMusic" to 0,
"maxCountItem" to 0
)
)
}
"GetGameWeeklyData" static { mapOf(
"gameWeeklyData" to mapOf(
"missionCategory" to 0,
"updateDate" to "2024-01-01 00:00:00.0",
"beforeDate" to "2077-01-01 00:00:00.0"
)
) }
"GetUserMissionData" { mapOf(
"uid" to uid,
"userWeeklyData" to mapOf (
"lastLoginWeek" to "",
"beforeLoginWeek" to "",
"friendBonusFlag" to false
),
"userMissionDataList" to empty
) }
"GetGameMusicScore" static { mapOf(
"gameMusicScore" to mapOf(
"musicId" to 0,
"level" to 0,
"type" to 0,
"scoreData" to ""
)
) }
}

View File

@ -3,13 +3,13 @@ package icu.samnyan.aqua.sega.maimai2
import ext.*
import icu.samnyan.aqua.net.utils.ApiException
import icu.samnyan.aqua.net.utils.simpleDescribe
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.general.*
import icu.samnyan.aqua.sega.maimai2.handler.*
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
import icu.samnyan.aqua.spring.Metrics
import io.ktor.client.request.*
import jakarta.servlet.http.HttpServletRequest
import org.slf4j.LoggerFactory
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import java.time.format.DateTimeFormatter
import kotlin.reflect.full.declaredMemberProperties
@ -34,8 +34,9 @@ class Maimai2ServletController(
val getUserRivalMusic: GetUserRivalMusicHandler,
val getUserCharacter: GetUserCharacterHandler,
val getGameRanking: GetGameRankingHandler,
val repos: Mai2Repos
) {
val db: Mai2Repos
): MeowApi(serialize = { _, resp -> if (resp is String) resp else resp.toJson() }) {
companion object {
private val logger = LoggerFactory.getLogger(Maimai2ServletController::class.java)
private val empty = listOf<Any>()
@ -43,306 +44,35 @@ class Maimai2ServletController(
private val GAME_SETTING_TIME_FMT = DateTimeFormatter.ofPattern("HH:mm:00")
}
val getUserExtend = UserReqHandler { _, userId -> mapOf(
"userId" to userId,
"userExtend" to (repos.userExtend.findSingleByUser_Card_ExtId(userId)() ?: (404 - "User not found"))
) }
init { initApis() }
val getUserData = UserReqHandler { _, userId -> mapOf(
"userId" to userId,
"userData" to (repos.userData.findByCardExtId(userId)() ?: (404 - "User not found")),
"banState" to 0
) }
val getUserLoginBonus = UserReqHandler { _, userId -> mapOf(
"userId" to userId,
"nextIndex" to 0,
"userLoginBonusList" to repos.userLoginBonus.findByUser_Card_ExtId(userId)
) }
val getUserMap = UserReqHandler { _, userId -> mapOf(
"userId" to userId,
"nextIndex" to 0,
"userMapList" to repos.userMap.findByUser_Card_ExtId(userId)
) }
val getUserMusic = UserReqHandler { _, userId -> mapOf(
"userId" to userId,
"nextIndex" to 0,
"userMusicList" to listOf(mapOf("userMusicDetailList" to repos.userMusicDetail.findByUser_Card_ExtId(userId)))
) }
val getUserCard = UserReqHandler { _, userId -> mapOf(
"userId" to userId,
"nextIndex" to 0,
"userCardList" to repos.userCard.findByUser_Card_ExtId(userId)
) }
val getUserCharge = UserReqHandler { _, userId -> repos.userCharge.findByUser_Card_ExtId(userId).let { mapOf(
"userId" to userId,
"length" to it.size,
"userChargeList" to it
) } }
val getUserFriendSeasonRanking = UserReqHandler { _, userId -> mapOf(
"userId" to userId,
"nextIndex" to 0,
"userFriendSeasonRankingList" to repos.userFriendSeasonRanking.findByUser_Card_ExtId(userId)
) }
val getUserCourse = UserReqHandler { _, userId -> mapOf(
"userId" to userId,
"nextIndex" to 0,
"userCourseList" to repos.userCourse.findByUser_Card_ExtId(userId)
) }
val getUserFavorite = UserReqHandler { req, userId -> mapOf(
"userId" to userId,
"userFavorite" to repos.userFavorite.findByUser_Card_ExtIdAndItemKind(userId, req["itemKind"] as Int)
) }
val getUserActivity = UserReqHandler { _, userId ->
repos.userAct.findByUser_Card_ExtId(userId).let { act -> mapOf(
"userActivity" to mapOf(
"playList" to act.filter { it.kind == 1 },
"musicList" to act.filter { it.kind == 2 }
)
) }
val endpointList = setOf("GetGameRankingApi","GetUserCharacterApi","GetUserItemApi","GetUserPortraitApi",
"GetUserRatingApi","UploadUserPhotoApi","UploadUserPlaylogApi","UploadUserPortraitApi","UserLoginApi",
"UserLogoutApi","UpsertUserAllApi","CMGetUserCardApi","CMGetUserCardPrintErrorApi","CMGetUserDataApi",
"CMGetUserItemApi","CMUpsertUserPrintApi","GetUserFavoriteItemApi","GetUserRivalMusicApi","GetUserScoreRankingApi",
"UpsertClientBookkeepingApi","UpsertClientSettingApi","UpsertClientTestmodeApi","UpsertClientUploadApi",
"Ping","RemoveTokenApi","CMLoginApi","CMLogoutApi","CMUpsertBuyCardApi").mut.also {
println(it.filter { it !in initH.keys }.toJson())
}
val getGameCharge = BaseHandler { repos.gameCharge.findAll().let {
mapOf("length" to it.size, "gameChargeList" to it)
} }
val getGameEvent = BaseHandler {
val type = parsing { (it["type"] as Number).toInt() }
mapOf(
"type" to type,
"gameEventList" to repos.gameEvent.findByEnable(true) // Maimai only request for type 1
)
}
val getUserRivalData = UserReqHandler { req, userId ->
val rivalId = parsing { (req["rivalId"] as Number).toLong() }
// rivalId should store and fetch with the id column of table rather than card_ext_id
// or user will be able to get others' ext_id by setting them as rival
mapOf(
"userId" to userId,
"userRivalData" to mapOf(
"rivalId" to rivalId,
"rivalName" to (repos.userData.findById(rivalId)()?.userName ?: "")
)
)
}
val getUserOption = UserReqHandler { _, userId -> mapOf(
"userId" to userId,
"userOption" to (repos.userOption.findSingleByUser_Card_ExtId(userId)() ?: (404 - "User not found"))
) }
val cmGetSellingCard = BaseHandler { repos.gameSellingCard.findAll().let {
mapOf("length" to it.size, "sellingCardList" to it)
} }
val cmGetUserCharacter = UserReqHandler { _, userId -> repos.userCharacter.findByUser_Card_ExtId(userId).let {
mapOf(
"returnCode" to 1,
"length" to it.size,
"userCharacterList" to it
)
} }
val cmGetUserPreview = UserReqHandler { _, userId -> repos.userData.findByCardExtId(userId)()?.let {
mapOf(
"userId" to userId,
"userName" to it.userName,
"rating" to it.playerRating,
"lastDataVersion" to it.lastDataVersion,
"isLogin" to false,
"isExistSellingCard" to false
)
} ?: (404 - "User not found") }
val getUserPreview = UserReqHandler { _, userId ->
val d = repos.userData.findByCardExtId(userId)() ?: (404 - "User not found")
val option = repos.userOption.findSingleByUser_Card_ExtId(userId)()
mapOf(
"userId" to userId,
"userName" to d.userName,
"isLogin" to false,
"lastGameId" to d.lastGameId,
"lastDataVersion" to d.lastDataVersion,
"lastRomVersion" to d.lastRomVersion,
"lastLoginDate" to d.lastPlayDate,
"lastPlayDate" to d.lastPlayDate,
"playerRating" to d.playerRating,
"nameplateId" to d.plateId,
"iconId" to d.iconId,
"trophyId" to 0,
"partnerId" to d.partnerId,
"frameId" to d.frameId,
"totalAwake" to d.totalAwake,
"isNetMember" to d.isNetMember,
"dailyBonusDate" to d.dailyBonusDate,
"headPhoneVolume" to (option?.headPhoneVolume ?: 0),
"dispRate" to (option?.dispRate ?: 0),
"isInherit" to false,
"banState" to d.banState
)
}
val getUserShopStock = UserReqHandler { req, userId ->
val shopItemIdList = req["shopItemIdList"] as List<*>
mapOf(
"userId" to userId,
"userShopStockList" to shopItemIdList.map { mapOf(
"shopItemId" to it,
"tradeCount" to 0
) }
)
}
// Empty List Handlers
val getUserRecommendRateMusic = UserReqHandler { _, userId -> mapOf(
"userId" to userId,
"userRecommendRateMusicIdList" to empty
) }
val getUserRecommendSelectMusic = UserReqHandler { _, uid -> mapOf(
"userId" to uid,
"userRecommendSelectionMusicIdList" to empty
) }
val getUserCardPrintError = BaseHandler { mapOf("length" to 0, "userPrintDetailList" to empty) }
val getUserRegion = UserReqHandler { _, uid -> mapOf("userId" to uid, "length" to 0, "userRegionList" to empty) }
val getUserGhost = UserReqHandler { _, uid -> mapOf("userId" to uid, "userGhostList" to empty) }
val getUserFriendBonus = UserReqHandler { _, uid -> mapOf("userId" to uid, "returnCode" to 0, "getMiles" to 0) }
val getUserFriendCheck = BaseHandler { mapOf("returnCode" to 0) }
val userFriendRegist = BaseHandler { mapOf("returnCode1" to 0, "returnCode2" to 0) }
val getUserIntimate = UserReqHandler { _, uid -> mapOf("userId" to uid, "length" to 0, "userIntimateList" to empty) }
val getTransferFriend = UserReqHandler { _, uid -> mapOf("userId" to uid, "transferFriendList" to empty) }
val getGameNgMusicId = BaseHandler { mapOf("length" to 0, "musicIdList" to empty) }
val getGameTournamentInfo = BaseHandler { mapOf("length" to 0, "gameTournamentInfoList" to empty) }
val getGameKaleidxScope = BaseHandler { mapOf("gameKaleidxScopeList" to empty) }
val getUserKaleidxScope = UserReqHandler { _, uid -> mapOf("userId" to uid, "userKaleidxScopeList" to empty) }
val getUserNewItem = UserReqHandler { _, uid -> mapOf("userId" to uid, "itemKind" to 0, "itemId" to 0) }
val getUserNewItemList = UserReqHandler { _, uid -> mapOf("userId" to uid, "userItemList" to empty) }
val getGameSetting = BaseHandler {
// The client-side implementation for reboot time is extremely cursed.
// Only hour and minute are used, date is discarded and second is set to 0.
// The time is adjusted to the next day if it's 12 hours or more from now.
// And it's using local timezone instead of treating it as UTC.
// The official maimai cabs will reboot every day, but we don't want that
// So, we need to return the hour and minute 5 hours ago
// val rebootStart = Instant.now().atZone(ZoneId.of("Asia/Tokyo")).minusHours(5)
// val rebootEnd = rebootStart.plusSeconds(60)
// Nope that didn't work
mapOf(
"isAouAccession" to true,
"gameSetting" to mapOf(
// "rebootStartTime" to GAME_SETTING_DATE_FMT.format(rebootStart),
// "rebootEndTime" to GAME_SETTING_DATE_FMT.format(rebootEnd),
"rebootStartTime" to "2020-01-01 23:59:00.0",
"rebootEndTime" to "2020-01-01 23:59:00.0",
"rebootInterval" to 0,
// Fields below doesn't seem to be used by the client at all
"isMaintenance" to false,
"requestInterval" to 10,
"movieUploadLimit" to 0,
"movieStatus" to 0,
"movieServerUri" to "",
"deliverServerUri" to "",
"oldServerUri" to "",
"usbDlServerUri" to "",
// Fields below are SDGB-specific settings (not present in SDEZ)
"pingDisable" to true,
"packetTimeout" to 20_000,
"packetTimeoutLong" to 60_000,
"packetRetryCount" to 5,
"userDataDlErrTimeout" to 300_000,
"userDataDlErrRetryCount" to 5,
"userDataDlErrSamePacketRetryCount" to 5,
"userDataUpSkipTimeout" to 0,
"userDataUpSkipRetryCount" to 0,
"iconPhotoDisable" to true,
"uploadPhotoDisable" to false,
"maxCountMusic" to 0,
"maxCountItem" to 0
)
)
}
val getGameWeeklyData = BaseHandler { mapOf(
"gameWeeklyData" to mapOf(
"missionCategory" to 0,
"updateDate" to "2024-01-01 00:00:00.0",
"beforeDate" to "2077-01-01 00:00:00.0"
)
) }
val getUserMissionData = UserReqHandler { _, uid -> mapOf(
"userId" to uid,
"userWeeklyData" to mapOf (
"lastLoginWeek" to "",
"beforeLoginWeek" to "",
"friendBonusFlag" to false
),
"userMissionDataList" to empty
) }
val getGameMusicScore = BaseHandler { mapOf(
"gameMusicScore" to mapOf(
"musicId" to 0,
"level" to 0,
"type" to 0,
"scoreData" to ""
)
) }
val endpointList = setOf("GetGameEventApi", "GetGameRankingApi", "GetGameSettingApi", "GetGameTournamentInfoApi", "GetGameWeeklyDataApi",
"GetTransferFriendApi", "GetUserActivityApi", "GetUserCardApi", "GetUserCharacterApi", "GetUserDataApi",
"GetUserExtendApi", "GetUserFavoriteApi", "GetUserGhostApi", "GetUserItemApi", "GetUserLoginBonusApi",
"GetUserMapApi", "GetUserMusicApi", "GetUserOptionApi", "GetUserPortraitApi", "GetUserPreviewApi",
"GetUserFriendBonusApi", "GetUserFriendCheckApi", "UserFriendRegistApi", "GetUserMissionDataApi", "GetUserIntimateApi", "GetUserShopStockApi",
"GetUserRatingApi", "GetUserRegionApi", "UploadUserPhotoApi", "UploadUserPlaylogApi", "UploadUserPortraitApi",
"UserLoginApi", "UserLogoutApi", "UpsertUserAllApi", "GetGameChargeApi", "GetUserChargeApi",
"GetUserCourseApi", "GetGameNgMusicIdApi", "GetUserFriendSeasonRankingApi", "CreateTokenApi",
"GetUserRecommendRateMusicApi", "GetUserRecommendSelectMusicApi", "CMGetSellingCardApi",
"CMGetUserCardApi", "CMGetUserCardPrintErrorApi", "CMGetUserCharacterApi", "CMGetUserDataApi", "CMGetUserItemApi",
"CMGetUserPreviewApi", "CMUpsertUserPrintApi",
"CMUpsertUserPrintlogApi", "GetUserFavoriteItemApi", "GetUserRivalDataApi", "GetUserRivalMusicApi",
"GetUserScoreRankingApi", "UpsertClientBookkeepingApi", "UpsertClientSettingApi",
"UpsertClientTestmodeApi", "UpsertClientUploadApi", "Ping", "RemoveTokenApi", "CMLoginApi", "CMLogoutApi",
"CMUpsertBuyCardApi", "GetGameSettingApi", "GetGameKaleidxScopeApi", "GetGameMusicScoreApi",
"GetUserKaleidxScopeApi", "GetUserNewItemApi", "GetUserNewItemListApi").mut
val noopEndpoint = setOf("GetUserScoreRankingApi", "UpsertClientBookkeepingApi",
"UpsertClientSettingApi", "UpsertClientTestmodeApi", "UpsertClientUploadApi", "Ping", "RemoveTokenApi",
"CMLoginApi", "CMLogoutApi", "CMUpsertBuyCardApi", "UserLogoutApi", "GetGameMapAreaConditionApi",
"UpsertUserChargelogApi").also { endpointList.removeAll(it) }
val staticEndpoint = mapOf(
"CreateTokenApi" to """{"Bearer":"meow"}""",
"CMUpsertUserPrintlogApi" to """{"returnCode":1,"orderId":"0","serialId":"FAKECARDIMAG12345678"}""",
).also { endpointList.removeAll(it.keys.toSet()) }
val members = this::class.declaredMemberProperties
val handlers: Map<String, BaseHandler> = endpointList.associateWith { api ->
val handlers: Map<String, SpecialHandler> = endpointList.associateWith { api ->
val name = api.replace("Api", "").lowercase()
(members.find { it.name.lowercase() == name } ?: members.find { it.name.lowercase() == name.replace("cm", "") })
?.let { it.call(this) as BaseHandler }
?.let { (it.call(this) as BaseHandler).toSpecial() }
?: initH[api] ?: initH[api.replace("CM", "")]
?: throw IllegalArgumentException("Mai2: No handler found for $api")
}
@API("/{api}")
fun handle(@PathVariable api: String, @RequestBody request: Map<String, Any>): Any {
logger.info("Mai2 < $api : ${request.toJson()}") // TODO: Optimize logging
if (api !in noopEndpoint && api !in staticEndpoint && !handlers.containsKey(api)) {
fun handle(@PathVariable api: String, @RequestBody data: Map<String, Any>, req: HttpServletRequest): Any {
logger.info("Mai2 < $api : ${data.toJson()}") // TODO: Optimize logging
if (api !in noopEndpoint && !handlers.containsKey(api)) {
logger.warn("Mai2 > $api not found")
return """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.$api"}"""
}
@ -355,16 +85,11 @@ class Maimai2ServletController(
return """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.$api"}"""
}
if (api in staticEndpoint) {
logger.info("Mai2 > $api static")
return staticEndpoint[api]!!
}
return try {
Metrics.timer("aquadx_maimai2_api_latency", "api" to api).recordCallable {
handlers[api]!!.handle(request).let { if (it is String) it else it.toJson() }.also {
if (api !in setOf("GetUserItemApi", "GetGameEventApi", "GetUserPortraitApi"))
logger.info("Mai2 > $api : $it")
val ctx = RequestContext(req, data.mut)
serialize(api, handlers[api]!!(ctx)).also {
logger.info("Mai2 > $api : ${it.truncate(1000)}")
}
}
} catch (e: Exception) {
@ -375,7 +100,7 @@ class Maimai2ServletController(
if (e is ApiException) {
// It's a bad practice to return 200 ok on error, but this is what maimai does so we have to follow
return ResponseEntity.ok().body("""{"returnCode":0,"apiName":"com.sega.maimai2servlet.api.$api","message":"${e.message?.replace("\"", "\\\"")} - ${e.code}"}""")
return mapOf("returnCode" to 0, "apiName" to "com.sega.maimai2servlet.api.$api", "message" to "${e.message} - ${e.code}").toJson()
} else throw e
}
}