diff --git a/src/main/java/icu/samnyan/aqua/Entry.kt b/src/main/java/icu/samnyan/aqua/Entry.kt index dea813ed..d08cccee 100644 --- a/src/main/java/icu/samnyan/aqua/Entry.kt +++ b/src/main/java/icu/samnyan/aqua/Entry.kt @@ -1,7 +1,6 @@ package icu.samnyan.aqua import icu.samnyan.aqua.sega.aimedb.AimeDbServer -import icu.samnyan.aqua.sega.maimai2.worldslink.MaimaiFutari import icu.samnyan.aqua.spring.AutoChecker import org.springframework.boot.SpringApplication import org.springframework.boot.ansi.AnsiOutput @@ -15,9 +14,6 @@ class Entry fun main(args: Array) { AnsiOutput.setEnabled(AnsiOutput.Enabled.ALWAYS) - when (args.getOrNull(0)) { - "futari" -> return MaimaiFutari().start() - } // If data/ is not found, create it File("data").mkdirs() diff --git a/src/main/java/icu/samnyan/aqua/net/transfer/DataBroker.kt b/src/main/java/icu/samnyan/aqua/net/transfer/DataBroker.kt index c3929971..c99f34d8 100644 --- a/src/main/java/icu/samnyan/aqua/net/transfer/DataBroker.kt +++ b/src/main/java/icu/samnyan/aqua/net/transfer/DataBroker.kt @@ -1,10 +1,11 @@ package icu.samnyan.aqua.net.transfer import ext.* -import icu.samnyan.aqua.sega.chusan.model.request.UpsertUserAll +import icu.samnyan.aqua.sega.chusan.model.request.Chu3UserAll import icu.samnyan.aqua.sega.chusan.model.userdata.UserActivity import icu.samnyan.aqua.sega.chusan.model.userdata.UserItem import icu.samnyan.aqua.sega.chusan.model.userdata.UserMusicDetail +import icu.samnyan.aqua.sega.maimai2.model.request.Mai2UserAll import icu.samnyan.aqua.sega.util.jackson.BasicMapper import icu.samnyan.aqua.sega.util.jackson.IMapper import icu.samnyan.aqua.sega.util.jackson.StringMapper @@ -48,7 +49,7 @@ class ChusanDataBroker(allNet: AllNetClient, log: (String) -> Unit): DataBroker( val userId = mapOf("userId" to allNet.userId) val paged = userId + mapOf("nextIndex" to 0, "maxCount" to 10000000) - return mapper.write(UpsertUserAll().apply { + return mapper.write(Chu3UserAll().apply { userData = ls("GetUserDataApi".get("userData", userId)) userGameOption = ls("GetUserOptionApi".get("userGameOption", userId)) userCharacterList = "GetUserCharacterApi".get("userCharacterList", paged) @@ -85,8 +86,10 @@ class MaimaiDataBroker(allNet: AllNetClient, log: (String) -> Unit): DataBroker( val userId = mapOf("userId" to allNet.userId) val paged = userId + mapOf("nextIndex" to 0, "maxCount" to 10000000) - return UpsertUserAll().apply { + return Mai2UserAll().apply { userData = ls("GetUserDataApi".get("userData", userId)) +// userGameOption = ls("GetUserOptionApi".get("userGameOption", userId)) + }.toJson() } diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanUpsertApis.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanUpsertApis.kt index fb40e9db..ccbe9f45 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanUpsertApis.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/handler/ChusanUpsertApis.kt @@ -2,7 +2,7 @@ package icu.samnyan.aqua.sega.chusan.handler import ext.* import icu.samnyan.aqua.sega.chusan.ChusanController -import icu.samnyan.aqua.sega.chusan.model.request.UpsertUserAll +import icu.samnyan.aqua.sega.chusan.model.request.Chu3UserAll import icu.samnyan.aqua.sega.chusan.model.userdata.* import icu.samnyan.aqua.sega.general.model.response.UserRecentRating @@ -17,7 +17,7 @@ fun ChusanController.upsertApiInit() { } "UpsertUserAll" api@ { - val req = parsing { mapper.convert(data["upsertUserAll"]!!) } + val req = parsing { mapper.convert(data["upsertUserAll"]!!) } req.run { // UserData diff --git a/src/main/java/icu/samnyan/aqua/sega/chusan/model/request/UpsertUserAll.kt b/src/main/java/icu/samnyan/aqua/sega/chusan/model/request/Chu3UserAll.kt similarity index 99% rename from src/main/java/icu/samnyan/aqua/sega/chusan/model/request/UpsertUserAll.kt rename to src/main/java/icu/samnyan/aqua/sega/chusan/model/request/Chu3UserAll.kt index 2c195609..3b0d1d20 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chusan/model/request/UpsertUserAll.kt +++ b/src/main/java/icu/samnyan/aqua/sega/chusan/model/request/Chu3UserAll.kt @@ -37,7 +37,7 @@ data class MusicIdWrapper( val musicId: Int = 0, ) -class UpsertUserAll( +class Chu3UserAll( var userData: List? = null, var userGameOption: List? = null, var userCharacterList: List? = null, diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/Maimai2Apis.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/Maimai2Apis.kt index 95324ac9..8eaa8b24 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/Maimai2Apis.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/Maimai2Apis.kt @@ -4,12 +4,10 @@ package icu.samnyan.aqua.sega.maimai2 import ext.* import icu.samnyan.aqua.sega.general.PagedHandler -import icu.samnyan.aqua.sega.maimai2.model.response.data.UserRivalMusic -import icu.samnyan.aqua.sega.maimai2.model.response.data.UserRivalMusicDetail -import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserIntimate +import icu.samnyan.aqua.sega.maimai2.model.UserRivalMusic +import icu.samnyan.aqua.sega.maimai2.model.UserRivalMusicDetail import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserKaleidx import java.time.LocalDate -import java.util.* fun Maimai2ServletController.initApis() { // Used because maimai does not actually require paging implementation @@ -157,7 +155,7 @@ fun Maimai2ServletController.initApis() { val rivalId = parsing { data["rivalId"]!!.long } val lst = db.userMusicDetail.findByUserId(rivalId) - val res = lst.associate { it.musicId to UserRivalMusic(it.musicId, LinkedList()) } + val res = lst.associate { it.musicId to UserRivalMusic(it.musicId) } lst.forEach { res[it.musicId]!!.userRivalMusicDetailList.add( @@ -195,6 +193,7 @@ fun Maimai2ServletController.initApis() { } // Kaleidoscope, added on 1.50 + // [{gateId, phaseId}] "GetGameKaleidxScope" { mapOf("gameKaleidxScopeList" to ls( mapOf("gateId" to 1, "phaseId" to findPhase(LocalDate.of(2025, 1, 18))), mapOf("gateId" to 2, "phaseId" to 2), @@ -203,6 +202,8 @@ fun Maimai2ServletController.initApis() { mapOf("gateId" to 5, "phaseId" to 2), mapOf("gateId" to 6, "phaseId" to 2), )) } + // Request: {userId} + // Response: {userId, userKaleidxScopeList} "GetUserKaleidxScope".unpaged { val u = db.userData.findByCardExtId(uid)() ?: (404 - "User not found") val lst = db.userKaleidx.findByUser(u) @@ -213,6 +214,8 @@ fun Maimai2ServletController.initApis() { lst } + // Request: {userId, version, userData: [UserDetail], userPlaylogList: [UserPlaylog]} + // Response: {userId, userItemList: [UserItem]} // Added on 1.50 "GetUserNewItemList" { mapOf("userId" to uid, "userItemList" to empty) } diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/GetUserPortraitHandler.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/GetUserPortraitHandler.java deleted file mode 100644 index 668f768f..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/GetUserPortraitHandler.java +++ /dev/null @@ -1,98 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.handler; - -import com.fasterxml.jackson.core.JsonProcessingException; -import icu.samnyan.aqua.net.db.AquaNetUser; -import icu.samnyan.aqua.net.utils.PathProps; -import icu.samnyan.aqua.sega.general.BaseHandler; -import icu.samnyan.aqua.sega.general.dao.CardRepository; -import icu.samnyan.aqua.sega.general.model.Card; -import icu.samnyan.aqua.sega.maimai2.model.request.data.UserPortrait; -import icu.samnyan.aqua.sega.util.jackson.BasicMapper; -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.security.crypto.codec.Utf8; -import org.springframework.stereotype.Component; - -import java.io.FileInputStream; -import java.nio.file.Paths; -import java.util.*; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Component("Maimai2GetUserPortraitHandler") -public class GetUserPortraitHandler implements BaseHandler { - private static final Logger logger = LoggerFactory.getLogger(GetUserPortraitHandler.class); - - private final BasicMapper mapper; - private final boolean enable; - private final CardRepository cardRepo; - private final String portraitPath; - - public GetUserPortraitHandler(BasicMapper mapper, - @Value("${game.maimai2.userPhoto.enable:true}") boolean enable, - CardRepository cardRepo, - PathProps paths) { - this.mapper = mapper; - this.enable = enable; - this.cardRepo = cardRepo; - this.portraitPath = paths.getAquaNetPortrait(); - } - - @Override - public String handle(Map request) throws JsonProcessingException { - if (enable) { - var userId = ((Number) request.get("userId")).longValue(); - var list = new ArrayList(); - var card = cardRepo.findByExtId(userId); - var user = card.map(Card::getAquaUser); - var profilePicture = user.map(AquaNetUser::getProfilePicture).orElse(null); - - try { - if (!StringUtils.isEmpty(profilePicture)) { - var filePath = Paths.get(portraitPath, profilePicture); - var buffer = new byte[10240]; - - var stream = new FileInputStream(filePath.toFile()); - while (stream.available() > 0) { - var read = stream.read(buffer, 0, 10240); - - var encodeBuffer = read == 10240 ? buffer : Arrays.copyOfRange(buffer, 0, read); - - var userPortrait = new UserPortrait(); - - userPortrait.setFileName("portrait.jpg"); - userPortrait.setPlaceId(0); - userPortrait.setUserId(userId); - userPortrait.setClientId(""); - userPortrait.setUploadDate("1970-01-01 09:00:00.0"); - userPortrait.setDivData(Utf8.decode(Base64.getEncoder().encode(encodeBuffer))); - - userPortrait.setDivNumber(list.size()); - - list.add(userPortrait); - } - - stream.close(); - for (var i = 0; i < list.size(); i++) { - var userPortrait = list.get(i); - userPortrait.setDivLength(list.size()); - } - - var map = new HashMap(); - map.put("length", list.size()); - map.put("userPortraitList", list); - - var respJson = mapper.write(map); - return respJson; - } - } catch (Exception e) { - logger.error("Result: User photo save failed", e); - } - } - - return "{\"length\":0,\"userPortraitList\":[]}"; - } -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/GetUserPortraitHandler.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/GetUserPortraitHandler.kt new file mode 100644 index 00000000..d36ed191 --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/GetUserPortraitHandler.kt @@ -0,0 +1,63 @@ +package icu.samnyan.aqua.sega.maimai2.handler + +import ext.invoke +import ext.logger +import icu.samnyan.aqua.net.utils.PathProps +import icu.samnyan.aqua.sega.general.BaseHandler +import icu.samnyan.aqua.sega.general.dao.CardRepository +import icu.samnyan.aqua.sega.maimai2.model.request.Mai2UserPortrait +import org.springframework.beans.factory.annotation.Value +import org.springframework.security.crypto.codec.Utf8 +import org.springframework.stereotype.Component +import java.io.FileInputStream +import java.nio.file.Paths +import java.util.* + +@Component("Maimai2GetUserPortraitHandler") +class GetUserPortraitHandler( + val cardRepo: CardRepository, + + @param:Value("\${game.maimai2.userPhoto.enable:true}") val enable: Boolean, + + paths: PathProps +) : BaseHandler { + val portraitPath = paths.aquaNetPortrait + val log = logger() + + override fun handle(request: Map): Any? { + if (!enable) return """{"length":0,"userPortraitList":[]}""" + + val uid = (request["userId"] as Number).toLong() + val list = ArrayList() + val profilePicture = cardRepo.findByExtId(uid)()?.aquaUser?.profilePicture?.ifBlank { null } + ?: return """{"length":0,"userPortraitList":[]}""" + + try { + val filePath = Paths.get(portraitPath, profilePicture) + val buffer = ByteArray(10240) + + FileInputStream(filePath.toFile()).use { stream -> + while (stream.available() > 0) { + val read = stream.read(buffer, 0, 10240) + val buf = if (read == 10240) buffer else Arrays.copyOfRange(buffer, 0, read) + + list.add(Mai2UserPortrait().apply { + userId = uid + divData = Utf8.decode(Base64.getEncoder().encode(buf)) + divNumber = list.size + }) + } + } + + list.forEach { it.divLength = list.size } + + return mapOf( + "length" to list.size, + "userPortraitList" to list + ) + } catch (e: Exception) { + log.error("Result: User photo get failed", e) + return """{"length":0,"userPortraitList":[]}""" + } + } +} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/GetUserRatingHandler.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/GetUserRatingHandler.kt index 0e1bd922..c9a6813d 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/GetUserRatingHandler.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/GetUserRatingHandler.kt @@ -3,7 +3,7 @@ package icu.samnyan.aqua.sega.maimai2.handler import ext.invoke import icu.samnyan.aqua.sega.general.BaseHandler import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos -import icu.samnyan.aqua.sega.maimai2.model.response.data.UserRating +import icu.samnyan.aqua.sega.maimai2.model.UserRating import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserRate import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserUdemae import icu.samnyan.aqua.sega.util.jackson.BasicMapper diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPhotoHandler.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPhotoHandler.kt index 58396b46..e3942a67 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPhotoHandler.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPhotoHandler.kt @@ -1,11 +1,8 @@ package icu.samnyan.aqua.sega.maimai2.handler -import ext.div -import ext.isoDateTime -import ext.logger -import ext.path +import ext.* import icu.samnyan.aqua.sega.general.BaseHandler -import icu.samnyan.aqua.sega.maimai2.model.request.UploadUserPhoto +import icu.samnyan.aqua.sega.maimai2.model.request.Mai2UserPhoto import icu.samnyan.aqua.sega.util.jackson.BasicMapper import org.springframework.stereotype.Component import java.io.IOException @@ -23,9 +20,7 @@ class UploadUserPhotoHandler(private val mapper: BasicMapper) : // Maimai DX sends split base64 data for one jpeg image. // So, make a temp file and keep append bytes until last part received. // If finished, rename it to other name so user can keep save multiple scorecards in a single day. - - val uploadUserPhoto = mapper.convert(request, UploadUserPhoto::class.java) - val up = uploadUserPhoto.userPhoto + val up = parsing { mapper.convert(request["userPhoto"]!!, Mai2UserPhoto::class.java) } try { val tmpFile = tmpDir / "${up.userId}-${up.trackNo}.tmp" diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPlaylogHandler.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPlaylogHandler.kt index 44d9d8fa..b5824cb5 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPlaylogHandler.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPlaylogHandler.kt @@ -1,12 +1,13 @@ package icu.samnyan.aqua.sega.maimai2.handler import ext.logger +import ext.long import ext.millis +import ext.parsing import icu.samnyan.aqua.sega.allnet.TokenChecker import icu.samnyan.aqua.sega.general.BaseHandler import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo import icu.samnyan.aqua.sega.maimai2.model.Mai2UserPlaylogRepo -import icu.samnyan.aqua.sega.maimai2.model.request.UploadUserPlaylog import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPlaylog import icu.samnyan.aqua.sega.util.jackson.BasicMapper import icu.samnyan.aqua.spring.Metrics @@ -33,9 +34,10 @@ class UploadUserPlaylogHandler( } override fun handle(request: Map): String { - val req = mapper.convert(request, UploadUserPlaylog::class.java) + val uid = parsing { request["userId"]!!.long } + val playlog = parsing { mapper.convert(request["userPlaylog"]!!, Mai2UserPlaylog::class.java) } - val version = tryParseGameVersion(req.userPlaylog.version) + val version = tryParseGameVersion(playlog.version) if (version != null) { val session = TokenChecker.getCurrentSession() val gameId = if (session?.gameId in VALID_GAME_IDS) session!!.gameId else "" @@ -47,9 +49,9 @@ class UploadUserPlaylogHandler( // Check duplicate val isDup = playlogRepo.findByUser_Card_ExtIdAndMusicIdAndUserPlayDate( - req.userId, - req.userPlaylog.musicId, - req.userPlaylog.userPlayDate + uid, + playlog.musicId, + playlog.userPlayDate ).size > 0 if (isDup) { log.info("Duplicate playlog detected") @@ -57,14 +59,14 @@ class UploadUserPlaylogHandler( } // Save if the user is registered - val u = userDataRepository.findByCardExtId(req.userId).getOrNull() - if (u != null) playlogRepo.save(req.userPlaylog.apply { user = u }) + val u = userDataRepository.findByCardExtId(uid).getOrNull() + if (u != null) playlogRepo.save(playlog.apply { user = u }) // If the user hasn't registered (first play), save the playlog to a backlog else { - playBacklog.putIfAbsent(req.userId, mutableListOf()) - playBacklog[req.userId]?.apply { - add(BacklogEntry(millis(), req.userPlaylog)) + playBacklog.putIfAbsent(uid, mutableListOf()) + playBacklog[uid]?.apply { + add(BacklogEntry(millis(), playlog)) if (size > 6) clear() // Prevent abuse } } diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPortraitHandler.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPortraitHandler.kt index 4df81ff6..1301bcb4 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPortraitHandler.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UploadUserPortraitHandler.kt @@ -2,10 +2,11 @@ package icu.samnyan.aqua.sega.maimai2.handler import ext.div import ext.logger +import ext.parsing import ext.path import icu.samnyan.aqua.net.utils.PathProps import icu.samnyan.aqua.sega.general.BaseHandler -import icu.samnyan.aqua.sega.maimai2.model.request.UploadUserPortrait +import icu.samnyan.aqua.sega.maimai2.model.request.Mai2UserPortrait import icu.samnyan.aqua.sega.util.jackson.BasicMapper import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component @@ -34,8 +35,7 @@ class UploadUserPortraitHandler( // Maimai DX sends split base64 data for one jpeg image. // So, make a temp file and keep append bytes until last part received. // If finished, rename it to other name so user can keep save multiple scorecards in a single day. - - val up = mapper.convert(request, UploadUserPortrait::class.java).userPortrait + val up = parsing { mapper.convert(request["userPortrait"]!!, Mai2UserPortrait::class.java) } val id = up.userId val num = up.divNumber diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt index df78d046..e2ec4b99 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserAllHandler.kt @@ -3,13 +3,12 @@ package icu.samnyan.aqua.sega.maimai2.handler import com.fasterxml.jackson.core.JsonProcessingException import ext.invoke import ext.mapApply -import ext.minus import ext.unique import icu.samnyan.aqua.sega.general.BaseHandler import icu.samnyan.aqua.sega.general.service.CardService import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPlaylogHandler.Companion.playBacklog import icu.samnyan.aqua.sega.maimai2.model.* -import icu.samnyan.aqua.sega.maimai2.model.request.UpsertUserAll +import icu.samnyan.aqua.sega.maimai2.model.request.Mai2UpsertUserAll import icu.samnyan.aqua.sega.maimai2.model.userdata.* import icu.samnyan.aqua.sega.util.jackson.BasicMapper import lombok.AllArgsConstructor @@ -31,16 +30,13 @@ class UpsertUserAllHandler( @Throws(JsonProcessingException::class) override fun handle(request: Map): Any? { - val upsertUserAll = mapper.convert(request, UpsertUserAll::class.java) + val upsertUserAll = mapper.convert(request, Mai2UpsertUserAll::class.java) val userId = upsertUserAll.userId val req = upsertUserAll.upsertUserAll // If user is guest, just return OK response. if ((userId and 281474976710657L) == 281474976710657L) return SUCCESS - // UserData - if (req.userData == null) 400 - "Invalid Request" - val userData = repos.userData.findByCardExtId(userId)() val u = repos.userData.saveAndFlush(req.userData[0].apply { id = userData?.id ?: 0 diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserPrintHandler.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserPrintHandler.java deleted file mode 100644 index bb40e166..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserPrintHandler.java +++ /dev/null @@ -1,107 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.handler; - -import com.fasterxml.jackson.core.JsonProcessingException; - -import icu.samnyan.aqua.sega.maimai2.model.Mai2UserCardRepo; -import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo; -import icu.samnyan.aqua.sega.maimai2.model.Mai2UserPrintDetailRepo; -import icu.samnyan.aqua.sega.general.BaseHandler; -import icu.samnyan.aqua.sega.maimai2.model.request.UpsertUserPrint; -import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserCard; -import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserDetail; -import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPrintDetail; -import icu.samnyan.aqua.sega.util.jackson.BasicMapper; -import lombok.AllArgsConstructor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; - -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ThreadLocalRandom; -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Component("Maimai2UpsertUserPrintHandler") -public class UpsertUserPrintHandler implements BaseHandler { - - private static final Logger logger = LoggerFactory.getLogger(UpsertUserPrintHandler.class); - private final BasicMapper mapper; - - private final Mai2UserCardRepo userCardRepository; - private final Mai2UserPrintDetailRepo userPrintDetailRepository; - private final Mai2UserDataRepo userDataRepository; - - private long expirationTime; - - public UpsertUserPrintHandler(BasicMapper mapper, - @Value("${game.cardmaker.card.expiration:15}") long expirationTime, - Mai2UserCardRepo userCardRepository, - Mai2UserPrintDetailRepo userPrintDetailRepository, - Mai2UserDataRepo userDataRepository - ) { - this.mapper = mapper; - this.expirationTime = expirationTime; - this.userCardRepository = userCardRepository; - this.userPrintDetailRepository = userPrintDetailRepository; - this.userDataRepository = userDataRepository; - } - - @Override - public String handle(Map request) throws JsonProcessingException { - long userId = ((Number) request.get("userId")).longValue(); - - Mai2UserDetail userData; - - Optional userOptional = userDataRepository.findByCardExtId(userId); - if (userOptional.isPresent()) { - userData = userOptional.get(); - } else { - logger.error("User not found. userId: {}", userId); - return null; - } - - UpsertUserPrint upsertUserPrint = mapper.convert(request, UpsertUserPrint.class); - - Mai2UserPrintDetail userPrintDetail = upsertUserPrint.getUserPrintDetail(); - Mai2UserCard newUserCard = userPrintDetail.getUserCard(); - - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"); - String currentDateTime = LocalDateTime.now().format(formatter); - String expirationDateTime = LocalDateTime.now().plusDays(expirationTime).format(formatter); - String randomSerialId = - String.format("%010d", ThreadLocalRandom.current().nextLong(0L, 9999999999L)) + - String.format("%010d", ThreadLocalRandom.current().nextLong(0L, 9999999999L)); - - newUserCard.setUser(userData); - userPrintDetail.setUser(userData); - - newUserCard.setStartDate(currentDateTime); - newUserCard.setEndDate(expirationDateTime); - userPrintDetail.setSerialId(randomSerialId); - - Optional userCardOptional = userCardRepository.findByUserAndCardId(newUserCard.getUser(), newUserCard.getCardId()); - if (userCardOptional.isPresent()) { - Mai2UserCard userCard = userCardOptional.get(); - newUserCard.setId(userCard.getId()); - } - - userCardRepository.save(newUserCard); - userPrintDetailRepository.save(userPrintDetail); - - Map resultMap = new LinkedHashMap<>(); - resultMap.put("returnCode", 1); - resultMap.put("orderId", 0); - resultMap.put("serialId", randomSerialId); - resultMap.put("startDate", currentDateTime); - resultMap.put("endDate", expirationDateTime); - - return mapper.write(resultMap); - } -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserPrintHandler.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserPrintHandler.kt new file mode 100644 index 00000000..6db707aa --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/handler/UpsertUserPrintHandler.kt @@ -0,0 +1,54 @@ +package icu.samnyan.aqua.sega.maimai2.handler + +import ext.invoke +import ext.logger +import ext.long +import ext.parsing +import icu.samnyan.aqua.sega.general.BaseHandler +import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos +import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPrintDetail +import icu.samnyan.aqua.sega.util.jackson.BasicMapper +import org.springframework.beans.factory.annotation.Value +import org.springframework.stereotype.Component +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter +import java.util.concurrent.ThreadLocalRandom + +@Component("Maimai2UpsertUserPrintHandler") +class UpsertUserPrintHandler( + val mapper: BasicMapper, + val db: Mai2Repos, + @param:Value("\${game.cardmaker.card.expiration:15}") val expirationTime: Long, +) : BaseHandler { + val log = logger() + val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS") + + override fun handle(request: Map): Any? { + val userId = parsing { request["userId"]!!.long } + val userData = db.userData.findByCardExtId(userId)() ?: return null + + val userPrint = parsing { mapper.convert(request["userPrintDetail"]!!, Mai2UserPrintDetail::class.java) } + val newCard = userPrint.userCard ?: return null + + newCard.user = userData + newCard.startDate = LocalDateTime.now().format(formatter) + newCard.endDate = LocalDateTime.now().plusDays(expirationTime).format(formatter) + newCard.id = db.userCard.findByUserAndCardId(newCard.user, newCard.cardId)()?.id ?: 0 + db.userCard.save(newCard) + + userPrint.user = userData + userPrint.serialId = buildString { + append(String.format("%010d", ThreadLocalRandom.current().nextLong(0L, 9999999999L))) + append(String.format("%010d", ThreadLocalRandom.current().nextLong(0L, 9999999999L))) + } + db.userPrintDetail.save(userPrint) + + return mapOf( + "returnCode" to 1, + "orderId" to 0, + "serialId" to userPrint.serialId, + "startDate" to newCard.startDate, + "endDate" to newCard.endDate + ) + } +} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/UserRating.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/UserRating.kt new file mode 100644 index 00000000..5639e410 --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/UserRating.kt @@ -0,0 +1,13 @@ +package icu.samnyan.aqua.sega.maimai2.model + +import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserRate +import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserUdemae + +class UserRating { + var rating = 0 + var ratingList: List = emptyList() + var newRatingList: List = emptyList() + var nextRatingList: List = emptyList() + var nextNewRatingList: List = emptyList() + var udemae: Mai2UserUdemae = Mai2UserUdemae() +} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/UserRivalMusic.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/UserRivalMusic.kt new file mode 100644 index 00000000..cdadd70b --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/UserRivalMusic.kt @@ -0,0 +1,12 @@ +package icu.samnyan.aqua.sega.maimai2.model + +class UserRivalMusic( + var musicId: Int, + var userRivalMusicDetailList: MutableList = mutableListOf() +) + +class UserRivalMusicDetail( + var level: Int, + var achievement: Int, + var deluxscoreMax: Int +) \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/Mai2UpsertUserAll.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/Mai2UpsertUserAll.kt new file mode 100644 index 00000000..2ad0a8fe --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/Mai2UpsertUserAll.kt @@ -0,0 +1,53 @@ +package icu.samnyan.aqua.sega.maimai2.model.request + +import icu.samnyan.aqua.sega.maimai2.model.UserRating +import icu.samnyan.aqua.sega.maimai2.model.userdata.* + +class Mai2UpsertUserAll( + var userId: Long, + var upsertUserAll: Mai2UserAll +) + +class Mai2UserAll { + var userData: List = emptyList() + var userOption: List? = null + var userExtend: List? = null + var userCharacterList: List? = null + var userGhost: List? = null + var userMapList: List? = null + var userLoginBonusList: List? = null + var userRatingList: List? = null + var userItemList: List? = null + var userMusicDetailList: List? = null + var userCourseList: List? = null + var userFriendSeasonRankingList: List? = null + var userChargeList: List? = null + var userFavoriteList: List? = null + var userActivityList: List? = null + var userGamePlaylogList: List>? = null + var userFavoritemusicList: List? = null + var userKaleidxScopeList: List? = null + var userIntimateList: List? = null + var isNewCharacterList: String? = null + var isNewMapList: String? = null + var isNewLoginBonusList: String? = null + var isNewItemList: String? = null + var isNewMusicDetailList: String? = null + var isNewCourseList: String? = null + var isNewFavoriteList: String? = null + var isNewFriendSeasonRankingList: String? = null + var isNewFavoritemusicList: String? = null + var isNewKaleidxScopeList: String? = null +} + +class Mai2UserFavoriteItem { + var orderId = 0 + var id = 0 +} + +class Mai2UserActivity { + var playList: List = emptyList() + var musicList: List = emptyList() +} + + diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/Mai2UserPhoto.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/Mai2UserPhoto.kt new file mode 100644 index 00000000..8ace5b2b --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/Mai2UserPhoto.kt @@ -0,0 +1,25 @@ +package icu.samnyan.aqua.sega.maimai2.model.request + +class Mai2UserPhoto { + var orderId = 0 + var userId: Long = 0 + var divNumber = 0 + var divLength = 0 + var divData: String? = null + var placeId = 0 + var clientId: String? = null + var uploadDate: String? = null + var playlogId: Long = 0 + var trackNo = 0 +} + +class Mai2UserPortrait { + var userId: Long = 0 + var divNumber = 0 + var divLength = 0 + var divData: String? = null + var placeId = 0 + var clientId: String = "" + var uploadDate: String = "1970-01-01 09:00:00.0" + var fileName: String = "portrait.jpg" +} \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UploadUserPhoto.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UploadUserPhoto.java deleted file mode 100644 index 196a2d39..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UploadUserPhoto.java +++ /dev/null @@ -1,18 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.request; - -import icu.samnyan.aqua.sega.maimai2.model.request.data.UserPhoto; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UploadUserPhoto implements Serializable { - private UserPhoto userPhoto; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UploadUserPlaylog.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UploadUserPlaylog.java deleted file mode 100644 index 7748e4cf..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UploadUserPlaylog.java +++ /dev/null @@ -1,20 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.request; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPlaylog; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UploadUserPlaylog implements Serializable { - private long userId; - private Mai2UserPlaylog userPlaylog; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UploadUserPortrait.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UploadUserPortrait.java deleted file mode 100644 index f1810d67..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UploadUserPortrait.java +++ /dev/null @@ -1,18 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.request; - -import icu.samnyan.aqua.sega.maimai2.model.request.data.UserPortrait; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UploadUserPortrait implements Serializable { - private UserPortrait userPortrait; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UpsertUserAll.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UpsertUserAll.java deleted file mode 100644 index 4ff893de..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UpsertUserAll.java +++ /dev/null @@ -1,25 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.request; - -import com.fasterxml.jackson.annotation.JsonProperty; -import icu.samnyan.aqua.sega.maimai2.model.request.data.UserAll; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UpsertUserAll implements Serializable { - private long userId; - private long playlogId; - @JsonProperty("isEventMode") - private boolean isEventMode; - @JsonProperty("isFreePlay") - private boolean isFreePlay; - private UserAll upsertUserAll; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UpsertUserPrint.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UpsertUserPrint.java deleted file mode 100644 index 62b41ee6..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/UpsertUserPrint.java +++ /dev/null @@ -1,22 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.request; - -import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPrintDetail; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; -import java.util.Map; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UpsertUserPrint implements Serializable { - private long userId; - private long orderId; - private Map userPrintReserve; - private Mai2UserPrintDetail userPrintDetail; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserAll.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserAll.java deleted file mode 100644 index 88c38e9f..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserAll.java +++ /dev/null @@ -1,50 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.request.data; - -import icu.samnyan.aqua.sega.maimai2.model.response.data.Mai2UserActivity; -import icu.samnyan.aqua.sega.maimai2.model.response.data.UserRating; -import icu.samnyan.aqua.sega.maimai2.model.userdata.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; -import java.util.List; -import java.util.Map; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserAll implements Serializable { - private List userData; - private List userExtend; - private List userOption; - private List userCharacterList; - private List userGhost; - private List userMapList; - private List userLoginBonusList; - private List userRatingList; - private List userItemList; - private List userMusicDetailList; - private List userCourseList; - private List userFriendSeasonRankingList; - private List userChargeList; - private List userFavoriteList; - private List userActivityList; - private List> userGamePlaylogList; - private List userFavoritemusicList; - private List userKaleidxScopeList; - private List userIntimateList; - private String isNewCharacterList; - private String isNewMapList; - private String isNewLoginBonusList; - private String isNewItemList; - private String isNewMusicDetailList; - private String isNewCourseList; - private String isNewFavoriteList; - private String isNewFriendSeasonRankingList; - private String isNewFavoritemusicList; - private String isNewKaleidxScopeList; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserFavoriteItem.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserFavoriteItem.java deleted file mode 100644 index 7b03799d..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserFavoriteItem.java +++ /dev/null @@ -1,15 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.request.data; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserFavoriteItem implements Serializable { - private int orderId; - private int id; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserPhoto.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserPhoto.java deleted file mode 100644 index 192a9c37..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserPhoto.java +++ /dev/null @@ -1,26 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.request.data; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserPhoto implements Serializable { - private int orderId; - private long userId; - private int divNumber; - private int divLength; - private String divData; - private int placeId; - private String clientId; - private String uploadDate; - private long playlogId; - private int trackNo; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserPortrait.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserPortrait.java deleted file mode 100644 index ac7be464..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/request/data/UserPortrait.java +++ /dev/null @@ -1,24 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.request.data; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.io.Serializable; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserPortrait implements Serializable { - private long userId; - private int divNumber; - private int divLength; - private String divData; - private int placeId; - private String clientId; - private String uploadDate; - private String fileName; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/Mai2UserActivity.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/Mai2UserActivity.java deleted file mode 100644 index fc59ddb8..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/Mai2UserActivity.java +++ /dev/null @@ -1,19 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.response.data; - -import java.util.List; - -import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserAct; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class Mai2UserActivity { - private List playList; - private List musicList; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/UserRating.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/UserRating.java deleted file mode 100644 index dac23350..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/UserRating.java +++ /dev/null @@ -1,24 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.response.data; - -import java.util.List; - -import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserRate; -import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserUdemae; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserRating { - private int rating; - private List ratingList; - private List newRatingList; - private List nextRatingList; - private List nextNewRatingList; - private Mai2UserUdemae udemae; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/UserRivalMusic.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/UserRivalMusic.java deleted file mode 100644 index 51f26449..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/UserRivalMusic.java +++ /dev/null @@ -1,15 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.response.data; - -import java.util.List; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserRivalMusic { - private int musicId; - private List userRivalMusicDetailList; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/UserRivalMusicDetail.java b/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/UserRivalMusicDetail.java deleted file mode 100644 index f05a8824..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/model/response/data/UserRivalMusicDetail.java +++ /dev/null @@ -1,14 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.model.response.data; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@AllArgsConstructor -@NoArgsConstructor -public class UserRivalMusicDetail { - private int level; - private int achievement; - private int deluxscoreMax; -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/worldslink/FutariLobby.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/worldslink/FutariLobby.kt deleted file mode 100644 index d9fb2b01..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/worldslink/FutariLobby.kt +++ /dev/null @@ -1,96 +0,0 @@ - -package icu.samnyan.aqua.sega.maimai2.worldslink - -import ext.* -import icu.samnyan.aqua.net.utils.PathProps -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.encodeToString -import kotlinx.serialization.json.Json -import org.springframework.web.bind.annotation.RequestMapping -import org.springframework.web.bind.annotation.RestController -import java.io.BufferedWriter -import java.io.File -import java.io.FileOutputStream -import java.time.LocalDateTime -import java.util.concurrent.locks.ReentrantLock -import kotlin.concurrent.withLock -import kotlin.io.path.readText - - -// KotlinX Serialization -@OptIn(ExperimentalSerializationApi::class) -private val KJson = Json { - ignoreUnknownKeys = true - isLenient = true - explicitNulls = false - coerceInputValues = true -} - -// Maximum time to live for a recruit record -const val MAX_TTL = 30 * 1000 - -@RestController -@RequestMapping(path = ["/mai2-futari"]) -class FutariLobby(val paths: PathProps) { - // - val recruits = mutableMapOf() - // Append writer - lateinit var writer: BufferedWriter - val mutex = ReentrantLock() - val log = logger() - - init { - paths.init() - writer = FileOutputStream(File(paths.futariRecruitLog), true).bufferedWriter() - } - - fun log(data: String) = mutex.withLock { - log.info(data) - writer.write(data) - writer.newLine() - writer.flush() - } - - fun log(data: RecruitRecord, msg: String) = - log("${LocalDateTime.now().isoDateTime()}: $msg: ${KJson.encodeToString(data)}") - - val RecruitRecord.ip get() = RecruitInfo.MechaInfo.IpAddress - - @API("recruit/start") - fun startRecruit(@RB data: String) { - val d = parsing { KJson.decodeFromString(data) }.apply { Time = millis() } - val exists = d.ip in recruits - recruits[d.ip] = d - - if (!exists) log(d, "StartRecruit") - d.RecruitInfo.MechaInfo.UserIDs = d.RecruitInfo.MechaInfo.UserIDs.map { it.str.hashToUInt().toLong() } - } - - @API("recruit/finish") - fun finishRecruit(@RB data: String) { - val d = parsing { KJson.decodeFromString(data) } - if (d.ip !in recruits) 400 - "Recruit not found" -// if (d.Keychip != recruits[d.ip]!!.Keychip) 400 - "Keychip mismatch" - recruits.remove(d.ip) - log(d, "EndRecruit") - } - - @API("recruit/list") - fun listRecruit(): String { - val time = millis() - recruits.filterValues { time - it.Time > MAX_TTL }.keys.forEach { recruits.remove(it) } - return recruits.values.toList().joinToString("\n") { KJson.encodeToString(it) } - } - - @API("server-list") - fun serverList() = paths.futariRelayInfo.path().readText().trim() -} - -fun main(args: Array) { - val json = """{"RecruitInfo":{"MechaInfo":{"IsJoin":true,"IpAddress":1820162433,"MusicID":11692,"Entrys":[true,false],"UserIDs":[281474976710657,281474976710657],"UserNames":["GUEST","GUEST"],"IconIDs":[1,1],"FumenDifs":[0,-1],"Rateing":[0,0],"ClassValue":[0,0],"MaxClassValue":[0,0],"UserType":[0,0]},"MusicID":11692,"GroupID":0,"EventModeID":false,"JoinNumber":1,"PartyStance":0,"_startTimeTicks":638725464510308001,"_recvTimeTicks":0}}""" - println(json.jsonMap().toJson()) - val data = KJson.decodeFromString(json) - println(json) - println(KJson.encodeToString(data)) - println(data) -} diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/worldslink/FutariRelay.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/worldslink/FutariRelay.kt deleted file mode 100644 index f037d285..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/worldslink/FutariRelay.kt +++ /dev/null @@ -1,187 +0,0 @@ -package icu.samnyan.aqua.sega.maimai2.worldslink - -import ext.logger -import ext.md5 -import ext.millis -import ext.thread -import java.io.BufferedReader -import java.io.BufferedWriter -import java.io.InputStreamReader -import java.io.OutputStreamWriter -import java.net.ServerSocket -import java.net.Socket -import java.net.SocketTimeoutException -import java.util.concurrent.ConcurrentHashMap -import java.util.concurrent.locks.ReentrantLock -import kotlin.collections.set -import kotlin.concurrent.withLock - -const val PROTO_VERSION = 1 -const val MAX_STREAMS = 10 -const val SO_TIMEOUT = 20000 -//const val SO_TIMEOUT = 10000000 - -fun ctlMsg(cmd: UInt, data: String? = null) = Msg(cmd, data = data) - -data class ActiveClient( - val clientKey: String, - val socket: Socket, - val reader: BufferedReader, - val writer: BufferedWriter, - val thread: Thread = Thread.currentThread(), - // - val tcpStreams: MutableMap = mutableMapOf(), - val pendingStreams: MutableSet = mutableSetOf(), -) { - val log = logger() - val stubIp = keychipToStubIp(clientKey) - val writeMutex = ReentrantLock() - - var lastHeartbeat = millis() - - fun send(msg: Msg) { - writeMutex.withLock { - try { - writer.write(msg.toString()) - writer.newLine() - writer.flush() - } - catch (e: Exception) { - log.error("Error sending message", e) - socket.close() - thread.interrupt() - } - } - } -} - -fun ActiveClient.handle(msg: Msg) { - // Find target by dst IP address or TCP stream ID - val target = (msg.sid?.let { tcpStreams[it] } ?: msg.dst)?.let { clients[it] } - - when (msg.cmd) { - Command.CTL_HEARTBEAT -> { - lastHeartbeat = millis() - send(ctlMsg(Command.CTL_HEARTBEAT)) - } - Command.DATA_BROADCAST -> { - // Broadcast to all clients. This is only used in UDP so SID is always 0 - if (msg.proto != Proto.UDP) return log.warn("TCP Broadcast received, something is wrong.") - clients.values.forEach { it.send(msg.copy(src = stubIp)) } - } - Command.DATA_SEND -> { - target ?: return log.warn("Send: Target not found: ${msg.dst}") - - if (msg.proto == Proto.TCP && msg.sid !in tcpStreams) - return log.warn("Stream ID not found: ${msg.sid}") - - target.send(msg.copy(src = stubIp, dst = target.stubIp)) - } - Command.CTL_TCP_CONNECT -> { - target ?: return log.warn("Connect: Target not found: ${msg.dst}") - val sid = msg.sid ?: return log.warn("Connect: Stream ID not found") - - if (sid in tcpStreams || sid in pendingStreams) - return log.warn("Stream ID already in use: $sid") - - // Add the stream to the pending list - pendingStreams.add(sid) - if (pendingStreams.size > MAX_STREAMS) { - log.warn("Too many pending streams, closing connection") - return socket.close() - } - - target.send(msg.copy(src = stubIp, dst = target.stubIp)) - } - Command.CTL_TCP_ACCEPT -> { - target ?: return log.warn("Accept: Target not found: ${msg.dst}") - val sid = msg.sid ?: return log.warn("Accept: Stream ID not found") - - if (sid !in target.pendingStreams) - return log.warn("Stream ID not found in pending list: $sid") - - // Add the stream to the active list - target.pendingStreams.remove(sid) - target.tcpStreams[sid] = stubIp - tcpStreams[sid] = target.stubIp - - target.send(msg.copy(src = stubIp, dst = target.stubIp)) - } - } -} - -fun String.hashToUInt() = md5().let { - ((it[0].toUInt() and 0xFFu) shl 24) or - ((it[1].toUInt() and 0xFFu) shl 16) or - ((it[2].toUInt() and 0xFFu) shl 8) or - (it[3].toUInt() and 0xFFu) -} - -fun keychipToStubIp(keychip: String) = keychip.hashToUInt() - -// Keychip ID to Socket -val clients = ConcurrentHashMap() - -/** - * Service for the party linker for AquaMai - */ -class MaimaiFutari(private val port: Int = 20101) { - val log = logger() - - fun start() { - val serverSocket = ServerSocket(port) - log.info("Server started on port $port") - - while (true) { - val clientSocket = serverSocket.accept().apply { - soTimeout = SO_TIMEOUT - log.info("[+] Client connected: $remoteSocketAddress") - } - thread { handleClient(clientSocket) } - } - } - - fun handleClient(socket: Socket) { - val reader = BufferedReader(InputStreamReader(socket.getInputStream())) - val writer = BufferedWriter(OutputStreamWriter(socket.getOutputStream())) - var handler: ActiveClient? = null - - try { - while (!Thread.interrupted() && !socket.isClosed) { - val input = (reader.readLine() ?: break).trim('\uFEFF') - if (input != "1,3") log.info("${socket.remoteSocketAddress} (${handler?.clientKey}) <<< $input") - val message = Msg.fromString(input) - - when (message.cmd) { - // Start: Register the client. Payload is the keychip - Command.CTL_START -> { - val id = message.data as String - val client = ActiveClient(id, socket, reader, writer) - clients[client.stubIp]?.socket?.close() - clients[client.stubIp] = client - handler = clients[client.stubIp] - log.info("[+] Client registered: ${socket.remoteSocketAddress} -> $id") - - // Send back the version - handler?.send(ctlMsg(Command.CTL_START, "version=$PROTO_VERSION")) - } - - // Handle any other command using the handler - else -> { - (handler ?: throw Exception("Client not registered")).handle(message) - } - } - } - } catch (e: Exception) { - if (e.message != "Connection reset" && e !is SocketTimeoutException) - log.error("Error in client handler", e) - } finally { - // Remove client - handler?.stubIp?.let { clients.remove(it) } - socket.close() - log.info("[-] Client disconnected: ${handler?.clientKey}") - } - } -} - -fun main() = MaimaiFutari().start() diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/worldslink/FutariTypes.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/worldslink/FutariTypes.kt deleted file mode 100644 index 4544b412..00000000 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/worldslink/FutariTypes.kt +++ /dev/null @@ -1,100 +0,0 @@ -@file:Suppress("PropertyName") - -package icu.samnyan.aqua.sega.maimai2.worldslink - -import ext.* -import kotlinx.serialization.Serializable - -object Command { - // Control plane - const val CTL_START = 1u - const val CTL_BIND = 2u - const val CTL_HEARTBEAT = 3u - const val CTL_TCP_CONNECT = 4u // Accept a new multiplexed TCP stream - const val CTL_TCP_ACCEPT = 5u - const val CTL_TCP_ACCEPT_ACK = 6u - const val CTL_TCP_CLOSE = 7u - - // Data plane - const val DATA_SEND = 21u - const val DATA_BROADCAST = 22u -} - -object Proto { - const val TCP = 6u - const val UDP = 17u -} - -data class Msg( - var cmd: UInt, - var proto: UInt? = null, - var sid: UInt? = null, - var src: UInt? = null, - var sPort: UInt? = null, - var dst: UInt? = null, - var dPort: UInt? = null, - var data: String? = null -) { - override fun toString() = ls( - 1, cmd, proto, sid, src, sPort, dst, dPort, - null, null, null, null, null, null, null, null, // reserved for future use - data - ).joinToString(",") { it?.str ?: "" }.trimEnd(',') - - companion object { - val fields = arr(Msg::proto, Msg::sid, Msg::src, Msg::sPort, Msg::dst, Msg::dPort) - - fun fromString(str: String): Msg { - val sp = str.split(',') - return Msg(0u).apply { - cmd = sp[1].toUInt() - fields.forEachIndexed { i, f -> f.set(this, sp.getOrNull(i + 2)?.some?.toUIntOrNull()) } - data = sp.drop(16).joinToString(",") - } - } - } -} - -@Serializable -data class MechaInfo( - val IsJoin: Bool, - val IpAddress: UInt, - val MusicID: Int, - val Entrys: List, - var UserIDs: List, - val UserNames: List, - val IconIDs: List, - val FumenDifs: List, - val Rateing: List, - val ClassValue: List, - val MaxClassValue: List, - val UserType: List -) - -@Serializable -data class RecruitInfo( - val MechaInfo: MechaInfo, - val MusicID: Int, - val GroupID: Int, - val EventModeID: Boolean, - val JoinNumber: Int, - val PartyStance: Int, - val _startTimeTicks: Long, - val _recvTimeTicks: Long -) - -@Serializable -data class RecruitRecord( - val RecruitInfo: RecruitInfo, - val Keychip: String, - var Server: RelayServerInfo? = null, - var Time: Long = 0, -) - -@Serializable -data class RelayServerInfo( - val name: String, - val addr: String, - val port: Int = 20101, - val official: Bool = true -) diff --git a/src/main/java/icu/samnyan/aqua/sega/util/jackson/BasicMapper.kt b/src/main/java/icu/samnyan/aqua/sega/util/jackson/BasicMapper.kt index 216aea0a..758e6c03 100644 --- a/src/main/java/icu/samnyan/aqua/sega/util/jackson/BasicMapper.kt +++ b/src/main/java/icu/samnyan/aqua/sega/util/jackson/BasicMapper.kt @@ -79,9 +79,9 @@ class StringMapper: IMapper(STRING_MAPPER) // Testing code -private class A { - var cat = "" -} +private class A( + var cat: String = "" +) fun main(args: Array) { val json = """{"cat":"meow"}"""