From 3b19257ab1070e4966972797300397cfe38fe6c9 Mon Sep 17 00:00:00 2001 From: Raymond <101374892+raymonable@users.noreply.github.com> Date: Sun, 21 Dec 2025 18:19:09 -0500 Subject: [PATCH] feat: :sparkles: option APIs for chu3, mai2 & ongeki (#194) --- .../aqua/net/games/GameApiController.kt | 8 ++++-- .../icu/samnyan/aqua/net/games/chu3/Chusan.kt | 25 ++++++++++++++++- .../samnyan/aqua/net/games/mai2/Maimai2.kt | 21 ++++++++++++++ .../samnyan/aqua/net/games/ongeki/Ongeki.kt | 28 ++++++++++++++++++- 4 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/main/java/icu/samnyan/aqua/net/games/GameApiController.kt b/src/main/java/icu/samnyan/aqua/net/games/GameApiController.kt index 6496b878..9b79ecba 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/GameApiController.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/GameApiController.kt @@ -27,6 +27,7 @@ abstract class GameApiController(val name: String, userDataClass: abstract val playlogRepo: GenericPlaylogRepo<*> abstract val userMusicRepo: GenericUserMusicRepo<*> abstract val shownRanks: List> + abstract val settableFields: Map Unit> open val gettableFields: Set = setOf() @@ -126,13 +127,11 @@ abstract class GameApiController(val name: String, userDataClass: (settableFields.keys.toSet() + gettableFields) .associateWith { k -> (vm[k] ?: error("Field $k not found")) } } } - @API("user-detail") suspend fun userDetail(@RP username: String) = us.cardByName(username) { card -> val u = userDataRepo.findByCard(card) ?: (404 - "User not found") userDetailFields.toList().associate { (k, f) -> k to f.invoke(u) } } - @API("user-detail-set") suspend fun userDetailSet(@RP token: String, @RP field: String, @RP value: String): Any { val prop = settableFields[field] ?: (400 - "Invalid field $field") @@ -146,6 +145,11 @@ abstract class GameApiController(val name: String, userDataClass: } } + @API("user-option") + open suspend fun userOption(@RP token: String): Any? = 400 - "Unsupported by this game" + @API("user-option-set") + open suspend fun userOptionSet(@RP token: String, @RP field: String, @RP value: Int): Any = 400 - "Unsupported by this game" + @API("user-music-from-list") suspend fun userMusicFromList(@RP username: Str, @RB musicList: List) = us.cardByName(username) { card -> userMusicRepo.findByUser_Card_ExtIdAndMusicIdIn(card.extId, musicList) diff --git a/src/main/java/icu/samnyan/aqua/net/games/chu3/Chusan.kt b/src/main/java/icu/samnyan/aqua/net/games/chu3/Chusan.kt index 97c9f2a5..2efced3d 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/chu3/Chusan.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/chu3/Chusan.kt @@ -6,7 +6,12 @@ import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.utils.* import icu.samnyan.aqua.sega.chusan.model.* import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData +import icu.samnyan.aqua.sega.chusan.model.userdata.UserGameOption import org.springframework.web.bind.annotation.RestController +import kotlin.jvm.optionals.getOrNull +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.memberProperties @RestController @API("api/v2/game/chu3") @@ -33,6 +38,7 @@ class Chusan( "trophyIdSub2" to { u, v -> u.trophyIdSub2 = v.int }, "mapIconId" to { u, v -> u.mapIconId = v.int }, "voiceId" to { u, v -> u.voiceId = v.int }, + "characterId" to { u, v -> u.characterId = v.int }, "avatarWear" to { u, v -> u.avatarWear = v.int }, "avatarHead" to { u, v -> u.avatarHead = v.int }, "avatarFace" to { u, v -> u.avatarFace = v.int }, @@ -44,7 +50,7 @@ class Chusan( "lastRomVersion" to { u, v -> u.lastRomVersion = v }, "lastDataVersion" to { u, v -> u.lastDataVersion = v }, ) } - override val gettableFields: Set = setOf("level", "playerRating", "characterId") + override val gettableFields: Set = setOf("level", "playerRating") override suspend fun userSummary(@RP username: Str, @RP token: String?) = us.cardByName(username) { card -> // Summary values: total plays, player rating, server-wide ranking @@ -94,6 +100,23 @@ class Chusan( ) } + @API("user-option") + override suspend fun userOption(@RP token: String): Any? = us.jwt.auth(token) { u -> + rp.userGameOption.findByUser_Card_ExtId(u.ghostCard.extId).getOrNull(0) + } + @API("user-option-set") + override suspend fun userOptionSet(@RP token: String, @RP field: String, @RP value: Int): Any = us.jwt.auth(token) { u -> + val gameOptions = rp.userGameOption.findSingleByUser_Card_ExtId(u.ghostCard.extId).getOrNull() + val property = UserGameOption::class.memberProperties.filterIsInstance>().find{ it.name == field } + + if (property != null && gameOptions != null) { + property.setter.call(gameOptions, value) + rp.userGameOption.save(gameOptions) + 200 - "Success" + } else + 400 - "Invalid parameters" + } + // UserBox related APIs @API("user-box") fun userBox(@RP token: String) = us.jwt.auth(token) { diff --git a/src/main/java/icu/samnyan/aqua/net/games/mai2/Maimai2.kt b/src/main/java/icu/samnyan/aqua/net/games/mai2/Maimai2.kt index 2a73eef4..ae21fcb5 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/mai2/Maimai2.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/mai2/Maimai2.kt @@ -11,6 +11,9 @@ import org.springframework.http.MediaType import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RestController import java.util.* +import kotlin.jvm.optionals.getOrNull +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.full.memberProperties @RestController @API("api/v2/game/mai2") @@ -145,6 +148,24 @@ class Maimai2( SUCCESS } + @API("user-option") + override suspend fun userOption(@RP token: String) = us.jwt.auth(token) { u -> + repos.userOption.findByUser_Card_ExtId(u.ghostCard.extId).getOrNull(0) + } + @API("user-option-set") + override suspend fun userOptionSet(@RP token: String, @RP field: String, @RP value: Int): Any = us.jwt.auth(token) { u -> + val gameOptions = repos.userOption.findSingleByUser_Card_ExtId(u.ghostCard.extId).getOrNull() + val property = Mai2UserOption::class.memberProperties.filterIsInstance>().find{ it.name == field } + + if (property != null && gameOptions != null) { + property.setter.call(gameOptions, value) + repos.userOption.save(gameOptions) + 200 - "Success" + } else + 400 - "Invalid parameters" + + } + @API("owned-items") suspend fun ownedItems(@RP token: String) = us.jwt.auth(token) { u -> us.cardByName(u.username) { card -> diff --git a/src/main/java/icu/samnyan/aqua/net/games/ongeki/Ongeki.kt b/src/main/java/icu/samnyan/aqua/net/games/ongeki/Ongeki.kt index 630dc1e1..ffa2c951 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/ongeki/Ongeki.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/ongeki/Ongeki.kt @@ -1,15 +1,23 @@ package icu.samnyan.aqua.net.games.ongeki import ext.API +import ext.RP +import ext.minus import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.utils.* import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo import icu.samnyan.aqua.sega.ongeki.OgkUserGeneralDataRepo import icu.samnyan.aqua.sega.ongeki.OgkUserMusicDetailRepo +import icu.samnyan.aqua.sega.ongeki.OgkUserOptionRepo import icu.samnyan.aqua.sega.ongeki.OgkUserPlaylogRepo import icu.samnyan.aqua.sega.ongeki.model.UserData +import icu.samnyan.aqua.sega.ongeki.model.UserOption +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController +import kotlin.jvm.optionals.getOrNull +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.full.memberProperties @RestController @API("api/v2/game/ongeki") @@ -18,7 +26,8 @@ class Ongeki( override val playlogRepo: OgkUserPlaylogRepo, override val userDataRepo: OgkUserDataRepo, override val userMusicRepo: OgkUserMusicDetailRepo, - val userGeneralDataRepository: OgkUserGeneralDataRepo + val userGeneralDataRepository: OgkUserGeneralDataRepo, + val userOptionRepo: OgkUserOptionRepo ): GameApiController("ongeki", UserData::class) { override suspend fun trend(username: String) = us.cardByName(username) { card -> findTrend(playlogRepo.findByUser_Card_ExtId(card.extId) @@ -45,4 +54,21 @@ class Ongeki( genericUserSummary(card, ratingComposition) } + + @API("user-option") + override suspend fun userOption(@RP token: String) = us.jwt.auth(token) { u -> + userOptionRepo.findByUser_Card_ExtId(u.ghostCard.extId).getOrNull(0) + } + @API("user-option-set") + override suspend fun userOptionSet(@RP token: String, @RP field: String, @RP value: Int): Any = us.jwt.auth(token) { u -> + val gameOptions = userOptionRepo.findSingleByUser_Card_ExtId(u.ghostCard.extId).getOrNull() + val property = UserOption::class.memberProperties.filterIsInstance>().find{ it.name == field } + + if (property != null && gameOptions != null) { + property.setter.call(gameOptions, value) + userOptionRepo.save(gameOptions) + 200 - "Success" + } else + 400 - "Invalid parameters" + } }