diff --git a/src/main/java/ext/Ext.kt b/src/main/java/ext/Ext.kt index 97f9db35..dff3d0b5 100644 --- a/src/main/java/ext/Ext.kt +++ b/src/main/java/ext/Ext.kt @@ -111,6 +111,10 @@ fun Str.asDateTime() = try { LocalDateTime.parse(this, DateTimeFormatter.ISO_LOC catch (e: Exception) { try { LocalDateTime.parse(this, ALT_DATETIME_FORMAT) } catch (e: Exception) { null } } +val Calendar.year get() = get(Calendar.YEAR) +val Calendar.month get() = get(Calendar.MONTH) + 1 +val Calendar.day get() = get(Calendar.DAY_OF_MONTH) + // Encodings fun Long.toHex(len: Int = 16): Str = "0x${this.toString(len).padStart(len, '0').uppercase()}" fun Map.toUrl() = entries.joinToString("&") { (k, v) -> "$k=$v" } diff --git a/src/main/java/ext/Json.kt b/src/main/java/ext/Json.kt index 3642c58e..302e8231 100644 --- a/src/main/java/ext/Json.kt +++ b/src/main/java/ext/Json.kt @@ -41,6 +41,17 @@ inline fun Str.parseJackson() = if (contains("null")) { else JACKSON.readValue(this, T::class.java) fun T.toJson() = JACKSON.writeValueAsString(this) +inline fun String.json() = try { + JACKSON.readValue(this, T::class.java) +} +catch (e: Exception) { + println("Failed to parse JSON: $this") + throw e +} + +fun String.jsonMap(): Map = json() +fun String.jsonArray(): List> = json() + // KotlinX Serialization @OptIn(ExperimentalSerializationApi::class) diff --git a/src/main/java/icu/samnyan/aqua/sega/wacca/WaccaServer.kt b/src/main/java/icu/samnyan/aqua/sega/wacca/WaccaServer.kt index c658d945..068679b8 100644 --- a/src/main/java/icu/samnyan/aqua/sega/wacca/WaccaServer.kt +++ b/src/main/java/icu/samnyan/aqua/sega/wacca/WaccaServer.kt @@ -12,24 +12,25 @@ import io.ktor.client.utils.* import jakarta.servlet.http.HttpServletRequest import org.springframework.http.ResponseEntity import org.springframework.web.bind.annotation.RestController +import java.util.* val empty = emptyList() @RestController @API("/g/wacca/") class WaccaServer(val rp: WaccaRepos, val cardRepo: CardRepository) { - val handlerMap = mutableMapOf) -> List>() + val handlerMap = mutableMapOf) -> Any>() val cacheMap = mutableMapOf() val log = logger() - init { api() } + init { init() } // DSL Functions fun options(u: WaccaUser?) = u?.let { rp.option.findByUser(u).associate { it.optId to it.value } } ?: emptyMap() operator fun Map.get(type: WaccaOptionType) = getOrDefault(type.id, type.default) operator fun String.minus(value: Any) = value - operator fun String.invoke(block: (BaseRequest, List) -> List) { handlerMap[this.lowercase()] = block } + operator fun String.invoke(block: (BaseRequest, List) -> Any) { handlerMap[this.lowercase()] = block } infix fun String.cached(block: () -> Any) { cacheMap[this.lowercase()] = block().toJson() } /** Convert "3.07.01.JPN.26935.S" into "3.7.1" */ @@ -54,13 +55,18 @@ class WaccaServer(val rp: WaccaRepos, val cardRepo: CardRepository) { if (path in cacheMap) return resp(cacheMap[path]!!) if (path !in handlerMap) return resp("[]", 1, "Not Found") + log.info("Wacca $path < $body") + val br = JACKSON.parse(body) - val resp = handlerMap[path]!!(br, br.params) - return resp(resp.toJson()) + return handlerMap[path]!!(br, br.params).let { when (it) { + is String -> return resp(it) + is List<*> -> return resp(it.toJson()) + else -> Error("Invalid response type ${it.javaClass}") + } }.let { log.info("Wacca $path > $it") } } } -fun WaccaServer.api() { +fun WaccaServer.init() { "housing/get" cached { ls("housingId" - 39, "isNewCab" - 0) } "housing/start" cached { ls("regionId" - 1, "recommendSongList" - ls(1269, 1007, 1270, 1002, 1020, 1003, 1008, 1211, 1018, 1092, 1056, 32, 1260, 1230, 1258, 1251, 2212, 1264, 1125, 1037, 2001, 1272, 1126, 1119, 1104, 1070, @@ -90,7 +96,7 @@ fun WaccaServer.api() { "user/status/create" { _, (uid, name) -> val u = rp.user.save(WaccaUser().apply { - card = cardRepo.findByExtId(uid.long())() ?: (400 - "Card not found") + card = cardRepo.findByExtId(uid.long())() ?: (404 - "Card not found") username = name.toString() }) @@ -107,4 +113,22 @@ fun WaccaServer.api() { ls(u.lStatus()) } + + "user/status/login" api@ { _, (uid) -> + if (uid == 0) return@api "[[], [], [], 0, [2077, 1, 1, 1, [], []], 0, []]" + val u = rp.user.findByCardExtId(uid.long()) ?: (404 - "User not found") + + // Record login + u.loginCountConsec = u.loginCount++ + if (millis() - u.lastLoginDate.time > 86400000) { // Is new day + u.loginCountDays++ + u.loginCountToday = 0 + if (millis() - u.lastLoginDate.time < 172800000) u.loginCountDaysConsec++ + } + u.loginCountToday++ + u.lastLoginDate = Date() + + "[[], [], [], 0, [2077, 1, 1, 1, [], []], ${u.lastLoginDate.time / 1000}, []]" + } + } \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/sega/wacca/model/db/WaccaUser.kt b/src/main/java/icu/samnyan/aqua/sega/wacca/model/db/WaccaUser.kt index 6db51cf4..df8bb2f5 100644 --- a/src/main/java/icu/samnyan/aqua/sega/wacca/model/db/WaccaUser.kt +++ b/src/main/java/icu/samnyan/aqua/sega/wacca/model/db/WaccaUser.kt @@ -51,7 +51,8 @@ class WaccaUser : BaseEntity() { var lastFolderOrder = 0 var lastFolderId = 0 var lastSongOrder = 0 - var lastLoginDate: String? = null + @Temporal(TemporalType.TIME) + var lastLoginDate: Date = Date(0) var gateTutorialFlags: String? = null fun lStatus() = ls(card.extId, username, 1, xp, danLevel, danType, wp, ls(0, 0, 0), loginCount, loginCountDays, diff --git a/src/test/kotlin/ext/TestExt.kt b/src/test/kotlin/ext/TestExt.kt index 7c608990..aba1fc92 100644 --- a/src/test/kotlin/ext/TestExt.kt +++ b/src/test/kotlin/ext/TestExt.kt @@ -12,17 +12,6 @@ const val FTK = "test" const val HOST = "http://localhost" val ACCESS_CODE = "9900" + (1..16).map { Random.nextInt(0..9) }.joinToString("") -inline fun String.json() = try { - JACKSON.readValue(this, T::class.java) -} -catch (e: Exception) { - println("Failed to parse JSON: $this") - throw e -} - -fun String.jsonMap(): Map = json() -fun String.jsonArray(): List> = json() - suspend fun registerUser(): Long { val resp = HTTP.post(HOST.ensureEndingSlash() + "api/v2/frontier/register-card") { parameter("ftk", FTK) diff --git a/src/test/kotlin/test/WaccaTest.kt b/src/test/kotlin/test/WaccaTest.kt index 3722db3e..6f307f85 100644 --- a/src/test/kotlin/test/WaccaTest.kt +++ b/src/test/kotlin/test/WaccaTest.kt @@ -28,31 +28,45 @@ class WaccaTest : StringSpec({ return PostResp(resp, res["params"] as List) } + infix fun List.exp(expected: List) { + expected.size shouldBe size + for (i in indices) { + val a = this[i] + when (val b = expected[i]) { + null -> {} // Use null to ignore the value + is List<*> -> a as List exp b as List + else -> a shouldBe b + } + } + } + + infix fun List.exp(json: String) = exp(json.jsonArray()) + beforeTest { if (uid == 0L) uid = registerUser() } "housing/get #1" { - post("housing/get", "[]").res shouldBe "[39, 0]".jsonArray() + post("housing/get", "[]").res exp "[39, 0]" } "housing/start #1" { - post("housing/start", """["", "2024/03/24 10:39:36, ApiUserStatusLogout,0\\n2024/03/24 10:51:06, ApiUserStatusLogout,0\\n2024/03/24 10:54:19, ApiUserStatusLogout,0\\n2024/03/24 10:59:33, ApiAdvertiseGetNews,0\\n2024/03/24 11:10:31, ApiAdvertiseGetNews,0\\n2024/03/24 11:11:04, ApiUserStatusLogout,0\\n2024/03/24 11:19:51, ,0\\n2024/03/24 11:20:14, ApiAdvertiseGetNews,0\\n", "", [[1, "SERVER"], [2, "JPN"]]]""").res shouldBe - "[1, [1269, 1007, 1270, 1002, 1020, 1003, 1008, 1211, 1018, 1092, 1056, 32, 1260, 1230, 1258, 1251, 2212, 1264, 1125, 1037, 2001, 1272, 1126, 1119, 1104, 1070, 1047, 1044, 1027, 1004, 1001, 24, 2068, 2062, 2021, 1275, 1249, 1207, 1203, 1107, 1021, 1009, 9, 4, 3, 23, 22, 2014, 13, 1276, 1247, 1240, 1237, 1128, 1114, 1110, 1109, 1102, 1045, 1043, 1036, 1035, 1030, 1023, 1015]]".jsonArray() + post("housing/start", """["", "2024/03/24 10:39:36, ApiUserStatusLogout,0\\n2024/03/24 10:51:06, ApiUserStatusLogout,0\\n2024/03/24 10:54:19, ApiUserStatusLogout,0\\n2024/03/24 10:59:33, ApiAdvertiseGetNews,0\\n2024/03/24 11:10:31, ApiAdvertiseGetNews,0\\n2024/03/24 11:11:04, ApiUserStatusLogout,0\\n2024/03/24 11:19:51, ,0\\n2024/03/24 11:20:14, ApiAdvertiseGetNews,0\\n", "", [[1, "SERVER"], [2, "JPN"]]]""").res exp + "[1, [1269, 1007, 1270, 1002, 1020, 1003, 1008, 1211, 1018, 1092, 1056, 32, 1260, 1230, 1258, 1251, 2212, 1264, 1125, 1037, 2001, 1272, 1126, 1119, 1104, 1070, 1047, 1044, 1027, 1004, 1001, 24, 2068, 2062, 2021, 1275, 1249, 1207, 1203, 1107, 1021, 1009, 9, 4, 3, 23, 22, 2014, 13, 1276, 1247, 1240, 1237, 1128, 1114, 1110, 1109, 1102, 1045, 1043, 1036, 1035, 1030, 1023, 1015]]" } "advertise/GetNews #1" { - post("advertise/GetNews", "[]").res shouldBe - "[[], [], [], [], [], [], [], [], []]".jsonArray() + post("advertise/GetNews", "[]").res exp + "[[], [], [], [], [], [], [], [], []]" } "user/status/get #1" { - post("user/status/get", """["$uid"]""").res shouldBe - """[[0, "", 1, 0, 0, 0, 0, [0, 0, 0], 0, 0, 0, 0, 0, 0, 0], 104001, 102001, 1, [2, "1.0.0"], []]""".jsonArray() + post("user/status/get", """["$uid"]""").res exp + """[[0, "", 1, 0, 0, 0, 0, [0, 0, 0], 0, 0, 0, 0, 0, 0, 0], 104001, 102001, 1, [2, "1.0.0"], []]""" } "user/status/create #1" { - post("user/status/create", """["$uid", "AZA"]""").res shouldBe - """[[$uid, "AZA", 1, 0, 0, 0, 0, [0, 0, 0], 0, 0, 0, 0, 0, 0, 0]]""".jsonArray() + post("user/status/create", """["$uid", "AZA"]""").res exp + """[[$uid, "AZA", 1, 0, 0, 0, 0, [0, 0, 0], 0, 0, 0, 0, 0, 0, 0]]""" } }) \ No newline at end of file