From 7fd7e17d1dadc94306f26688227ee17531bac547 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Wed, 20 Mar 2024 02:00:28 -0400 Subject: [PATCH] [+] mai2 artemis import script --- .../java/icu/samnyan/aqua/net/games/Models.kt | 17 ++-- .../samnyan/aqua/net/games/mai2/Mai2Import.kt | 98 +++++++++++++++++++ .../samnyan/aqua/net/games/mai2/Maimai2.kt | 4 +- 3 files changed, 111 insertions(+), 8 deletions(-) create mode 100644 src/main/java/icu/samnyan/aqua/net/games/mai2/Mai2Import.kt diff --git a/src/main/java/icu/samnyan/aqua/net/games/Models.kt b/src/main/java/icu/samnyan/aqua/net/games/Models.kt index 2bd72672..b601e1b0 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/Models.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/Models.kt @@ -1,19 +1,15 @@ package icu.samnyan.aqua.net.games -import ext.* -import icu.samnyan.aqua.net.db.AquaUserServices -import icu.samnyan.aqua.net.utils.* -import icu.samnyan.aqua.sega.chusan.model.userdata.UserData import icu.samnyan.aqua.sega.general.model.Card import kotlinx.serialization.Serializable +import org.springframework.context.annotation.Import import org.springframework.data.domain.Page import org.springframework.data.domain.Pageable import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.NoRepositoryBean import java.util.* -import kotlin.jvm.optionals.getOrNull -import kotlin.reflect.KClass +import kotlin.system.measureTimeMillis data class TrendOut(val date: String, val rating: Int, val plays: Int) @@ -114,4 +110,13 @@ interface GenericUserDataRepo : JpaRepository { interface GenericPlaylogRepo : JpaRepository { fun findByUserCardExtId(extId: Long): List fun findByUserCardExtId(extId: Long, page: Pageable): Page +} + +data class ImportResult(val errors: List, val warnings: List, val json: String) + +interface GameDataImport { + /** + * Read an artemis SQL dump file and return Aqua JSON + */ + fun importArtemisSql(sql: String): ImportResult } \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/net/games/mai2/Mai2Import.kt b/src/main/java/icu/samnyan/aqua/net/games/mai2/Mai2Import.kt new file mode 100644 index 00000000..4e7093df --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/net/games/mai2/Mai2Import.kt @@ -0,0 +1,98 @@ +package icu.samnyan.aqua.net.games.mai2 + +import ext.jackson +import icu.samnyan.aqua.api.model.resp.sega.maimai2.external.Maimai2DataExport +import icu.samnyan.aqua.net.games.GameDataImport +import icu.samnyan.aqua.net.games.ImportResult +import icu.samnyan.aqua.net.games.asSqlInsert +import icu.samnyan.aqua.sega.maimai2.model.userdata.* +import kotlin.io.path.Path +import kotlin.io.path.readText +import kotlin.io.path.writeText +import kotlin.reflect.KClass +import kotlin.time.measureTime + +data class ImportClass( + val type: KClass, + val renames: Map? = null, + val name: String = type.simpleName!!.lowercase() +) +val exportFields = Maimai2DataExport::class.java.declaredFields.associateBy { + it.name.replace("List", "").lowercase() +} + +class Mai2Import : GameDataImport { + fun ImportClass.mapTo(rawDict: Map): T { + // Process renaming + var dict = renames?.let { rawDict + .filter { (k, _) -> if (k in it) it[k] != null else true } + .mapKeys { (k, _) -> it[k] ?: k } } ?: rawDict + + // Process Nones + dict = dict.filterValues { it != "None" } + + return jackson.convertValue(dict, type.java) + } + + val renameTable = mapOf( + "mai2_item_character" to ImportClass(UserCharacter::class), + "mai2_item_charge" to ImportClass(UserCharge::class), + "mai2_item_friend_season_ranking" to ImportClass(UserFriendSeasonRanking::class), + "mai2_item_item" to ImportClass(UserItem::class, mapOf("isValid" to "valid")), + "mai2_item_login_bonus" to ImportClass(UserLoginBonus::class), + "mai2_item_map" to ImportClass(UserMap::class), + "mai2_playlog" to ImportClass(UserPlaylog::class, mapOf("userId" to null)), + "mai2_profile_activity" to ImportClass(UserAct::class, mapOf("activityId" to "id")), + "mai2_profile_detail" to ImportClass(UserDetail::class, + mapOf("user" to null, "version" to null, "isNetMember" to null), + name = "userdata"), + "mai2_profile_extend" to ImportClass(UserExtend::class, mapOf("version" to null)), + "mai2_profile_option" to ImportClass(UserOption::class, mapOf("version" to null)), + "mai2_score_best" to ImportClass(UserMusicDetail::class), + "mai2_score_course" to ImportClass(UserCourse::class), + // "mai2_profile_ghost" to ImportClass(UserGhost::class), + // "mai2_profile_rating" to ImportClass(UserRating::class), + // "mai2_profile_region" to ImportClass(UserRegion::class), + ) + + init { + renameTable.values.forEach { + if (it.name !in exportFields) error("Code error! Export fields incomplete") + } + } + + @Suppress("UNCHECKED_CAST") + override fun importArtemisSql(sql: String): ImportResult { + val data = Maimai2DataExport("SDEZ", UserDetail(), UserExtend(), UserOption(), + ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), + ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), + ArrayList(), UserUdemae()) + val errors = ArrayList() + val warnings = ArrayList() + fun err(msg: String) { errors.add(msg) } + fun warn(msg: String) { warnings.add(msg) } + + sql.lines() + + val lists = exportFields.filter { it.value.type == List::class.java } + .mapValues { it.value.get(data) as ArrayList } + + val statements = sql.replace("\r\n", "\n").split('\n', '\r').mapNotNull { + try { it.asSqlInsert() } + catch (e: Exception) { err("Failed to parse insert: $it\n${e.message}"); null } + } + + // For each insert statement, we will try to parse the values + statements.forEachIndexed fi@{ i, insert -> + // Try to map tables + val tb = renameTable[insert.table] ?: return@fi warn("Unknown table ${insert.table} in insert $i") + val field = exportFields[tb.name]!! + val obj = tb.mapTo(insert.mapping) + + // Add value to list or set field + lists[tb.name]?.add(obj) ?: field.set(data, obj) + } + + return ImportResult(errors, warnings, jackson.writeValueAsString(data)) + } +} \ No newline at end of file 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 d42e53bc..5f189084 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 @@ -47,8 +47,7 @@ class Maimai2( } // Use reflection to get all properties in Mai2Repos with matching names in Maimai2DataExport - var exportFields: Map> = listOf(*Maimai2DataExport::class.java.declaredFields) - .filter { it.name !in arrayOf("gameId", "userData") } + val exportFields: Map> = listOf(*Maimai2DataExport::class.java.declaredFields) .associateWith { Mai2Repos::class.declaredMembers .filter { f -> f returns UserLinked::class } .firstOrNull { f -> f.name == it.name || f.name == it.name.replace("List", "") } @@ -62,6 +61,7 @@ class Maimai2( gameId = "SDEZ" userData = repos.userData.findByCard(u.ghostCard) ?: (404 - "User not found") exportFields.forEach { (f, u) -> + if (f.name == "gameId" || f.name == "userData") return@forEach f.set(this, if (f.type == List::class.java) u.findByUser(userData) else u.findSingleByUser(userData).orElse(null)) }