package icu.samnyan.aqua.sega.maimai2 import ext.* import icu.samnyan.aqua.net.utils.ApiException import icu.samnyan.aqua.sega.general.BaseHandler import icu.samnyan.aqua.sega.maimai2.handler.* import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos import io.ktor.client.request.* import org.slf4j.LoggerFactory import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.* import kotlin.reflect.full.declaredMemberProperties /** * @author samnyan (privateamusement@protonmail.com) */ @Suppress("unused") @RestController @RequestMapping(path = ["/g/mai2/Maimai2Servlet/", "/g/mai2/"]) class Maimai2ServletController( val userLogin: UserLoginHandler, val upsertUserAll: UpsertUserAllHandler, val getUserItem: GetUserItemHandler, val getUserRating: GetUserRatingHandler, val uploadUserPhoto: UploadUserPhotoHandler, val uploadUserPlaylog: UploadUserPlaylogHandler, val getUserPortrait: GetUserPortraitHandler, val uploadUserPortrait: UploadUserPortraitHandler, val upsertUserPrint: UpsertUserPrintHandler, val getUserFavoriteItem: GetUserFavoriteItemHandler, val getUserRivalMusic: GetUserRivalMusicHandler, val repos: Mai2Repos ) { companion object { private val logger = LoggerFactory.getLogger(Maimai2ServletController::class.java) private val empty = listOf() } val getUserExtend = UserReqHandler { _, userId -> mapOf( "userId" to userId, "userExtend" to (repos.userExtend.findSingleByUser_Card_ExtId(userId)() ?: (404 - "User not found")) ) } 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 getUserCharacter = UserReqHandler { _, userId -> mapOf( "userId" to userId, "userCharacterList" to repos.userCharacter.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 { _, userId -> mapOf( "userId" to userId, "userFavoriteData" to repos.userFavorite.findByUser_Card_ExtId(userId) ) } 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 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() } mapOf( "userId" to userId, "userRivalData" to mapOf( "rivalId" to rivalId, "rivalName" to (repos.userData.findByCardExtId(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 "1.20.00", "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 ) } // 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 getTransferFriend = UserReqHandler { _, uid -> mapOf("userId" to uid, "transferFriendList" to empty) } val getGameNgMusicId = BaseHandler { mapOf("length" to 0, "musicIdList" to empty) } val getGameRanking = BaseHandler { mapOf("type" to it["type"].toString(), "gameRankingList" to empty) } val getGameTournamentInfo = BaseHandler { mapOf("length" to 0, "gameTournamentInfoList" to empty) } val endpointList = setOf("GetGameEventApi", "GetGameRankingApi", "GetGameSettingApi", "GetGameTournamentInfoApi", "GetTransferFriendApi", "GetUserActivityApi", "GetUserCardApi", "GetUserCharacterApi", "GetUserDataApi", "GetUserExtendApi", "GetUserFavoriteApi", "GetUserGhostApi", "GetUserItemApi", "GetUserLoginBonusApi", "GetUserMapApi", "GetUserMusicApi", "GetUserOptionApi", "GetUserPortraitApi", "GetUserPreviewApi", "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").toMutableList() val noopEndpoint = endpointList.popAll("GetUserScoreRankingApi", "UpsertClientBookkeepingApi", "UpsertClientSettingApi", "UpsertClientTestmodeApi", "UpsertClientUploadApi", "Ping", "RemoveTokenApi", "CMLoginApi", "CMLogoutApi", "CMUpsertBuyCardApi", "UserLogoutApi", "GetGameMapAreaConditionApi") val staticEndpoint = mapOf( "CreateTokenApi" to """{"Bearer":"meow"}""", "CMUpsertUserPrintlogApi" to """{"returnCode":1,"orderId":"0","serialId":"FAKECARDIMAG12345678"}""", "GetGameSettingApi" to mapOf( "isAouAccession" to true, "gameSetting" to mapOf( "isMaintenance" to false, "requestInterval" to 10, "rebootStartTime" to "2099-01-01 23:59:00.0", "rebootEndTime" to "2099-01-01 23:59:00.0", "movieUploadLimit" to 10000, "movieStatus" to 0, "movieServerUri" to "", "deliverServerUri" to "", "oldServerUri" to "", "usbDlServerUri" to "", "rebootInterval" to 0 ) ).toJson() ).also { endpointList.popAll(it.keys.toList()) } val members = this::class.declaredMemberProperties val handlers: Map = 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 } ?: throw IllegalArgumentException("Mai2: No handler found for $api") } @API("/{api}") fun handle(@PathVariable api: String, @RequestBody request: Map): Any { try { logger.info("Mai2 < $api : ${request.toJson()}") // TODO: Optimize logging if (api in noopEndpoint) { logger.info("Mai2 > $api no-op") return """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.$api"}""" } if (api in staticEndpoint) { logger.info("Mai2 > $api static") return staticEndpoint[api]!! } return handlers[api]?.handle(request)?.let { if (it is String) it else it.toJson() }?.also { if (api !in setOf("GetUserItemApi", "GetGameEventApi")) logger.info("Mai2 > $api : $it") } ?: { logger.warn("Mai2 > $api not found") """{"returnCode":1,"apiName":"com.sega.maimai2servlet.api.$api"}""" } } catch (e: 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}"}""") } } }