59 Commits

Author SHA1 Message Date
Azalea
c6175b372e [-] Completely remove lombok 2025-10-25 12:14:06 +09:00
Azalea
b5e27e859b [O] Optimize imports 2025-10-25 12:14:06 +09:00
Azalea
458367668e [O] Other models > kt 2025-10-25 12:14:06 +09:00
Azalea
20b6af11cc [M] Move 2025-10-25 12:14:06 +09:00
Azalea
7fd71e389d [O] Abstract pd_id 2025-10-25 12:14:06 +09:00
Azalea
1827c8db55 [-] Remove unused 2025-10-25 12:14:06 +09:00
Azalea
4cdf324f85 [O] Request > kt 2025-10-25 12:14:06 +09:00
Azalea
8e5827eeaf [O] DB > kt 2025-10-25 12:14:06 +09:00
Azalea
3a54798d5f [O] Pojoless 2025-10-25 12:14:06 +09:00
Azalea
55869f9d07 [O] Modelless 2025-10-25 12:14:06 +09:00
Azalea
8fc2a74f95 [O] Baseless 2025-10-25 12:14:06 +09:00
Azalea
7e90aec55d [O] Auto optimize 2025-10-25 12:14:06 +09:00
Azalea
e90123389c [O] Capsule 2025-10-25 12:14:06 +09:00
Azalea
b7cb3cb6ea [-] Drop ng words and unused repo functinos 2025-10-25 12:14:06 +09:00
Azalea
619caec2ff [O] Use global db 2025-10-25 12:14:06 +09:00
Azalea
c7a5458f11 [-] Remove unused imports 2025-10-25 12:14:06 +09:00
Azalea
419e22cc49 [-] BaseHandler 2025-10-25 12:14:06 +09:00
Azalea
8d48ab0d3f [O] DIVA utils > kt 2025-10-25 12:14:06 +09:00
Azalea
32c3226db7 [O] DIVA repos > kt 2025-10-25 12:14:06 +09:00
Azalea
acf76bd270 [O] DIVA kt > better kt 2025-10-25 12:14:06 +09:00
Azalea
664365b09b [O] DIVA java > kt 2025-10-25 12:14:06 +09:00
Azalea
38b8831efd [-] No advice 2025-10-25 05:54:00 +08:00
Azalea
6767b62fd5 Update CardMakerController.kt 2025-10-25 05:53:34 +08:00
Azalea
458146ae0c [O] Optimize 2025-10-25 05:52:25 +08:00
Azalea
b2aef08c01 [O] Optional should not exist in kt 2025-10-25 05:50:01 +08:00
Azalea
e7b96d4b24 [-] Remove old files 2025-10-25 05:27:25 +08:00
Azalea
b87ff18c69 [-] Remove old start.bat 2025-10-25 05:26:34 +08:00
Azalea
f3cf6735e4 [F] SQL order 2025-10-25 05:15:39 +08:00
Azalea
ccd6367875 [+] Firendly note 2025-10-25 05:12:15 +08:00
Azalea
01d4262fff [-] Drop chuni database 2025-10-25 05:08:10 +08:00
Azalea
00d74e6b0b [O] Optimize imports 2025-10-25 05:06:37 +08:00
Azalea
a61f6022f9 [-] Remove unused 2025-10-25 05:05:26 +08:00
Azalea
69f4f75347 [M] Move files 2025-10-25 04:59:24 +08:00
Azalea
45e14e21f1 [F] Fix compile 2025-10-25 04:58:41 +08:00
Azalea
7553c6a863 [O] Assume non-null 2025-10-25 04:57:46 +08:00
Azalea
7cd750b11c [O] Java > kt 2025-10-25 04:56:05 +08:00
Azalea
9fe945007c [-] Drop chuni paradise 2025-10-25 04:46:37 +08:00
Azalea
567ed059ad [-] ByteBufUtil 2025-10-25 04:46:18 +08:00
Azalea
6d99beafe9 [F] 2025-10-25 04:35:57 +08:00
Azalea
1ca1e3edc3 [-] Drop maimai finale database 2025-10-25 04:26:06 +08:00
Azalea
c4182fb725 [O] CM Java > kt 2025-10-25 04:18:18 +08:00
Azalea
32d050cb2f [-] Drop maimai finale since literally 0 accounts was created since 2024 2025-10-25 03:42:53 +08:00
Menci
34aae0c87a [F] Player name validation (#186) 2025-10-21 03:46:43 +09:00
Clansty
69bd35a579 [O] Change tip 2025-10-11 04:05:56 +08:00
Menci
3e6c0b4159 feat: user management APIs (#184) 2025-10-07 13:21:01 -07:00
Menci
a33ec8b11c feat: crop pfp to at most 1024px (#183) 2025-10-07 13:20:49 -07:00
Azalea
dd03ca38a1 [F] Fix memory leak 2025-10-07 03:40:49 +08:00
Menci
1cac5e451a refactor: user registrar (#182) 2025-10-06 12:32:52 -07:00
Azalea
010d4592e4 hot fix 2025-10-07 00:30:48 +08:00
Menci
b0d0f8ef7d feat: some data management APIs (#176) 2025-10-06 09:27:39 -07:00
Raymond
967d311ee4 chusan x-v a001 (#180)
Co-authored-by: asterisk727 <59166650+asterisk727@users.noreply.github.com>
2025-10-04 10:59:02 -07:00
crxmsxn
d5b777d720 fix: mobile styling for favorites (#181) 2025-10-04 10:57:31 -07:00
Raymond
2ab2666ad0 Feat: Favorites (for all supported games) (#174) 2025-09-29 22:56:26 -07:00
alexay7
4971f2be78 [+] Add support for geki cm and fix chusan cm implementation (#175) 2025-09-29 22:47:31 -07:00
Menci
b0a49d6626 [+] Add APIs (#177) 2025-09-29 21:54:58 -07:00
凌莞~(=^▽^=)
d830854eaa fix: X-Verse Username (#179) 2025-09-29 22:35:56 -04:00
凌莞~(=^▽^=)
68820d5a86 chore: Bump versions 2025-09-13 21:49:46 +08:00
Raymond
8b079bc40b fix: nvm looks like shit 2025-09-12 11:34:25 -04:00
Raymond
b0dd9b845f chore: bump versions 2025-09-12 11:33:23 -04:00
507 changed files with 6730 additions and 17261 deletions

View File

@@ -1,49 +0,0 @@
name: Gradle Build
on:
pull_request:
branches: [ v1-dev ]
workflow_dispatch:
jobs:
build:
runs-on: macos-arm64
env:
GIT_SSL_NO_VERIFY: true
steps:
- name: Force Git to use HTTP/1.1 (Experimental)
run: |
git config --global http.version HTTP/1.1
git config --global http.sslVerify false
git config --global http.sslVerify false
git config --global https.sslVerify false
git config --global http.proxy 'http://127.0.0.1:7890'
git config --global https.proxy 'http://127.0.0.1:7890'
export NODE_TLS_REJECT_UNAUTHORIZED='0'
- name: Disable SSL verification (Temporary Fix)
run: git config --global http.sslVerify false
- name: Checkout repository
uses: https://gitee.com/github-actions/checkout@v4
- name: Set up JDK
uses: https://gitea.com/actions/setup-java@v3
with:
java-version: '21'
distribution: 'temurin'
- name: Build with Gradle
run: |
mkdir data
bash ./src/main/resources/meta/update.sh
chmod +x gradlew
./gradlew build
- name: Upload artifact
uses: https://gitee.com/actions-mirror/upload-artifact@v3
with:
name: AquaDX-1.0.0.jar
path: build/libs

View File

@@ -1,25 +0,0 @@
image: gradle:alpine
before_script:
- GRADLE_USER_HOME="$(pwd)/.gradle"
- export GRADLE_USER_HOME
build:
stage: build
script: gradle --build-cache assemble
cache:
key: "$CI_COMMIT_REF_NAME"
policy: push
paths:
- build
- .gradle
test:
stage: test
script: gradle check
cache:
key: "$CI_COMMIT_REF_NAME"
policy: pull
paths:
- build
- .gradle

View File

@@ -107,7 +107,8 @@ export interface GenericGameSummary {
lastVersion: string lastVersion: string
ratingComposition: { [key: string]: any } ratingComposition: { [key: string]: any }
recent: GenericGamePlaylog[] recent: GenericGamePlaylog[]
rival?: boolean rival?: boolean,
favorites?: number[]
} }
export interface MusicMeta { export interface MusicMeta {

View File

@@ -28,6 +28,7 @@ export const EN_REF_USER = {
'UserHome.RemoveRival': "Remove from Rival", 'UserHome.RemoveRival': "Remove from Rival",
'UserHome.InvalidGame': "Game ${game} is not supported on the web UI yet. We only support maimai, chunithm, wacca, and ongeki for now.", 'UserHome.InvalidGame': "Game ${game} is not supported on the web UI yet. We only support maimai, chunithm, wacca, and ongeki for now.",
'UserHome.ShowMoreRecent': 'Show more', 'UserHome.ShowMoreRecent': 'Show more',
'UserHome.FavoriteSongs': 'Favorite Songs'
} }
export const EN_REF_Welcome = { export const EN_REF_Welcome = {

View File

@@ -40,6 +40,7 @@ const zhUser: typeof EN_REF_USER = {
'UserHome.RemoveRival': "移除劲敌", 'UserHome.RemoveRival': "移除劲敌",
'UserHome.InvalidGame': "游戏 ${game} 还不支持网页端查看。我们目前只支持舞萌、中二、华卡和音击。", 'UserHome.InvalidGame': "游戏 ${game} 还不支持网页端查看。我们目前只支持舞萌、中二、华卡和音击。",
'UserHome.ShowMoreRecent': "显示更多", 'UserHome.ShowMoreRecent': "显示更多",
'UserHome.FavoriteSongs': "收藏歌曲"
} }
const zhWelcome: typeof EN_REF_Welcome = { const zhWelcome: typeof EN_REF_Welcome = {

View File

@@ -80,11 +80,12 @@
// Don't know why this isn't just a part of the cropper module. Have to do this myself.. What a shame // Don't know why this isn't just a part of the cropper module. Have to do this myself.. What a shame
let canvas = document.createElement("canvas"); let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d"); let ctx = canvas.getContext("2d");
canvas.width = 256; const size = Math.round(Math.min(pfpCrop.width, pfpCrop.height, 1024));
canvas.height = 256; canvas.width = size;
canvas.height = size;
let img = document.createElement("img"); let img = document.createElement("img");
img.onload = () => { img.onload = () => {
ctx?.drawImage(img, pfpCrop.x, pfpCrop.y, pfpCrop.width, pfpCrop.height, 0, 0, 256, 256); ctx?.drawImage(img, pfpCrop.x, pfpCrop.y, pfpCrop.width, pfpCrop.height, 0, 0, size, size);
canvas.toBlob(blob => { canvas.toBlob(blob => {
if (!blob) return; if (!blob) return;
submitting = 'profilePicture' submitting = 'profilePicture'

View File

@@ -325,7 +325,6 @@
<RatingComposition title="Recent 10" comp={d.user.ratingComposition.recent10} {allMusics} game={game != "auto" ? game : "mai2"} top={10}/> <RatingComposition title="Recent 10" comp={d.user.ratingComposition.recent10} {allMusics} game={game != "auto" ? game : "mai2"} top={10}/>
{/if} {/if}
<div class="recent"> <div class="recent">
<h2>{t('UserHome.RecentScores')}</h2> <h2>{t('UserHome.RecentScores')}</h2>
<div class="scores"> <div class="scores">
@@ -368,6 +367,22 @@
{/if} {/if}
</div> </div>
</div> </div>
{#if d.user.favorites != null && d.user.favorites.length > 0}
<div class="favorites">
<h2>{t('UserHome.FavoriteSongs')}</h2>
<div class="scores">
{#each d.user.favorites as favoriteSongId, i}
<div>
<img src={`${DATA_HOST}/d/${game}/music/00${favoriteSongId.toString().padStart(6, '0').substring(2)}.png`} alt="" on:error={coverNotFound} />
<div class="info">
<div class="song-title">{allMusics[favoriteSongId.toString()] ? allMusics[favoriteSongId.toString()].name : t("UserHome.UnknownSong")}</div>
</div>
</div>
{/each}
</div>
</div>
{/if}
{/if} {/if}
<StatusOverlays {error} loading={!d || isLoading} /> <StatusOverlays {error} loading={!d || isLoading} />
@@ -554,6 +569,57 @@
flex-direction: row flex-direction: row
justify-content: space-between justify-content: space-between
.favorites
.scores
display: grid
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr))
gap: 20px
// Image and song info
> div
display: flex
align-items: center
gap: 20px
background-color: rgba(white, 0.03)
border-radius: vars.$border-radius
img
width: 50px
height: 50px
border-radius: vars.$border-radius
object-fit: cover
// Song info and score
> div.info
flex: 1
display: flex
justify-content: space-between
overflow: hidden
flex-direction: column
.first-line
display: flex
flex-direction: row
// Limit song name to one line
.song-title
max-width: 90%
overflow: hidden
text-overflow: ellipsis
white-space: nowrap
// Make song score and rank not wrap
> div:last-child
white-space: nowrap
@media (max-width: vars.$w-mobile)
flex-direction: column
gap: 0
.rank-text
text-align: left
// Recent Scores section // Recent Scores section
.recent .recent
.scores .scores

View File

@@ -33,9 +33,9 @@ Below is a list of games supported by this server.
| Game | Ver | Codename | Thanks to | | Game | Ver | Codename | Thanks to |
|------------------------|------|-------------|------------------------------------------------------| |------------------------|------|-------------|------------------------------------------------------|
| SDHD: CHUNITHM | 2.30 | VERSE | | | SDHD: CHUNITHM | 2.40 | X-VERSE | |
| SDEZ: MaiMai DX | 1.55 | PRiSM Plus | | | SDEZ: MaiMai DX | 1.55 | PRiSM Plus | |
| SDGA: MaiMai DX (Intl) | 1.50 | PRiSM | [@Clansty](https://github.com/clansty) | | SDGA: MaiMai DX (Intl) | 1.55 | PRiSM | [@Clansty](https://github.com/clansty) |
| SDED: Card Maker | 1.39 | | [@Becods](https://github.com/Becods) | | SDED: Card Maker | 1.39 | | [@Becods](https://github.com/Becods) |
| SDDT: O.N.G.E.K.I. | 1.50 | Re:Fresh | [@PenguinCaptain](https://github.com/PenguinCaptain) | | SDDT: O.N.G.E.K.I. | 1.50 | Re:Fresh | [@PenguinCaptain](https://github.com/PenguinCaptain) |
| SBZV: Project DIVA | 7.10 | Future Tone | | | SBZV: Project DIVA | 7.10 | Future Tone | |

View File

@@ -7,14 +7,12 @@ plugins {
val ktVer = "2.1.10" val ktVer = "2.1.10"
java java
kotlin("plugin.lombok") version ktVer
kotlin("jvm") version ktVer kotlin("jvm") version ktVer
kotlin("plugin.spring") version ktVer kotlin("plugin.spring") version ktVer
kotlin("plugin.jpa") version ktVer kotlin("plugin.jpa") version ktVer
kotlin("plugin.serialization") version ktVer kotlin("plugin.serialization") version ktVer
kotlin("plugin.allopen") version ktVer kotlin("plugin.allopen") version ktVer
kotlin("kapt") version ktVer kotlin("kapt") version ktVer
id("io.freefair.lombok") version "8.6"
id("org.springframework.boot") version "3.2.3" id("org.springframework.boot") version "3.2.3"
id("com.github.ben-manes.versions") version "0.51.0" id("com.github.ben-manes.versions") version "0.51.0"
id("org.hibernate.orm") version "6.4.4.Final" id("org.hibernate.orm") version "6.4.4.Final"

View File

@@ -40,6 +40,7 @@ import kotlin.reflect.jvm.jvmErasure
typealias RP = RequestParam typealias RP = RequestParam
typealias RB = RequestBody typealias RB = RequestBody
typealias RT = RequestPart
typealias RH = RequestHeader typealias RH = RequestHeader
typealias PV = PathVariable typealias PV = PathVariable
typealias API = RequestMapping typealias API = RequestMapping
@@ -211,6 +212,8 @@ val <K, V> Map<K, V>.mut get() = toMutableMap()
val <T> Set<T>.mut get() = toMutableSet() val <T> Set<T>.mut get() = toMutableSet()
fun <T> List<T>.unique(fn: (T) -> Any) = distinctBy(fn).ifEmpty { null } fun <T> List<T>.unique(fn: (T) -> Any) = distinctBy(fn).ifEmpty { null }
val <T> Collection<T>.csv get() = joinToString(",")
val IntArray.csv get() = joinToString(",")
// Optionals // Optionals
operator fun <T> Optional<T>.invoke(): T? = orElse(null) operator fun <T> Optional<T>.invoke(): T? = orElse(null)
@@ -227,6 +230,7 @@ fun Str.fromChusanUsername() = String(this.toByteArray(StandardCharsets.ISO_8859
fun Str.truncate(len: Int) = if (this.length > len) this.take(len) + "..." else this fun Str.truncate(len: Int) = if (this.length > len) this.take(len) + "..." else this
val Str.some get() = ifBlank { null } val Str.some get() = ifBlank { null }
val ByteArray.hexStr get() = toHexString() val ByteArray.hexStr get() = toHexString()
operator fun StringBuilder.plusAssign(other: String) { this.append(other) }
// Coroutine // Coroutine
suspend fun <T> async(block: suspend kotlinx.coroutines.CoroutineScope.() -> T): T = withContext(Dispatchers.IO) { block() } suspend fun <T> async(block: suspend kotlinx.coroutines.CoroutineScope.() -> T): T = withContext(Dispatchers.IO) { block() }
@@ -255,6 +259,7 @@ operator fun <E> List<E>.component13(): E = get(12)
inline operator fun <reified E> List<Any?>.invoke(i: Int) = get(i) as E inline operator fun <reified E> List<Any?>.invoke(i: Int) = get(i) as E
val empty = emptyList<Any>() val empty = emptyList<Any>()
val emptyMap = emptyMap<Any, Any>()
val <F> Pair<F, *>.l get() = component1() val <F> Pair<F, *>.l get() = component1()
val <S> Pair<*, S>.r get() = component2() val <S> Pair<*, S>.r get() = component2()

View File

@@ -4,6 +4,7 @@ import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.utils.SUCCESS import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.chusan.model.Chu3Repos import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.model.CardStatus import icu.samnyan.aqua.sega.general.model.CardStatus
import icu.samnyan.aqua.sega.general.model.sensitiveInfo import icu.samnyan.aqua.sega.general.model.sensitiveInfo
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
@@ -52,7 +53,7 @@ class BotController(
secret.checkSecret() secret.checkSecret()
// 1. Find user card // 1. Find user card
val oc = (us.cardRepo.findByLuid(card)() ?: (404 - "Card not found")).maybeGhost() val oc = (us.cardRepo.findByLuid(card) ?: (404 - "Card not found")).maybeGhost()
// 2. Change the status to migrated // 2. Change the status to migrated
us.cardRepo.save(oc.apply { us.cardRepo.save(oc.apply {
@@ -66,7 +67,7 @@ class BotController(
fun clearMigrateFlag(@RP secret: Str, @RP card: Str): Any { fun clearMigrateFlag(@RP secret: Str, @RP card: Str): Any {
secret.checkSecret() secret.checkSecret()
val oc = (us.cardRepo.findByLuid(card)() ?: (404 - "Card not found")).maybeGhost() val oc = (us.cardRepo.findByLuid(card) ?: (404 - "Card not found")).maybeGhost()
us.cardRepo.save(oc.apply { us.cardRepo.save(oc.apply {
status = CardStatus.NORMAL status = CardStatus.NORMAL
@@ -82,14 +83,14 @@ class BotController(
secret.checkSecret() secret.checkSecret()
// 1. Check if the card exist // 1. Check if the card exist
var cards = listOfNotNull( var cards: MutableList<Card> = listOfNotNull(
us.cardRepo.findByLuid(cardId)(), us.cardRepo.findByLuid(cardId),
).mut ).mut
cardId.toLongOrNull()?.let { cardId.toLongOrNull()?.let {
cards += listOfNotNull( cards += listOfNotNull(
us.cardRepo.findById(it)(), us.cardRepo.findById(it)(),
us.cardRepo.findByExtId(it)(), us.cardRepo.findByExtId(it),
) )
cards += listOfNotNull( cards += listOfNotNull(
@@ -110,8 +111,8 @@ class BotController(
return cards.map { card -> return cards.map { card ->
// Find all games played by this card // Find all games played by this card
val chu3 = chu3Db.userData.findByCard_ExtId(card.extId)() val chu3 = chu3Db.userData.findByCard_ExtId(card.extId)
val mai2 = mai2Db.userData.findByCard_ExtId(card.extId)() val mai2 = mai2Db.userData.findByCard_ExtId(card.extId)
val gamesDict = listOfNotNull(chu3, mai2).map { val gamesDict = listOfNotNull(chu3, mai2).map {
// Find the keychip owner // Find the keychip owner
val keychip = it.lastClientId val keychip = it.lastClientId

View File

@@ -8,6 +8,7 @@ import icu.samnyan.aqua.net.games.IUserData
import icu.samnyan.aqua.net.utils.AquaNetProps import icu.samnyan.aqua.net.utils.AquaNetProps
import icu.samnyan.aqua.net.utils.SUCCESS import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo
import icu.samnyan.aqua.sega.diva.PlayerProfileRepository
import icu.samnyan.aqua.sega.general.dao.CardRepository import icu.samnyan.aqua.sega.general.dao.CardRepository
import icu.samnyan.aqua.sega.general.model.Card import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.service.CardService import icu.samnyan.aqua.sega.general.service.CardService
@@ -19,7 +20,6 @@ import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import java.time.LocalDateTime import java.time.LocalDateTime
import kotlin.jvm.optionals.getOrNull
import kotlin.random.Random import kotlin.random.Random
@RestController @RestController
@@ -30,7 +30,8 @@ class CardController(
val cardService: CardService, val cardService: CardService,
val cardGameService: CardGameService, val cardGameService: CardGameService,
val cardRepository: CardRepository, val cardRepository: CardRepository,
val props: AquaNetProps val props: AquaNetProps,
val fedy: Fedy
) { ) {
companion object { companion object {
val log = logger() val log = logger()
@@ -80,10 +81,12 @@ class CardController(
val id = cardService.sanitizeCardId(cardId) val id = cardService.sanitizeCardId(cardId)
// Create a new card // Create a new card
cardService.registerByAccessCode(id, u) val newCard = cardService.registerByAccessCode(id, u)
log.info("Net /card/link : Created new card $id for user ${u.username}") log.info("Net /card/link : Created new card $id for user ${u.username}")
fedy.onCardLinked(newCard.luid, oldExtId = null, ghostExtId = u.ghostCard.extId, emptyList())
return SUCCESS return SUCCESS
} }
@@ -98,6 +101,9 @@ class CardController(
val games = migrate.split(',') val games = migrate.split(',')
cardGameService.migrate(card, games) cardGameService.migrate(card, games)
fedy.onCardLinked(card.luid, oldExtId = card.extId, ghostExtId = u.ghostCard.extId,
games.map { Fedy.getGameName(it) }.filterNotNull())
log.info("Net /card/link : Linked card ${card.id} to user ${u.username} and migrated data to ${games.joinToString()}") log.info("Net /card/link : Linked card ${card.id} to user ${u.username} and migrated data to ${games.joinToString()}")
SUCCESS SUCCESS
@@ -115,10 +121,14 @@ class CardController(
// Ghost cards cannot be unlinked // Ghost cards cannot be unlinked
if (card.isGhost) 400 - "Account virtual cards cannot be unlinked" if (card.isGhost) 400 - "Account virtual cards cannot be unlinked"
val luid = card.luid
// Unbind the card // Unbind the card
card.aquaUser = null card.aquaUser = null
async { cardRepository.save(card) } async { cardRepository.save(card) }
fedy.onCardUnlinked(luid)
log.info("Net /card/unlink : Unlinked card ${card.id} from user ${u.username}") log.info("Net /card/unlink : Unlinked card ${card.id} from user ${u.username}")
SUCCESS SUCCESS
@@ -136,7 +146,7 @@ class CardController(
* *
* Assumption: The card is already linked to the user. * Assumption: The card is already linked to the user.
*/ */
suspend fun <T : IUserData> migrateCard(repo: GenericUserDataRepo<T>, cardRepo: CardRepository, card: Card): Bool { suspend fun <T : IUserData> migrateCard(gameName: Str, repo: GenericUserDataRepo<T>, cardRepo: CardRepository, card: Card): Bool {
val ghost = card.aquaUser!!.ghostCard val ghost = card.aquaUser!!.ghostCard
// Check if data already exists in the user's ghost card // Check if data already exists in the user's ghost card
@@ -144,7 +154,7 @@ suspend fun <T : IUserData> migrateCard(repo: GenericUserDataRepo<T>, cardRepo:
// Create a new dummy card for deleted data // Create a new dummy card for deleted data
it.card = async { it.card = async {
cardRepo.save(Card().apply { cardRepo.save(Card().apply {
luid = "Migrated data of ghost card ${ghost.id} for user ${card.aquaUser!!.auId} on ${utcNow().isoDateTime()}" luid = "Migrated data of ghost card ${ghost.id} for user ${card.aquaUser!!.auId} on ${utcNow().isoDateTime()} (${gameName})"
// Randomize an extId outside the normal range // Randomize an extId outside the normal range
extId = Random.nextLong(0x7FFFFFF7L shl 32, 0x7FFFFFFFL shl 32) extId = Random.nextLong(0x7FFFFFF7L shl 32, 0x7FFFFFFFL shl 32)
registerTime = LocalDateTime.now() registerTime = LocalDateTime.now()
@@ -162,6 +172,23 @@ suspend fun <T : IUserData> migrateCard(repo: GenericUserDataRepo<T>, cardRepo:
return true return true
} }
suspend fun <T : IUserData> orphanData(gameName: Str, repo: GenericUserDataRepo<T>, cardRepo: CardRepository, card: Card) {
// Orphan the data by assigning them to a dummy card
repo.findByCard(card)?.let {
// Create a new dummy card for orphaned data
it.card = async {
cardRepo.save(Card().apply {
luid = "Unmigrated data of card ${card.luid} for user ${card.aquaUser!!.auId} on ${utcNow().isoDateTime()} (${gameName})"
// Randomize an extId outside the normal range
extId = Random.nextLong(0x7FFFFFF7L shl 32, 0x7FFFFFFFL shl 32)
registerTime = LocalDateTime.now()
accessTime = registerTime
})
}
async { repo.save(it) }
}
}
suspend fun getSummaryFor(repo: GenericUserDataRepo<*>, card: Card): Map<Str, Any>? { suspend fun getSummaryFor(repo: GenericUserDataRepo<*>, card: Card): Map<Str, Any>? {
val data = async { repo.findByCard(card) } ?: return null val data = async { repo.findByCard(card) } ?: return null
return mapOf( return mapOf(
@@ -177,7 +204,7 @@ class CardGameService(
val chusan: Chu3UserDataRepo, val chusan: Chu3UserDataRepo,
val wacca: WcUserRepo, val wacca: WcUserRepo,
val ongeki: OgkUserDataRepo, val ongeki: OgkUserDataRepo,
val diva: icu.samnyan.aqua.sega.diva.dao.userdata.PlayerProfileRepository, val diva: PlayerProfileRepository,
val safety: AquaNetSafetyService, val safety: AquaNetSafetyService,
val cardRepo: CardRepository, val cardRepo: CardRepository,
val em: EntityManager val em: EntityManager
@@ -189,18 +216,20 @@ class CardGameService(
suspend fun migrate(crd: Card, games: List<String>) = async { suspend fun migrate(crd: Card, games: List<String>) = async {
// Migrate data from the card to the user's ghost card // Migrate data from the card to the user's ghost card
// An easy migration is to change the UserData card field to the user's ghost card // An easy migration is to change the UserData card field to the user's ghost card
val dataRepos = mapOf(
"mai2" to maimai2,
"chu3" to chusan,
"ongeki" to ongeki,
"wacca" to wacca,
)
val remainingGames = dataRepos.keys.toMutableSet()
games.forEach { game -> games.forEach { game ->
when (game) { val dataRepo = dataRepos[game] ?: return@forEach
"mai2" -> migrateCard(maimai2, cardRepo, crd) migrateCard(game, dataRepo, cardRepo, crd)
"chu3" -> migrateCard(chusan, cardRepo, crd) remainingGames.remove(game)
"ongeki" -> migrateCard(ongeki, cardRepo, crd)
"wacca" -> migrateCard(wacca, cardRepo, crd)
// TODO: diva
// "diva" -> diva.findByPdId(card.extId.toInt()).getOrNull()?.let {
// it.pdId = card.aquaUser!!.ghostCard
// }
}
} }
// For remaining games, orphan the data by assigning them to a dummy card
remainingGames.forEach { game -> orphanData(game, dataRepos[game]!!, cardRepo, crd) }
} }
suspend fun getSummary(card: Card) = async { suspend fun getSummary(card: Card) = async {
@@ -209,7 +238,7 @@ class CardGameService(
"chu3" to getSummaryFor(chusan, card), "chu3" to getSummaryFor(chusan, card),
"ongeki" to getSummaryFor(ongeki, card), "ongeki" to getSummaryFor(ongeki, card),
"wacca" to getSummaryFor(wacca, card), "wacca" to getSummaryFor(wacca, card),
"diva" to diva.findByPdId(card.extId).getOrNull()?.let { "diva" to diva.findByPdId(card.extId)()?.let {
mapOf( mapOf(
"name" to it.playerName, "name" to it.playerName,
"rating" to it.level, "rating" to it.level,

View File

@@ -1,32 +1,37 @@
package icu.samnyan.aqua.net package icu.samnyan.aqua.net
import ext.* import ext.*
import icu.samnyan.aqua.sega.general.service.CardService import icu.samnyan.aqua.net.components.EmailProperties
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
import org.springframework.web.bind.annotation.RestController
import java.security.MessageDigest
import icu.samnyan.aqua.net.db.AquaNetUserRepo
import icu.samnyan.aqua.net.db.AquaNetUserFedyRepo
import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.net.components.JWT import icu.samnyan.aqua.net.components.JWT
import icu.samnyan.aqua.net.db.AquaNetUserFedy
import icu.samnyan.aqua.net.db.AquaNetUser import icu.samnyan.aqua.net.db.AquaNetUser
import icu.samnyan.aqua.net.games.ImportController import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.mai2.Mai2Import
import icu.samnyan.aqua.net.games.ExportOptions import icu.samnyan.aqua.net.games.ExportOptions
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPlaylogHandler as Mai2UploadUserPlaylogHandler
import icu.samnyan.aqua.sega.maimai2.handler.UpsertUserAllHandler as Mai2UpsertUserAllHandler
import icu.samnyan.aqua.net.utils.ApiException
import java.util.Arrays
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.support.TransactionTemplate
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
import icu.samnyan.aqua.net.games.GenericUserDataRepo import icu.samnyan.aqua.net.games.GenericUserDataRepo
import icu.samnyan.aqua.net.games.IUserData import icu.samnyan.aqua.net.games.IUserData
import icu.samnyan.aqua.net.games.mai2.Mai2Import
import icu.samnyan.aqua.net.utils.ApiException
import icu.samnyan.aqua.net.utils.PathProps
import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo
import icu.samnyan.aqua.sega.general.dao.CardRepository
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.service.CardService
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo
import icu.samnyan.aqua.sega.wacca.model.db.WcUserRepo
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.support.TransactionTemplate
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
import java.security.MessageDigest
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import kotlin.io.path.getLastModifiedTime
import kotlin.io.path.isRegularFile
import kotlin.io.path.writeBytes
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPlaylogHandler as Mai2UploadUserPlaylogHandler
import icu.samnyan.aqua.sega.maimai2.handler.UpsertUserAllHandler as Mai2UpsertUserAllHandler
@Configuration @Configuration
@ConfigurationProperties(prefix = "aqua-net.fedy") @ConfigurationProperties(prefix = "aqua-net.fedy")
@@ -36,24 +41,44 @@ class FedyProps {
var remote: String = "" var remote: String = ""
} }
enum class FedyEvent { data class UserProfilePicture(val url: Str, val lastUpdatedMs: Long)
Linked, data class UserBasicInfo(
Unlinked, val auId: Long, val ghostExtId: Long, val registrationTimeMs: Long,
Upserted, val username: Str, val displayName: Str, val email: Str, val passwordHash: Str, val profileBio: Str,
Imported, val profilePicture: UserProfilePicture?,
} )
private data class UserUpdatedEvent(val user: UserBasicInfo, val isNewlyCreated: Bool)
private data class CardCreatedEvent(val luid: Str, val extId: Long)
private data class CardLinkedEvent(val luid: Str, val oldExtId: Long?, val ghostExtId: Long, val migratedGames: List<Str>)
private data class CardUnlinkedEvent(val luid: Str)
private data class DataUpdatedEvent(val extId: Long, val isGhostCard: Bool, val game: Str, val removeOldData: Bool)
private data class FedyEvent(
var userUpdated: UserUpdatedEvent? = null,
var cardCreated: CardCreatedEvent? = null,
var cardLinked: CardLinkedEvent? = null,
var cardUnlinked: CardUnlinkedEvent? = null,
var dataUpdated: DataUpdatedEvent? = null,
)
@RestController @RestController
@API("/api/v2/fedy") @API("/api/v2/fedy", consumes = ["multipart/form-data"])
class Fedy( class Fedy(
val jwt: JWT, val jwt: JWT,
val userRepo: AquaNetUserRepo, val us: AquaUserServices,
val userFedyRepo: AquaNetUserFedyRepo, val emailProps: EmailProperties,
val cardRepo: CardRepository,
val cardService: CardService,
val mai2Import: Mai2Import, val mai2Import: Mai2Import,
val mai2UserDataRepo: Mai2UserDataRepo, val mai2UserDataRepo: Mai2UserDataRepo,
val mai2UploadUserPlaylog: Mai2UploadUserPlaylogHandler, val mai2UploadUserPlaylog: Mai2UploadUserPlaylogHandler,
val mai2UpsertUserAll: Mai2UpsertUserAllHandler, val mai2UpsertUserAll: Mai2UpsertUserAllHandler,
val chu3UserDataRepo: Chu3UserDataRepo,
val ongekiUserDataRepo: OgkUserDataRepo,
val waccaUserDataRepo: WcUserRepo,
val props: FedyProps, val props: FedyProps,
val paths: PathProps,
val transactionManager: PlatformTransactionManager val transactionManager: PlatformTransactionManager
) { ) {
val transaction by lazy { TransactionTemplate(transactionManager) } val transaction by lazy { TransactionTemplate(transactionManager) }
@@ -63,78 +88,102 @@ class Fedy(
if (!MessageDigest.isEqual(this.toByteArray(), props.key.toByteArray())) 403 - "Invalid Key" if (!MessageDigest.isEqual(this.toByteArray(), props.key.toByteArray())) 403 - "Invalid Key"
} }
@API("/status") val suppressEvents = ThreadLocal.withInitial { false }
fun handleStatus(@RP token: Str): Any { private fun <T> handleFedy(key: Str, block: () -> T): T {
val user = jwt.auth(token) val old = suppressEvents.get()
val userFedy = userFedyRepo.findByAquaNetUserAuId(user.auId) suppressEvents.set(true)
return mapOf("linkedAt" to (userFedy?.createdAt?.toEpochMilli() ?: 0)) try {
}
@API("/link")
fun handleLink(@RP token: Str, @RP nonce: Str): Any {
val user = jwt.auth(token)
if (userFedyRepo.findByAquaNetUserAuId(user.auId) != null) 412 - "User already linked"
val userFedy = AquaNetUserFedy(aquaNetUser = user)
userFedyRepo.save(userFedy)
notify(FedyEvent.Linked, mapOf("auId" to user.auId, "nonce" to nonce))
return mapOf("linkedAt" to userFedy.createdAt.toEpochMilli())
}
@API("/unlink")
fun handleUnlink(@RP token: Str): Any {
val user = jwt.auth(token)
val userFedy = userFedyRepo.findByAquaNetUserAuId(user.auId) ?: 412 - "User not linked"
userFedyRepo.delete(userFedy)
notify(FedyEvent.Unlinked, mapOf("auId" to user.auId))
return SUCCESS
}
private fun ensureUser(auId: Long): AquaNetUser {
val userFedy = userFedyRepo.findByAquaNetUserAuId(auId) ?: 404 - "User not linked"
val user = userRepo.findByAuId(auId) ?: 404 - "User not found"
return user
}
data class UnlinkByRemoteReq(val auId: Long)
@API("/unlink-by-remote")
fun handleUnlinkByRemote(@RH(KEY_HEADER) key: Str, @RB req: UnlinkByRemoteReq): Any {
key.checkKey() key.checkKey()
val user = ensureUser(req.auId) return block()
userFedyRepo.deleteByAquaNetUserAuId(user.auId) } finally { suppressEvents.set(old) }
// No need to notify remote, because initiated by remote
return SUCCESS
} }
data class PullReq(val auId: Long, val game: Str, val exportOptions: ExportOptions) data class FedyErr(val code: Int, val message: Str)
@API("/pull")
fun handlePull(@RH(KEY_HEADER) key: Str, @RB req: PullReq): Any { data class UserPullReq(val auId: Long)
key.checkKey() data class UserPullRes(val user: UserBasicInfo?)
val user = ensureUser(req.auId) @API("/user/pull")
fun catched(block: () -> Any) = fun handleUserPull(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: UserPullReq): UserPullRes = handleFedy(key) {
try { mapOf("result" to block()) } UserPullRes(us.userRepo.findByAuId(req.auId)?.fedyBasicInfo())
catch (e: ApiException) { mapOf("error" to mapOf("code" to e.code, "message" to e.message.toString())) } }
return when (req.game) {
"mai2" -> catched { mai2Import.export(user, req.exportOptions) } data class UserLookupReq(val username: Str?, val email: Str?)
data class UserLookupRes(val user: UserBasicInfo?)
@API("/user/lookup")
fun handleUserLogin(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: UserLookupReq): UserLookupRes = handleFedy(key) {
UserLookupRes(user =
(req.username?.let { us.userRepo.findByUsernameIgnoreCase(it) } ?: req.email?.let {us.userRepo.findByEmailIgnoreCase(it) })
?.takeIf { it.emailConfirmed || !emailProps.enable }
?.fedyBasicInfo()
)
}
data class UserRegisterReq(val username: Str, val email: Str, val password: Str)
data class UserRegisterRes(val error: FedyErr? = null, val user: UserBasicInfo? = null)
@API("/user/register")
fun handleUserRegister(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: UserRegisterReq): UserRegisterRes = handleFedy(key) {
{
UserRegisterRes(user = us.create(req.username, req.email, req.password, "", emailConfirmed = true).fedyBasicInfo())
} caught { UserRegisterRes(error = it) }
}
data class UserUpdateReq(val auId: Long, val fields: Map<Str, Str?>?)
data class UserUpdateRes(val error: FedyErr? = null, val user: UserBasicInfo? = null)
@API("/user/update")
fun handleUserUpdate(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: UserUpdateReq, @RT(PFP_PART) pfpFile: MultipartFile?): UserUpdateRes = handleFedy(key) {
{
val ru = us.userRepo.findByAuId(req.auId) ?: (404 - "User not found")
val fields = req.fields?.filterValues { it != null }?.mapValues { it.value as Str } ?: emptyMap()
fields.forEach { (k, v) ->
if (k == "email") { ru.email = us.validateEmail(v) }
else us.update(ru, k, v)
}
pfpFile?.run {
val mime = TIKA.detect(pfpFile.bytes).takeIf { it.startsWith("image/") } ?: (400 - "Invalid file type")
val name = "${ru.auId}${MIMES.forName(mime)?.extension ?: ".jpg"}"
(paths.aquaNetPortrait.path() / name).writeBytes(bytes)
ru.profilePicture = name
}
us.userRepo.save(ru)
if (fields.containsKey("pwHash") ?: false) { us.clearAllSessions(ru) }
UserUpdateRes(user = ru.fedyBasicInfo())
} caught { UserUpdateRes(error = it) }
}
private fun AquaNetUser.fedyBasicInfo() = UserBasicInfo(
auId, ghostCard.extId, regTime,
username, displayName, email, pwHash, profileBio ?: "",
profilePicture
?.let { paths.aquaNetPortrait.path() / it }?.takeIf { it.isRegularFile() }
?.let { UserProfilePicture(
url = "/uploads/net/portrait/${profilePicture}",
lastUpdatedMs = it.getLastModifiedTime().toMillis()
) }
)
data class DataPullReq(val extId: Long, val game: Str, val exportOptions: ExportOptions)
data class DataPullRes(val error: FedyErr? = null, val result: Any? = null)
@API("/data/pull")
fun handleDataPull(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: DataPullReq): DataPullRes = handleFedy(key) {
val card = cardRepo.findByExtId(req.extId)
?: (404 - "Card with extId ${req.extId} not found")
{
DataPullRes(result = when (req.game) {
"mai2" -> mai2Import.export(card, req.exportOptions)
else -> 406 - "Unsupported game" else -> 406 - "Unsupported game"
} })
} caught { DataPullRes(error = it) }
} }
data class PushReq(val auId: Long, val game: Str, val data: JDict, val removeOldData: Bool) data class DataPushReq(val extId: Long, val game: Str, val data: JDict, val removeOldData: Bool)
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@API("/push") @API("/data/push")
fun handlePush(@RH(KEY_HEADER) key: Str, @RB req: PushReq): Any { fun handleDataPush(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: DataPushReq): Any = handleFedy(key) {
key.checkKey() val extId = req.extId
val user = ensureUser(req.auId)
val extId = user.ghostCard.extId
fun<UserData : IUserData, UserRepo : GenericUserDataRepo<UserData>> removeOldData(repo: UserRepo) { fun<UserData : IUserData, UserRepo : GenericUserDataRepo<UserData>> removeOldData(repo: UserRepo) {
val oldData = repo.findByCard_ExtId(extId) repo.findByCard_ExtId(extId)?.let { oldData ->
if (oldData.isPresent) {
log.info("Fedy: Deleting old data for $extId (${req.game})") log.info("Fedy: Deleting old data for $extId (${req.game})")
repo.delete(oldData.get()); repo.delete(oldData);
repo.flush() repo.flush()
} }
} }
@@ -149,29 +198,112 @@ class Fedy(
else -> 406 - "Unsupported game" else -> 406 - "Unsupported game"
} } } }
return SUCCESS SUCCESS
} }
fun onUpserted(game: Str, maybeExtId: Any?) = maybeNotifyAsync(FedyEvent.Upserted, game, maybeExtId) data class CardResolveReq(val luid: Str, val pairedLuid: Str?, val createIfNotFound: Bool)
fun onImported(game: Str, maybeExtId: Any?) = maybeNotifyAsync(FedyEvent.Imported, game, maybeExtId) data class CardResolveRes(val extId: Long, val isGhostCard: Bool, val isNewlyCreated: Bool, val isPairedLuidDiverged: Bool)
@API("/card/resolve")
fun handleCardResolve(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: CardResolveReq): CardResolveRes = handleFedy(key) {
var card = cardService.tryLookup(req.luid)
var isNewlyCreated = false
if (card != null) {
card = card.maybeGhost()
if (!card.isGhost) isNewlyCreated = isCardFresh(card)
} else if (req.createIfNotFound) {
card = cardService.registerByAccessCode(req.luid, null)
isNewlyCreated = true
log.info("Fedy /card/resolve : Created new card ${card.id} (${card.luid})")
}
var isPairedLuidDiverged = false
if (req.pairedLuid != null) {
var pairedCard = cardService.tryLookup(req.pairedLuid)?.maybeGhost()
if (pairedCard?.extId != card?.extId) {
var isGhost = pairedCard?.isGhost == true
var isFresh = pairedCard != null && isCardFresh(pairedCard)
if (isGhost && isFresh) isPairedLuidDiverged = true
else if (!isGhost && card?.isGhost == true) {
// Ensure paired card is linked, if the main card is linked
// If the main card is not linked, there's nothing Fedy can do. It's Fedy's best effort.
if (pairedCard == null) { pairedCard = cardService.registerByAccessCode(req.pairedLuid, card.aquaUser) }
else { pairedCard.aquaUser = card.aquaUser; cardRepo.save(pairedCard) }
log.info("Fedy /card/resolve : Created paired card ${pairedCard.id} (${pairedCard.luid}) for user ${card.aquaUser?.auId} (${card.aquaUser?.username})")
}
}
}
private fun maybeNotifyAsync(event: FedyEvent, game: Str, maybeExtId: Any?) = if (!props.enabled) {} else CompletableFuture.runAsync { try { CardResolveRes(
val extId = maybeExtId?.long ?: return@runAsync card?.extId ?: 0,
val user = userRepo.findByGhostCardExtId(extId) ?: return@runAsync card?.isGhost ?: false,
val userFedy = userFedyRepo.findByAquaNetUserAuId(user.auId) ?: return@runAsync isNewlyCreated,
notify(event, mapOf("auId" to user.auId, "game" to game)) isPairedLuidDiverged)
}
data class CardLinkReq(val auId: Long, val luid: Str)
@API("/card/link")
fun handleCardLink(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: CardLinkReq): Any = handleFedy(key) {
val ru = us.userRepo.findByAuId(req.auId) ?: (404 - "User not found")
var card = cardService.tryLookup(req.luid)
if (card == null) {
card = cardService.registerByAccessCode(req.luid, ru)
log.info("Fedy /card/link : Linked new card ${card.id} (${card.luid}) to user ${ru.auId} (${ru.username})")
} else {
if (card.isGhost) 400 - "Account virtual cards cannot be unlinked"
val cu = card.aquaUser
if (cu != null) {
if (cu.auId == req.auId) log.info("Fedy /card/link : Existing card ${card.id} (${card.luid}) already linked to user ${ru.auId} (${ru.username})")
else 400 - "Card linked to another user"
} else {
card.aquaUser = ru
cardRepo.save(card)
log.info("Fedy /card/link : Linked existing card ${card.id} (${card.luid}) to user ${ru.auId} (${ru.username})")
}
}
}
data class CardUnlinkReq(val auId: Long, val luid: Str)
@API("/card/unlink")
fun handleCardUnlink(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: CardUnlinkReq): Any = handleFedy(key) {
val card = cardService.tryLookup(req.luid)
val cu = card?.aquaUser ?: return@handleFedy SUCCESS // Nothing to do
if (cu.auId != req.auId) 400 - "Card linked to another user"
if (card.isGhost) 400 - "Account virtual cards cannot be unlinked"
card.aquaUser = null
cardRepo.save(card)
log.info("Fedy /card/unlink : Unlinked card ${card.id} (${card.luid}) from user ${cu.auId} (${cu.username})")
}
fun onUserUpdated(u: AquaNetUser, isNew: Bool = false) = maybeNotifyAsync(FedyEvent(userUpdated = UserUpdatedEvent(u.fedyBasicInfo(), isNew)))
fun onCardCreated(luid: Str, extId: Long) = maybeNotifyAsync(FedyEvent(cardCreated = CardCreatedEvent(luid, extId)))
fun onCardLinked(luid: Str, oldExtId: Long?, ghostExtId: Long, migratedGames: List<Str>) = maybeNotifyAsync(FedyEvent(cardLinked = CardLinkedEvent(luid, oldExtId, ghostExtId, migratedGames)))
fun onCardUnlinked(luid: Str) = maybeNotifyAsync(FedyEvent(cardUnlinked = CardUnlinkedEvent(luid)))
fun onDataUpdated(extId: Long, game: Str, removeOldData: Bool) = maybeNotifyAsync({
val card = cardRepo.findByExtId(extId) ?: return@maybeNotifyAsync null // Card not found, nothing to do
FedyEvent(dataUpdated = DataUpdatedEvent(extId, card.isGhost, game, removeOldData))
})
private fun maybeNotifyAsync(event: FedyEvent) = maybeNotifyAsync({ event })
private fun maybeNotifyAsync(getEvent: () -> FedyEvent?) = if (!props.enabled && !suppressEvents.get()) {} else CompletableFuture.runAsync {
var event: FedyEvent? = null
try {
event = getEvent()
if (event == null) return@runAsync // Nothing to do
notify(event)
} catch (e: Exception) { } catch (e: Exception) {
log.error("Error handling Fedy on maybeNotifyAsync($event, $game, $maybeExtId)", e) log.error("Error handling Fedy on maybeNotifyAsync($event)", e)
} } }
}.let {}
private fun notify(event: FedyEvent, body: Any?) { private fun notify(event: FedyEvent) {
val MAX_RETRY = 3 val MAX_RETRY = 3
val body = body?.toJson() ?: "{}" val body = event.toJson() ?: "{}"
var retry = 0 var retry = 0
var shouldRetry = true var shouldRetry = true
while (retry < MAX_RETRY) { while (true) {
try { try {
val response = "${props.remote.trimEnd('/')}/notify/${event.name}".request() val response = "${props.remote.trimEnd('/')}/notify".request()
.header("Content-Type" to "application/json") .header("Content-Type" to "application/json")
.header(KEY_HEADER to props.key) .header(KEY_HEADER to props.key)
.post(body) .post(body)
@@ -191,13 +323,38 @@ class Fedy(
} }
} }
// Apparently existing cards could possibly be fresh and never used in any game. Treat them as new cards.
private fun isCardFresh(c: Card): Bool {
fun <T : IUserData> checkForGame(repo: GenericUserDataRepo<T>, card: Card): Bool = repo.findByCard(card) == null
return when {
checkForGame(mai2UserDataRepo, c) -> false
checkForGame(chu3UserDataRepo, c) -> false
checkForGame(ongekiUserDataRepo, c) -> false
checkForGame(waccaUserDataRepo, c) -> false
else -> true
}
}
private infix fun <T> (() -> T).caught(onError: (FedyErr) -> T) =
try { this() }
catch (e: ApiException) { onError(FedyErr(code = e.code, message = e.message.toString())) }
companion object companion object
{ {
const val KEY_HEADER = "X-Fedy-Key" const val KEY_HEADER = "X-Fedy-Key"
const val REQ_PART = "request"
const val PFP_PART = "profilePicture"
val log = logger() val log = logger()
fun getGameName(gameId: Str) = when (gameId) { fun getGameName(gameId: Str) = when (gameId) {
"mai2" -> "mai2"
"SDEZ" -> "mai2" "SDEZ" -> "mai2"
"chu3" -> "chu3"
"SDHD" -> "chu3"
"ongeki" -> "mu3"
"SDDT" -> "mu3"
"wacca" -> "wacca"
"SDFE" -> "wacca"
else -> null // Not supported else -> null // Not supported
} }
} }

View File

@@ -19,7 +19,8 @@ class FrontierProps {
@API("/api/v2/frontier") @API("/api/v2/frontier")
class Frontier( class Frontier(
val cardService: CardService, val cardService: CardService,
val props: FrontierProps val props: FrontierProps,
val fedy: Fedy
) { ) {
fun Str.checkFtk() { fun Str.checkFtk() {
if (this != props.ftk) 403 - "Invalid FTK" if (this != props.ftk) 403 - "Invalid FTK"
@@ -32,9 +33,12 @@ class Frontier(
if (accessCode.length != 20) 400 - "Invalid access code" if (accessCode.length != 20) 400 - "Invalid access code"
// if (!accessCode.startsWith("9900")) 400 - "Frontier access code must start with 9900" // if (!accessCode.startsWith("9900")) 400 - "Frontier access code must start with 9900"
if (async { cardService.cardRepo.findByLuid(accessCode) }.isPresent) 400 - "Card already registered" if (async { cardService.cardRepo.findByLuid(accessCode) } != null) 400 - "Card already registered"
val card = async { cardService.registerByAccessCode(accessCode) } val card = async { cardService.registerByAccessCode(accessCode) }
fedy.onCardCreated(accessCode, card.extId)
return mapOf( return mapOf(
"card" to card, "card" to card,
"id" to card.extId // Expose hidden ID "id" to card.extId // Expose hidden ID

View File

@@ -1,7 +1,6 @@
package icu.samnyan.aqua.net package icu.samnyan.aqua.net
import ext.JACKSON import ext.JACKSON
import ext.invoke
import ext.logger import ext.logger
import ext.parse import ext.parse
import icu.samnyan.aqua.net.db.AquaNetUserRepo import icu.samnyan.aqua.net.db.AquaNetUserRepo
@@ -24,7 +23,7 @@ class Migrations(
@PostConstruct @PostConstruct
fun migrate() { fun migrate() {
val db = props.findByPropertyKey("migrations")() ?: PropertyEntry("migrations", "[]") val db = props.findByPropertyKey("migrations") ?: PropertyEntry("migrations", "[]")
val p = JACKSON.parse<ArrayList<String>>(db.propertyValue) val p = JACKSON.parse<ArrayList<String>>(db.propertyValue)
val old = p.size val old = p.size
@@ -47,7 +46,7 @@ class Migrations(
if (c.extId > max) { if (c.extId > max) {
var new = c.extId and max var new = c.extId and max
log.info("Removing signed bit: {${c.extId} -> $new} for ${c.luid}") log.info("Removing signed bit: {${c.extId} -> $new} for ${c.luid}")
while (cardRepo.findByExtId(new).isPresent) { while (cardRepo.findByExtId(new) != null) {
log.error("> Conflicting card found for ${c.luid}: $new") log.error("> Conflicting card found for ${c.luid}: $new")
new++ new++
} }

View File

@@ -2,21 +2,22 @@ package icu.samnyan.aqua.net
import ext.* import ext.*
import icu.samnyan.aqua.net.components.* import icu.samnyan.aqua.net.components.*
import icu.samnyan.aqua.net.db.* import icu.samnyan.aqua.net.db.AquaNetUserRepo
import icu.samnyan.aqua.net.db.AquaUserServices.Companion.SETTING_FIELDS import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.db.EmailConfirmationRepo
import icu.samnyan.aqua.net.db.ResetPasswordRepo
import icu.samnyan.aqua.net.utils.PathProps import icu.samnyan.aqua.net.utils.PathProps
import icu.samnyan.aqua.net.utils.SUCCESS import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.general.dao.CardRepository import icu.samnyan.aqua.sega.general.dao.CardRepository
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.model.CardStatus import icu.samnyan.aqua.sega.general.model.CardStatus
import icu.samnyan.aqua.sega.general.service.CardService
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Lazy
import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile import org.springframework.web.multipart.MultipartFile
import java.time.Instant import java.time.Instant
import java.time.LocalDateTime
import kotlin.io.path.writeBytes import kotlin.io.path.writeBytes
@RestController @RestController
@@ -31,12 +32,11 @@ class UserRegistrar(
val confirmationRepo: EmailConfirmationRepo, val confirmationRepo: EmailConfirmationRepo,
val resetPasswordRepo: ResetPasswordRepo, val resetPasswordRepo: ResetPasswordRepo,
val cardRepo: CardRepository, val cardRepo: CardRepository,
val cardService: CardService,
val validator: AquaUserServices, val validator: AquaUserServices,
val emailProps: EmailProperties, val emailProps: EmailProperties,
val sessionRepo: SessionTokenRepo,
final val paths: PathProps final val paths: PathProps
) { ) {
@Autowired @Lazy lateinit var fedy: Fedy
val portraitPath = paths.aquaNetPortrait.path() val portraitPath = paths.aquaNetPortrait.path()
companion object { companion object {
@@ -68,29 +68,7 @@ class UserRegistrar(
val country = geoIP.getCountry(ip) val country = geoIP.getCountry(ip)
// Create user // Create user
val u = async { AquaNetUser( val u = async { validator.create(username, email, password, country) }
username = validator.checkUsername(username),
email = validator.checkEmail(email),
pwHash = validator.checkPwHash(password),
regTime = millis(), lastLogin = millis(), country = country,
) }
// Create a ghost card
val card = Card().apply {
extId = cardService.randExtID(cardExtIdStart, cardExtIdEnd)
luid = extId.toString()
registerTime = LocalDateTime.now()
accessTime = registerTime
aquaUser = u
isGhost = true
}
u.ghostCard = card
// Save the user
async {
userRepo.save(u)
cardRepo.save(card)
}
// Send confirmation email // Send confirmation email
emailService.sendConfirmation(u) emailService.sendConfirmation(u)
@@ -200,16 +178,13 @@ class UserRegistrar(
if (reset.createdAt.plusSeconds(60 * 60 * 24).isBefore(Instant.now())) 400 - "Token expired" if (reset.createdAt.plusSeconds(60 * 60 * 24).isBefore(Instant.now())) 400 - "Token expired"
// Change the password // Change the password
async { userRepo.save(reset.aquaNetUser.apply { pwHash = validator.checkPwHash(password) }) } val u = reset.aquaNetUser
async { userRepo.save(u.apply { pwHash = validator.checkPwHash(password) }) }
fedy.onUserUpdated(u)
// Remove the token from the list // Remove the token from the list
resetPasswordRepo.delete(reset) resetPasswordRepo.delete(reset)
// Clear all sessions
sessionRepo.deleteAll(
sessionRepo.findByAquaNetUserAuId(reset.aquaNetUser.auId)
)
return SUCCESS return SUCCESS
} }
@@ -227,8 +202,13 @@ class UserRegistrar(
// Check if the token is expired // Check if the token is expired
if (confirmation.createdAt.plusSeconds(60 * 60 * 24).isBefore(Instant.now())) 400 - "Token expired" if (confirmation.createdAt.plusSeconds(60 * 60 * 24).isBefore(Instant.now())) 400 - "Token expired"
// Check if the email is already confirmed
val u = confirmation.aquaNetUser
if (u.emailConfirmed) 400 - "Email already confirmed"
// Confirm the email // Confirm the email
async { userRepo.save(confirmation.aquaNetUser.apply { emailConfirmed = true }) } async { userRepo.save(u.apply { emailConfirmed = true }) }
fedy.onUserUpdated(u, isNew = true)
return SUCCESS return SUCCESS
} }
@@ -245,22 +225,16 @@ class UserRegistrar(
@API("/setting") @API("/setting")
@Doc("Validate and set a user setting field.", "Success message") @Doc("Validate and set a user setting field.", "Success message")
suspend fun setting(@RP token: Str, @RP key: Str, @RP value: Str) = jwt.auth(token) { u -> suspend fun setting(@RP token: Str, @RP key: Str, @RP value: Str) = jwt.auth(token) { u ->
// Check if the key is a settable field
val field = SETTING_FIELDS.find { it.name == key } ?: (400 - "Invalid setting")
async { async {
// Set the validated field validator.update(u, key, value)
field.setter.call(u, field.checker.call(validator, value))
// Save the user // Save the user
userRepo.save(u) userRepo.save(u)
// Clear all tokens if changing password // Clear all tokens if changing password
if (key == "pwHash") if (key == "pwHash") validator.clearAllSessions(u)
sessionRepo.deleteAll(
sessionRepo.findByAquaNetUserAuId(u.auId)
)
} }
fedy.onUserUpdated(u)
SUCCESS SUCCESS
} }
@@ -299,6 +273,7 @@ class UserRegistrar(
(portraitPath / name).writeBytes(bytes) (portraitPath / name).writeBytes(bytes)
userRepo.save(u.apply { profilePicture = name }) userRepo.save(u.apply { profilePicture = name })
} }
fedy.onUserUpdated(u)
SUCCESS SUCCESS
} }

View File

@@ -3,11 +3,7 @@ package icu.samnyan.aqua.net.components
import ext.Bool import ext.Bool
import ext.Str import ext.Str
import ext.logger import ext.logger
import icu.samnyan.aqua.net.db.AquaNetUser import icu.samnyan.aqua.net.db.*
import icu.samnyan.aqua.net.db.EmailConfirmation
import icu.samnyan.aqua.net.db.EmailConfirmationRepo
import icu.samnyan.aqua.net.db.ResetPassword
import icu.samnyan.aqua.net.db.ResetPasswordRepo
import org.simplejavamail.api.mailer.Mailer import org.simplejavamail.api.mailer.Mailer
import org.simplejavamail.email.EmailBuilder import org.simplejavamail.email.EmailBuilder
import org.simplejavamail.springsupport.SimpleJavaMailSpringSupport import org.simplejavamail.springsupport.SimpleJavaMailSpringSupport

View File

@@ -2,11 +2,7 @@ package icu.samnyan.aqua.net.components
import ext.Str import ext.Str
import ext.minus import ext.minus
import icu.samnyan.aqua.net.db.AquaNetUser import icu.samnyan.aqua.net.db.*
import icu.samnyan.aqua.net.db.AquaNetUserRepo
import icu.samnyan.aqua.net.db.SessionToken
import icu.samnyan.aqua.net.db.SessionTokenRepo
import icu.samnyan.aqua.net.db.getTokenExpiry
import io.jsonwebtoken.JwtParser import io.jsonwebtoken.JwtParser
import io.jsonwebtoken.Jwts import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys import io.jsonwebtoken.security.Keys

View File

@@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository import org.springframework.stereotype.Repository
import java.io.Serializable import java.io.Serializable
import java.time.Instant import java.time.Instant
import java.util.UUID import java.util.*
fun getTokenExpiry() = Instant.now().plusSeconds(7 * 86400) fun getTokenExpiry() = Instant.now().plusSeconds(7 * 86400)

View File

@@ -2,6 +2,8 @@ package icu.samnyan.aqua.net.db
import com.fasterxml.jackson.annotation.JsonIgnore import com.fasterxml.jackson.annotation.JsonIgnore
import ext.* import ext.*
import icu.samnyan.aqua.net.UserRegistrar.Companion.cardExtIdEnd
import icu.samnyan.aqua.net.UserRegistrar.Companion.cardExtIdStart
import icu.samnyan.aqua.net.components.JWT import icu.samnyan.aqua.net.components.JWT
import icu.samnyan.aqua.sega.allnet.AllNetProps import icu.samnyan.aqua.sega.allnet.AllNetProps
import icu.samnyan.aqua.sega.allnet.KeyChipRepo import icu.samnyan.aqua.sega.allnet.KeyChipRepo
@@ -9,11 +11,13 @@ import icu.samnyan.aqua.sega.allnet.KeychipSession
import icu.samnyan.aqua.sega.general.GameMusicPopularity import icu.samnyan.aqua.sega.general.GameMusicPopularity
import icu.samnyan.aqua.sega.general.dao.CardRepository import icu.samnyan.aqua.sega.general.dao.CardRepository
import icu.samnyan.aqua.sega.general.model.Card import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.service.CardService
import jakarta.persistence.* import jakarta.persistence.*
import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import java.io.Serializable import java.io.Serializable
import java.time.LocalDateTime
import kotlin.jvm.optionals.getOrNull import kotlin.jvm.optionals.getOrNull
import kotlin.reflect.KFunction import kotlin.reflect.KFunction
import kotlin.reflect.KMutableProperty import kotlin.reflect.KMutableProperty
@@ -124,7 +128,9 @@ class AquaUserServices(
val allNetProps: AllNetProps, val allNetProps: AllNetProps,
val jwt: JWT, val jwt: JWT,
val em: EntityManager, val em: EntityManager,
val pop: GameMusicPopularity val pop: GameMusicPopularity,
val cardService: CardService,
val sessionRepo: SessionTokenRepo,
) { ) {
companion object { companion object {
val SETTING_FIELDS = AquaUserServices::class.functions val SETTING_FIELDS = AquaUserServices::class.functions
@@ -136,12 +142,49 @@ class AquaUserServices(
} }
} }
fun create(username: Str, email: Str, password: Str, country: Str, emailConfirmed: Boolean = false): AquaNetUser {
// Create user
val u = AquaNetUser(
username = checkUsername(username),
email = validateEmail(email),
pwHash = checkPwHash(password),
regTime = millis(), lastLogin = millis(), country = country,
emailConfirmed = emailConfirmed
)
// Create a ghost card
val card = Card().apply {
extId = cardService.randExtID(cardExtIdStart, cardExtIdEnd)
luid = extId.toString()
registerTime = LocalDateTime.now()
accessTime = registerTime
aquaUser = u
isGhost = true
}
u.ghostCard = card
// Save the user
userRepo.save(u)
cardRepo.save(card)
return u
}
fun update(user: AquaNetUser, key: Str, value: Str) {
// Check if the key is a settable field
val field = SETTING_FIELDS.find { it.name == key } ?: (400 - "Invalid setting")
// Set the validated field
field.setter.call(user, field.checker.call(this, value))
}
fun clearAllSessions(user: AquaNetUser) = sessionRepo.deleteAll(sessionRepo.findByAquaNetUserAuId(user.auId))
suspend fun <T> byName(username: Str, callback: suspend (AquaNetUser) -> T) = suspend fun <T> byName(username: Str, callback: suspend (AquaNetUser) -> T) =
async { userRepo.findByUsernameIgnoreCase(username) }?.let { callback(it) } ?: (404 - "User not found") async { userRepo.findByUsernameIgnoreCase(username) }?.let { callback(it) } ?: (404 - "User not found")
suspend fun cardByName(username: Str) = suspend fun cardByName(username: Str) =
if (username.startsWith("user")) username.substring(4).toLongOrNull() if (username.startsWith("user")) username.substring(4).toLongOrNull()
?.let { cardRepo.findById(it).getOrNull() } ?: (404 - "Card not found") ?.let { cardRepo.findById(it)() } ?: (404 - "Card not found")
else byName(username) { it.ghostCard } else byName(username) { it.ghostCard }
suspend fun <T> cardByName(username: Str, callback: suspend (Card) -> T) = callback(cardByName(username)) suspend fun <T> cardByName(username: Str, callback: suspend (Card) -> T) = callback(cardByName(username))
@@ -173,7 +216,7 @@ class AquaUserServices(
400 - "User with username `$this` already exists" 400 - "User with username `$this` already exists"
} }
fun checkEmail(email: Str) = email.apply { fun validateEmail(email: Str) = email.apply {
// Check if email is valid // Check if email is valid
if (!isValidEmail()) 400 - "Invalid email" if (!isValidEmail()) 400 - "Invalid email"

View File

@@ -1,29 +0,0 @@
package icu.samnyan.aqua.net.db
import jakarta.persistence.*
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.io.Serializable
import java.time.Instant
@Entity
@Table(name = "aqua_net_user_fedy")
class AquaNetUserFedy(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
@Column(nullable = false)
var createdAt: Instant = Instant.now(),
// Linking to the AquaNetUser
@OneToOne
@JoinColumn(name = "auId", referencedColumnName = "auId")
var aquaNetUser: AquaNetUser,
) : Serializable
@Repository
interface AquaNetUserFedyRepo : JpaRepository<AquaNetUserFedy, Long> {
fun findByAquaNetUserAuId(auId: Long): AquaNetUserFedy?
fun deleteByAquaNetUserAuId(auId: Long): Unit
}

View File

@@ -118,7 +118,7 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
} }
@API("playlog") @API("playlog")
fun playlog(@RP id: Long): IGenericGamePlaylog = playlogRepo.findById(id).getOrNull() ?: (404 - "Playlog not found") fun playlog(@RP id: Long): IGenericGamePlaylog = playlogRepo.findById(id)() ?: (404 - "Playlog not found")
val userDetailFields by lazy { userDataClass.gettersMap().let { vm -> val userDetailFields by lazy { userDataClass.gettersMap().let { vm ->
(settableFields.keys.toSet() + gettableFields) (settableFields.keys.toSet() + gettableFields)
@@ -148,7 +148,7 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
userMusicRepo.findByUser_Card_ExtIdAndMusicIdIn(card.extId, musicList) userMusicRepo.findByUser_Card_ExtIdAndMusicIdIn(card.extId, musicList)
} }
fun genericUserSummary(card: Card, ratingComp: Map<String, String>, rival: Boolean? = null): GenericGameSummary { fun genericUserSummary(card: Card, ratingComp: Map<String, String>, rival: Boolean? = null, favorites: List<Int>? = null): GenericGameSummary {
// Summary values: total plays, player rating, server-wide ranking // Summary values: total plays, player rating, server-wide ranking
// number of each rank, max combo, number of full combo, number of all perfect // number of each rank, max combo, number of full combo, number of all perfect
val user = userDataRepo.findByCard(card) ?: (404 - "Game data not found") val user = userDataRepo.findByCard(card) ?: (404 - "Game data not found")
@@ -199,7 +199,8 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
ratingComposition = ratingComp, ratingComposition = ratingComp,
recent = plays.sortedBy { it.userPlayDate.toString() }.takeLast(100).reversed(), recent = plays.sortedBy { it.userPlayDate.toString() }.takeLast(100).reversed(),
lastPlayedHost = user.lastClientId?.let { us.userRepo.findByKeychip(it)?.username }, lastPlayedHost = user.lastClientId?.let { us.userRepo.findByKeychip(it)?.username },
rival = rival rival = rival,
favorites = favorites
) )
} }

View File

@@ -8,10 +8,10 @@ import java.time.LocalDate
const val LETTERS = "" + const val LETTERS = "" +
"" + "" +
"" ""
const val SYMBOLS = "・:;?!~/+-×÷=♂♀∀#&*@☆○◎◇□△▽♪†‡ΣαβγθφψωДё$()._" const val SYMBOLS = "・:;?!~/+-×÷=♂♀∀#&*@☆○◎◇□△▽♪†‡ΣαβγθφψωДё$()._ "
const val KANA = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん" + const val KANA = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん" +
"アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン" "アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン"
const val SEGA_USERNAME_CAHRS = LETTERS + SYMBOLS + KANA const val SEGA_USERNAME_CHARS = LETTERS + SYMBOLS + KANA
const val WACCA_USERNAME_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + const val WACCA_USERNAME_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
"abcdefghijklmnopqrstuvwxyz" + "abcdefghijklmnopqrstuvwxyz" +
"0123456789" + "0123456789" +

View File

@@ -1,22 +1,22 @@
package icu.samnyan.aqua.net.games package icu.samnyan.aqua.net.games
import ext.* import ext.*
import icu.samnyan.aqua.net.Fedy
import icu.samnyan.aqua.net.db.AquaNetUser import icu.samnyan.aqua.net.db.AquaNetUser
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.Fedy
import icu.samnyan.aqua.net.utils.AquaNetProps import icu.samnyan.aqua.net.utils.AquaNetProps
import icu.samnyan.aqua.net.utils.SUCCESS import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.general.model.Card
import org.springframework.beans.factory.annotation.Autowired import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Lazy
import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.repository.NoRepositoryBean import org.springframework.data.repository.NoRepositoryBean
import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.support.TransactionTemplate import org.springframework.transaction.support.TransactionTemplate
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.*
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.writeText import kotlin.io.path.writeText
import kotlin.reflect.KClass import kotlin.reflect.KClass
import org.springframework.context.annotation.Lazy
data class ExportOptions( data class ExportOptions(
val playlogAfter: String? = null val playlogAfter: String? = null
@@ -42,7 +42,7 @@ interface IExportClass<UserModel: IUserData> {
@NoRepositoryBean @NoRepositoryBean
interface IUserRepo<UserModel, ThisModel>: JpaRepository<ThisModel, Long> { interface IUserRepo<UserModel, ThisModel>: JpaRepository<ThisModel, Long> {
fun findByUser(user: UserModel): List<ThisModel> fun findByUser(user: UserModel): List<ThisModel>
fun findSingleByUser(user: UserModel): Optional<ThisModel> fun findSingleByUser(user: UserModel): ThisModel?
} }
/** /**
@@ -81,14 +81,14 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
val listRepos = exportRepos.filter { it.key returns List::class } val listRepos = exportRepos.filter { it.key returns List::class }
val singleRepos = exportRepos.filter { !(it.key returns List::class) } val singleRepos = exportRepos.filter { !(it.key returns List::class) }
fun export(u: AquaNetUser): ExportModel = export(u, ExportOptions()) fun export(u: AquaNetUser): ExportModel = export(u.ghostCard, ExportOptions())
fun export(u: AquaNetUser, options: ExportOptions) = createEmpty().apply { fun export(c: Card, options: ExportOptions) = createEmpty().apply {
gameId = game gameId = game
userData = userDataRepo.findByCard(u.ghostCard) ?: (404 - "User not found") userData = userDataRepo.findByCard(c) ?: (404 - "User not found")
exportRepos.forEach { (f, u) -> exportRepos.forEach { (f, u) ->
if (f returns List::class) f.set(this, u.findByUser(userData)) if (f returns List::class) f.set(this, u.findByUser(userData))
else u.findSingleByUser(userData)()?.let { f.set(this, it) } else u.findSingleByUser(userData)?.let { f.set(this, it) }
} }
customExporters.forEach { (f, exporter) -> customExporters.forEach { (f, exporter) ->
exporter(userData, options)?.let { f.set(this, it) } exporter(userData, options)?.let { f.set(this, it) }
@@ -147,7 +147,7 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
} }
} }
Fedy.getGameName(game)?.let { fedy.onImported(it, u.ghostCard.extId) } Fedy.getGameName(game)?.let { fedy.onDataUpdated(u.ghostCard.extId, it, true) }
SUCCESS SUCCESS
} }

View File

@@ -47,7 +47,8 @@ data class GenericGameSummary(
val recent: List<IGenericGamePlaylog>, val recent: List<IGenericGamePlaylog>,
val rival: Boolean? val rival: Boolean?,
val favorites: List<Int>?
) )
data class GenericRankingPlayer( data class GenericRankingPlayer(
@@ -124,7 +125,7 @@ open class BaseEntity(
@NoRepositoryBean @NoRepositoryBean
interface GenericUserDataRepo<T : IUserData> : JpaRepository<T, Long> { interface GenericUserDataRepo<T : IUserData> : JpaRepository<T, Long> {
fun findByCard(card: Card): T? fun findByCard(card: Card): T?
fun findByCard_ExtId(extId: Long): Optional<T> fun findByCard_ExtId(extId: Long): T?
@Query("select e from #{#entityName} e where e.card.rankingBanned = false") @Query("select e from #{#entityName} e where e.card.rankingBanned = false")
fun findAllNonBanned(): List<T> fun findAllNonBanned(): List<T>

View File

@@ -3,8 +3,11 @@ package icu.samnyan.aqua.net.games.chu3
import ext.* import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.games.*
import icu.samnyan.aqua.net.utils.* import icu.samnyan.aqua.net.utils.chu3Scores
import icu.samnyan.aqua.sega.chusan.model.* import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo
import icu.samnyan.aqua.sega.chusan.model.Chu3UserMusicDetailRepo
import icu.samnyan.aqua.sega.chusan.model.Chu3UserPlaylogRepo
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
@@ -25,7 +28,7 @@ class Chusan(
// Only show > AAA rank // Only show > AAA rank
override val shownRanks = chu3Scores.filter { it.first >= 95 * 10000 } override val shownRanks = chu3Scores.filter { it.first >= 95 * 10000 }
override val settableFields: Map<String, (Chu3UserData, String) -> Unit> by lazy { mapOf( override val settableFields: Map<String, (Chu3UserData, String) -> Unit> by lazy { mapOf(
"userName" to usernameCheck(SEGA_USERNAME_CAHRS), "userName" to usernameCheck(SEGA_USERNAME_CHARS),
"nameplateId" to { u, v -> u.nameplateId = v.int }, "nameplateId" to { u, v -> u.nameplateId = v.int },
"frameId" to { u, v -> u.frameId = v.int }, "frameId" to { u, v -> u.frameId = v.int },
"trophyId" to { u, v -> u.trophyId = v.int }, "trophyId" to { u, v -> u.trophyId = v.int },
@@ -60,7 +63,9 @@ class Chusan(
"new" to (extra["rating_new_list"] ?: ""), "new" to (extra["rating_new_list"] ?: ""),
) )
genericUserSummary(card, ratingComposition) val misc = rp.userMisc.findByUser_Card_ExtId(card.extId).firstOrNull()
genericUserSummary(card, ratingComposition, null, misc?.favMusic)
} }
/** /**

View File

@@ -14,6 +14,7 @@ import icu.samnyan.aqua.sega.maimai2.model.userdata.*
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import kotlin.reflect.full.declaredMembers import kotlin.reflect.full.declaredMembers
@Suppress("UNCHECKED_CAST")
@RestController @RestController
@API("api/v2/game/mai2") @API("api/v2/game/mai2")
class Mai2Import( class Mai2Import(
@@ -61,7 +62,7 @@ class Mai2Import(
} }
}, },
Maimai2DataExport::userFavoriteMusicList to { user: Mai2UserDetail, _: ExportOptions -> Maimai2DataExport::userFavoriteMusicList to { user: Mai2UserDetail, _: ExportOptions ->
repos.userGeneralData.findByUserAndPropertyKey(user, "favorite_music").orElse(null) repos.userGeneralData.findByUserAndPropertyKey(user, "favorite_music")
?.propertyValue ?.propertyValue
?.takeIf { it.isNotEmpty() } ?.takeIf { it.isNotEmpty() }
?.split(",") ?.split(",")
@@ -78,7 +79,7 @@ class Mai2Import(
if (favoriteMusicList.isNotEmpty()) { if (favoriteMusicList.isNotEmpty()) {
val key = "favorite_music" val key = "favorite_music"
// This field always imports as incremental, since the userGeneralData field (for backwards compatibility) is processed before this // This field always imports as incremental, since the userGeneralData field (for backwards compatibility) is processed before this
val data = repos.userGeneralData.findByUserAndPropertyKey(user, key).orElse(null) val data = repos.userGeneralData.findByUserAndPropertyKey(user, key)
?: Mai2UserGeneralData().apply { this.user = user; propertyKey = key } ?: Mai2UserGeneralData().apply { this.user = user; propertyKey = key }
repos.userGeneralData.save(data.apply { repos.userGeneralData.save(data.apply {
propertyValue = favoriteMusicList.sortedBy { it.orderId }.map { it.id }.joinToString(",") propertyValue = favoriteMusicList.sortedBy { it.orderId }.map { it.id }.joinToString(",")

View File

@@ -1,6 +1,9 @@
package icu.samnyan.aqua.net.games.mai2 package icu.samnyan.aqua.net.games.mai2
import ext.* import ext.API
import ext.RB
import ext.RP
import ext.minus
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.utils.SUCCESS import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
@@ -17,24 +20,18 @@ class Mai2MusicDetailImport(
@PostMapping("import-music-detail") @PostMapping("import-music-detail")
suspend fun importMusicDetail(@RP token: String, @RB data: List<Mai2UserMusicDetail>) = us.jwt.auth(token) { u -> suspend fun importMusicDetail(@RP token: String, @RB data: List<Mai2UserMusicDetail>) = us.jwt.auth(token) { u ->
us.cardByName(u.username) { card -> us.cardByName(u.username) { card ->
val user = repos.userData.findByCardExtId(card.extId).orElse(null) ?: (404 - "User not found") val user = repos.userData.findByCardExtId(card.extId) ?: (404 - "User not found")
data.forEach { newMusic -> data.forEach { newMusic ->
val musicRec = repos.userMusicDetail.findByUserAndMusicIdAndLevel(user, newMusic.musicId, newMusic.level) newMusic.user = user
if (musicRec.isPresent) { repos.userMusicDetail.findByUserAndMusicIdAndLevel(user, newMusic.musicId, newMusic.level)?.let { m ->
val music = musicRec.get()
newMusic.apply { newMusic.apply {
id = music.id id = m.id
this.user = user achievement = achievement.coerceAtLeast(m.achievement)
achievement = achievement.coerceAtLeast(music.achievement) scoreRank = scoreRank.coerceAtLeast(m.scoreRank)
scoreRank = scoreRank.coerceAtLeast(music.scoreRank) comboStatus = comboStatus.coerceAtLeast(m.comboStatus)
comboStatus = comboStatus.coerceAtLeast(music.comboStatus) syncStatus = syncStatus.coerceAtLeast(m.syncStatus)
syncStatus = syncStatus.coerceAtLeast(music.syncStatus) deluxscoreMax = deluxscoreMax.coerceAtLeast(m.deluxscoreMax)
deluxscoreMax = deluxscoreMax.coerceAtLeast(music.deluxscoreMax) playCount = playCount.coerceAtLeast(m.playCount)
playCount = playCount.coerceAtLeast(music.playCount)
}
} else {
newMusic.apply {
this.user = user
} }
} }
} }

View File

@@ -3,14 +3,19 @@ package icu.samnyan.aqua.net.games.mai2
import ext.* import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.games.*
import icu.samnyan.aqua.net.utils.* import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.net.utils.mai2Scores
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPhotoHandler import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPhotoHandler
import icu.samnyan.aqua.sega.maimai2.model.* import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
import icu.samnyan.aqua.sega.maimai2.model.userdata.* import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserMusicDetailRepo
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserPlaylogRepo
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserDetail
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserGeneralData
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserLoginBonus
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import java.util.*
@RestController @RestController
@API("api/v2/game/mai2") @API("api/v2/game/mai2")
@@ -29,7 +34,7 @@ class Maimai2(
// Only show > S rank // Only show > S rank
override val shownRanks = mai2Scores.filter { it.first >= 97 * 10000 } override val shownRanks = mai2Scores.filter { it.first >= 97 * 10000 }
override val settableFields: Map<String, (Mai2UserDetail, String) -> Unit> by lazy { mapOf( override val settableFields: Map<String, (Mai2UserDetail, String) -> Unit> by lazy { mapOf(
"userName" to usernameCheck(SEGA_USERNAME_CAHRS), "userName" to usernameCheck(SEGA_USERNAME_CHARS),
"iconId" to { u, v -> u.iconId = v.int() }, "iconId" to { u, v -> u.iconId = v.int() },
"plateId" to { u, v -> u.plateId = v.int() }, "plateId" to { u, v -> u.plateId = v.int() },
"titleId" to { u, v -> u.titleId = v.int() }, "titleId" to { u, v -> u.titleId = v.int() },
@@ -58,15 +63,15 @@ class Maimai2(
us.jwt.auth(t) { u -> us.jwt.auth(t) { u ->
if (u.username == username) return@auth null if (u.username == username) return@auth null
us.cardByName(u.username) { myCard -> us.cardByName(u.username) { myCard ->
val user = repos.userData.findByCardExtId(card.extId).orElse(null) ?: (404 - "User not found") val user = repos.userData.findByCardExtId(card.extId) ?: (404 - "User not found")
val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival") val myRival = (repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival")?.propertyValue?.split(',') ?: emptyList())
.map { it.propertyValue.split(',') }.orElse(emptyList()).filter { it.isNotEmpty() }.map { it.long() } .filter { it.isNotEmpty() }.map { it.long() }
myRival.contains(user.id) myRival.contains(user.id)
} }
} }
} }
genericUserSummary(card, ratingComposition, isMyRival) genericUserSummary(card, ratingComposition, isMyRival, extra["favorite_music"]?.split(",")?.mapNotNull{it -> it.toIntOrNull()})
} }
@API("user-rating") @API("user-rating")
@@ -132,7 +137,7 @@ class Maimai2(
if (loginBonus.none { it.bonusId == bonusId }) { if (loginBonus.none { it.bonusId == bonusId }) {
// create one // create one
val newBonus = Mai2UserLoginBonus().apply { val newBonus = Mai2UserLoginBonus().apply {
user = repos.userData.findByCardExtId(card.extId).orElse(null) ?: (404 - "User not found") user = repos.userData.findByCardExtId(card.extId) ?: (404 - "User not found")
this.bonusId = bonusId this.bonusId = bonusId
isCurrent = true isCurrent = true
} }
@@ -154,10 +159,10 @@ class Maimai2(
suspend fun setRival(@RP token: String, @RP rivalUserName: String, @RP isAdd: Boolean) = us.jwt.auth(token) { u -> suspend fun setRival(@RP token: String, @RP rivalUserName: String, @RP isAdd: Boolean) = us.jwt.auth(token) { u ->
us.cardByName(u.username) { myCard -> us.cardByName(u.username) { myCard ->
val rivalCard = us.cardByName(rivalUserName) { it } val rivalCard = us.cardByName(rivalUserName) { it }
val rivalUser = repos.userData.findByCardExtId(rivalCard.extId).orElse(null) ?: (404 - "User not found") val rivalUser = repos.userData.findByCardExtId(rivalCard.extId) ?: (404 - "User not found")
val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival").orElse(null) val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival")
?: Mai2UserGeneralData().apply { ?: Mai2UserGeneralData().apply {
user = repos.userData.findByCardExtId(myCard.extId).orElse(null) ?: (404 - "User not found") user = repos.userData.findByCardExtId(myCard.extId) ?: (404 - "User not found")
propertyKey = "favorite_rival" propertyKey = "favorite_rival"
} }
val myRivalList = myRival.propertyValue.split(',').filter { it.isNotEmpty() }.mut val myRivalList = myRival.propertyValue.split(',').filter { it.isNotEmpty() }.mut

View File

@@ -3,7 +3,7 @@ package icu.samnyan.aqua.net.games.ongeki
import ext.API import ext.API
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.games.*
import icu.samnyan.aqua.net.utils.* import icu.samnyan.aqua.net.utils.ongekiScores
import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserGeneralDataRepo import icu.samnyan.aqua.sega.ongeki.OgkUserGeneralDataRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserMusicDetailRepo import icu.samnyan.aqua.sega.ongeki.OgkUserMusicDetailRepo
@@ -27,7 +27,7 @@ class Ongeki(
override val shownRanks = ongekiScores.filter { it.first >= 950000 } override val shownRanks = ongekiScores.filter { it.first >= 950000 }
override val settableFields: Map<String, (UserData, String) -> Unit> by lazy { mapOf( override val settableFields: Map<String, (UserData, String) -> Unit> by lazy { mapOf(
"userName" to usernameCheck(SEGA_USERNAME_CAHRS), "userName" to usernameCheck(SEGA_USERNAME_CHARS),
"lastRomVersion" to { u, v -> u.lastRomVersion = v }, "lastRomVersion" to { u, v -> u.lastRomVersion = v },
"lastDataVersion" to { u, v -> u.lastDataVersion = v }, "lastDataVersion" to { u, v -> u.lastDataVersion = v },

View File

@@ -1,6 +1,9 @@
package icu.samnyan.aqua.net.games.wacca package icu.samnyan.aqua.net.games.wacca
import ext.* import ext.API
import ext.RP
import ext.isoDate
import ext.utc
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.games.*
import icu.samnyan.aqua.net.utils.waccaScores import icu.samnyan.aqua.net.utils.waccaScores
@@ -31,7 +34,10 @@ class Wacca(
override suspend fun userSummary(@RP username: String, @RP token: String?) = us.cardByName(username) { card -> override suspend fun userSummary(@RP username: String, @RP token: String?) = us.cardByName(username) { card ->
// TODO: Rating composition // TODO: Rating composition
genericUserSummary(card, mapOf())
val data = userDataRepo.findByCard_ExtId(card.extId)
genericUserSummary(card, mapOf(), null, data?.favoriteSongs)
} }
override val shownRanks: List<Pair<Int, String>> = waccaScores.filter { it.first > 85 * 10000 } override val shownRanks: List<Pair<Int, String>> = waccaScores.filter { it.first > 85 * 10000 }

View File

@@ -3,10 +3,10 @@ package icu.samnyan.aqua.net.transfer
import ext.header import ext.header
import ext.post import ext.post
import ext.request import ext.request
import java.net.URI
import icu.samnyan.aqua.sega.aimedb.AimeDbClient import icu.samnyan.aqua.sega.aimedb.AimeDbClient
import icu.samnyan.aqua.sega.allnet.AllNetBillingDecoder import icu.samnyan.aqua.sega.allnet.AllNetBillingDecoder
import icu.samnyan.aqua.sega.allnet.AllNetBillingDecoder.decodeAllNetResp import icu.samnyan.aqua.sega.allnet.AllNetBillingDecoder.decodeAllNetResp
import java.net.URI
val keychipPattern = Regex("([A-Z\\d]{4}-[A-Z\\d]{11}|[A-Z\\d]{11})") val keychipPattern = Regex("([A-Z\\d]{4}-[A-Z\\d]{11}|[A-Z\\d]{11})")

View File

@@ -11,9 +11,9 @@ import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserItem
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserMusicDetail import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserMusicDetail
import icu.samnyan.aqua.sega.ongeki.model.OngekiUpsertUserAll import icu.samnyan.aqua.sega.ongeki.model.OngekiUpsertUserAll
import icu.samnyan.aqua.sega.ongeki.model.UserItem import icu.samnyan.aqua.sega.ongeki.model.UserItem
import icu.samnyan.aqua.sega.util.jackson.BasicMapper import icu.samnyan.aqua.sega.util.BasicMapper
import icu.samnyan.aqua.sega.util.jackson.IMapper import icu.samnyan.aqua.sega.util.IMapper
import icu.samnyan.aqua.sega.util.jackson.StringMapper import icu.samnyan.aqua.sega.util.StringMapper
abstract class DataBroker( abstract class DataBroker(

View File

@@ -1,11 +1,11 @@
package icu.samnyan.aqua.sega.aimedb package icu.samnyan.aqua.sega.aimedb
import ext.* import ext.logger
import icu.samnyan.aqua.net.BotProps import ext.toHex
import icu.samnyan.aqua.net.Fedy
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.sega.allnet.AllNetProps import icu.samnyan.aqua.sega.allnet.AllNetProps
import icu.samnyan.aqua.sega.general.model.Card import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.model.CardStatus
import icu.samnyan.aqua.sega.general.service.CardService import icu.samnyan.aqua.sega.general.service.CardService
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import io.netty.buffer.ByteBufUtil import io.netty.buffer.ByteBufUtil
@@ -26,6 +26,7 @@ class AimeDB(
val cardService: CardService, val cardService: CardService,
val us: AquaUserServices, val us: AquaUserServices,
val allNetProps: AllNetProps, val allNetProps: AllNetProps,
val fedy: Fedy,
): ChannelInboundHandlerAdapter() { ): ChannelInboundHandlerAdapter() {
val logger = logger() val logger = logger()
@@ -68,9 +69,9 @@ class AimeDB(
*/ */
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
if (msg !is Map<*, *>) return if (msg !is Map<*, *>) return
try {
val type = msg["type"] as Int val type = msg["type"] as Int
val data = msg["data"] as ByteBuf val data = msg["data"] as ByteBuf
try {
val base = data.decodeHeader() val base = data.decodeHeader()
val handler = handlers[type] ?: return logger.error("AimeDB: Unknown request type 0x${type.toString(16)}") val handler = handlers[type] ?: return logger.error("AimeDB: Unknown request type 0x${type.toString(16)}")
@@ -89,6 +90,7 @@ class AimeDB(
handler.fn(data)?.let { ctx.write(it) } handler.fn(data)?.let { ctx.write(it) }
} finally { } finally {
data.release()
ctx.flush() ctx.flush()
ctx.close() ctx.close()
} }
@@ -124,7 +126,7 @@ class AimeDB(
} }
} }
fun getCard(accessCode: String) = us.cardRepo.findByLuid(accessCode)()?.maybeGhost()?.let { card -> fun getCard(accessCode: String) = us.cardRepo.findByLuid(accessCode)?.maybeGhost()?.let { card ->
// Update card access time and return the extId // Update card access time and return the extId
us.cardRepo.save(card.apply { accessTime = LocalDateTime.now() }).extId us.cardRepo.save(card.apply { accessTime = LocalDateTime.now() }).extId
} ?: -1 } ?: -1
@@ -195,11 +197,13 @@ class AimeDB(
var status = 0 var status = 0
var aimeId = 0L var aimeId = 0L
if (us.cardRepo.findByLuid(luid).isEmpty) { if (us.cardRepo.findByLuid(luid) == null) {
val card: Card = cardService.registerByAccessCode(luid) val card: Card = cardService.registerByAccessCode(luid)
status = 1 status = 1
aimeId = card.extId aimeId = card.extId
fedy.onCardCreated(luid, card.extId)
} }
else logger.warn("> Duplicated Aime Card Register detected, access code: $luid") else logger.warn("> Duplicated Aime Card Register detected, access code: $luid")

View File

@@ -1,12 +1,30 @@
package icu.samnyan.aqua.sega.aimedb package icu.samnyan.aqua.sega.aimedb
import icu.samnyan.aqua.sega.util.ByteBufUtil
import io.netty.buffer.ByteBuf import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled.copiedBuffer import io.netty.buffer.Unpooled.copiedBuffer
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import javax.crypto.Cipher import javax.crypto.Cipher
import javax.crypto.spec.SecretKeySpec import javax.crypto.spec.SecretKeySpec
fun ByteBuf.toBytes(): ByteArray {
val readerPos = readerIndex()
resetReaderIndex()
val result = ByteArray(readableBytes())
readBytes(result)
readerIndex(readerPos)
return result
}
fun ByteBuf.toAllBytes(): ByteArray {
val readerPos = readerIndex()
resetReaderIndex()
writerIndex(capacity())
val result = ByteArray(capacity())
readBytes(result)
readerIndex(readerPos)
return result
}
/** /**
* @author samnyan (privateamusement@protonmail.com) * @author samnyan (privateamusement@protonmail.com)
*/ */
@@ -15,7 +33,6 @@ object AimeDbEncryption {
val enc = Cipher.getInstance("AES/ECB/NoPadding").apply { init(Cipher.ENCRYPT_MODE, KEY) } val enc = Cipher.getInstance("AES/ECB/NoPadding").apply { init(Cipher.ENCRYPT_MODE, KEY) }
val dec = Cipher.getInstance("AES/ECB/NoPadding").apply { init(Cipher.DECRYPT_MODE, KEY) } val dec = Cipher.getInstance("AES/ECB/NoPadding").apply { init(Cipher.DECRYPT_MODE, KEY) }
fun decrypt(src: ByteBuf) = copiedBuffer(dec.doFinal(ByteBufUtil.toBytes(src))) fun decrypt(src: ByteBuf) = copiedBuffer(dec.doFinal(src.toBytes()))
fun encrypt(src: ByteBuf) = copiedBuffer(enc.doFinal(src.toAllBytes()))
fun encrypt(src: ByteBuf) = copiedBuffer(enc.doFinal(ByteBufUtil.toAllBytes(src)))
} }

View File

@@ -9,8 +9,6 @@ import io.netty.channel.socket.SocketChannel
import io.netty.channel.socket.nio.NioServerSocketChannel import io.netty.channel.socket.nio.NioServerSocketChannel
import io.netty.handler.logging.LogLevel import io.netty.handler.logging.LogLevel
import io.netty.handler.logging.LoggingHandler import io.netty.handler.logging.LoggingHandler
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration import org.springframework.context.annotation.Configuration
import org.springframework.stereotype.Component import org.springframework.stereotype.Component

View File

@@ -3,7 +3,6 @@ package icu.samnyan.aqua.sega.allnet
import ext.* import ext.*
import icu.samnyan.aqua.net.db.AquaNetUserRepo import icu.samnyan.aqua.net.db.AquaNetUserRepo
import icu.samnyan.aqua.sega.allnet.AllNetBillingDecoder.decodeAllNet import icu.samnyan.aqua.sega.allnet.AllNetBillingDecoder.decodeAllNet
import icu.samnyan.aqua.sega.util.AquaConst
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse import jakarta.servlet.http.HttpServletResponse
import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.boot.context.properties.ConfigurationProperties
@@ -14,7 +13,6 @@ import java.io.InputStream
import java.nio.charset.StandardCharsets import java.nio.charset.StandardCharsets
import java.time.Instant import java.time.Instant
import java.time.LocalDateTime import java.time.LocalDateTime
import java.util.*
@Configuration @Configuration
@ConfigurationProperties(prefix = "allnet.server") @ConfigurationProperties(prefix = "allnet.server")
@@ -84,7 +82,7 @@ class AllNet(
logger.info("AllNet /DownloadOrder : $reqMap") logger.info("AllNet /DownloadOrder : $reqMap")
val serial = reqMap["serial"] ?: AquaConst.DEFAULT_KEYCHIP_ID val serial = reqMap["serial"] ?: "A69E01A8888"
val resp = mapOf( val resp = mapOf(
"stat" to "1", "stat" to "1",
"serial" to serial "serial" to serial

View File

@@ -0,0 +1,108 @@
package icu.samnyan.aqua.sega.cardmaker
import ext.API
import ext.logger
import ext.long
import ext.parsing
import icu.samnyan.aqua.sega.allnet.TokenChecker
import icu.samnyan.aqua.sega.util.BasicMapper
import org.springframework.beans.factory.annotation.Value
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.net.InetAddress
import java.net.UnknownHostException
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@RestController
@RequestMapping("/g/card")
class CardMakerController(
val mapper: BasicMapper,
@param:Value("\${allnet.server.host:}") val ALLNET_HOST: String,
@param:Value("\${allnet.server.port:}") val ALLNET_PORT: String,
@param:Value("\${server.port:}") val SERVER_PORT: String
) {
val logger = logger()
@API("GetGameSettingApi")
fun getGameSetting(@ModelAttribute request: MutableMap<String, Any>): Any? {
val formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss")
val rebootStartTime = LocalDateTime.now().minusHours(3)
val rebootEndTime = LocalDateTime.now().minusHours(2)
val gameSetting = mapOf(
"dataVersion" to "1.35.0",
"ongekiCmVersion" to "1.32.0",
"chuniCmVersion" to "1.30.0",
"maimaiCmVersion" to "1.45.0",
"isMaintenance" to false,
"requestInterval" to 10,
"rebootStartTime" to rebootStartTime.format(formatter),
"rebootEndTime" to rebootEndTime.format(formatter),
"isBackgroundDistribute" to false,
"maxCountCharacter" to 100,
"maxCountItem" to 100,
"maxCountCard" to 100,
"watermark" to false
)
val json = mapper.write(mapOf(
"gameSetting" to gameSetting,
"isDumpUpload" to false,
"isAou" to false
))
logger.info("Response: {}", json)
return json
}
fun gameConnect(modelKind: Int, modelVersion: Int, url: String) =
mapOf("modelKind" to modelKind, "modelVersion" to modelVersion, "url" to url)
@API("GetGameConnectApi")
fun getGameConnect(@ModelAttribute request: MutableMap<String, Any>): Any? {
val version = parsing { request["version"]!!.long } // Rom version
val session = TokenChecker.Companion.getCurrentSession()
val addr = ALLNET_HOST.ifBlank { null } ?:
try { InetAddress.getLocalHost().hostAddress }
catch (_: UnknownHostException) { "localhost" }
val port = ALLNET_PORT.ifBlank { null } ?: SERVER_PORT
val base = if (session == null) "/g" else "/gs/" + session.token
val json = mapper.write(mapOf(
"length" to 3,
"gameConnectList" to listOf(
gameConnect(0, 1, "http://$addr:$port$base/chu3/$version/"),
gameConnect(1, 1, "http://$addr:$port$base/mai2/"),
gameConnect(2, 1, "http://$addr:$port$base/ongeki/")
)
))
logger.info("Response: $json")
return json
}
@API("GetClientBookkeepingApi")
fun getClientBookkeeping(@ModelAttribute request: MutableMap<String, Any>): Any? {
val placeId = parsing { request["placeId"]!!.long }
val json = mapper.write(mapOf(
"placeId" to placeId,
"length" to 0,
"clientBookkeepingList" to mutableListOf<Any>()
))
logger.info("Response: $json")
return json
}
@API("UpsertClientBookkeepingApi")
fun upsertClientBookkeeping() = "{\"returnCode\":1,\"apiName\":\"UpsertClientBookkeepingApi\"}"
@API("UpsertClientSettingApi")
fun upsertClientSetting() = "{\"returnCode\":1,\"apiName\":\"UpsertClientSettingApi\"}"
}

View File

@@ -0,0 +1,31 @@
package icu.samnyan.aqua.sega.cardmaker
import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import ext.logger
import jakarta.servlet.http.HttpServletRequest
import org.springframework.web.bind.annotation.ModelAttribute
import org.springframework.web.bind.annotation.RestControllerAdvice
import java.nio.charset.StandardCharsets
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@RestControllerAdvice(basePackages = ["icu.samnyan.aqua.sega.cardmaker"])
class CardMakerControllerAdvice {
val logger = logger()
val mapper = ObjectMapper()
/**
* Get the map object from json string
*
* @param request HttpServletRequest
*/
@ModelAttribute
fun preHandle(request: HttpServletRequest): MutableMap<String, Any> {
val src = request.inputStream.readAllBytes()
val outputString = String(src, StandardCharsets.UTF_8).trim { it <= ' ' }
logger.info("Request ${request.requestURI}: $outputString")
return mapper.readValue(outputString, object : TypeReference<MutableMap<String, Any>>() {})
}
}

View File

@@ -1,56 +0,0 @@
package icu.samnyan.aqua.sega.cardmaker.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.cardmaker.handler.impl.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@RestController
@RequestMapping("/g/card")
public class CardMakerController {
private final GetGameSettingHandler getGameSettingHandler;
private final GetClientBookkeepingHandler getClientBookkeepingHandler;
private final GetGameConnectHandler getGameConnectHandler;
@Autowired
public CardMakerController(GetGameSettingHandler getGameSettingHandler, GetClientBookkeepingHandler getClientBookkeepingHandler, GetGameConnectHandler getGameConnectHandler) {
this.getGameSettingHandler = getGameSettingHandler;
this.getClientBookkeepingHandler = getClientBookkeepingHandler;
this.getGameConnectHandler = getGameConnectHandler;
}
@PostMapping("GetGameSettingApi")
public String getGameSetting(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getGameSettingHandler.handle(request);
}
@PostMapping("GetGameConnectApi")
public String getGameConnect(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getGameConnectHandler.handle(request);
}
@PostMapping("GetClientBookkeepingApi")
public String getClientBookkeeping(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getClientBookkeepingHandler.handle(request);
}
@PostMapping("UpsertClientBookkeepingApi")
public String upsertClientBookkeeping(@ModelAttribute Map<String, Object> request) {
return "{\"returnCode\":1,\"apiName\":\"UpsertClientBookkeepingApi\"}";
}
@PostMapping("UpsertClientSettingApi")
public String upsertClientSetting(@ModelAttribute Map<String, Object> request) {
return "{\"returnCode\":1,\"apiName\":\"UpsertClientSettingApi\"}";
}
}

View File

@@ -1,39 +0,0 @@
package icu.samnyan.aqua.sega.cardmaker.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@RestControllerAdvice(basePackages = "icu.samnyan.aqua.sega.cardmaker")
public class CardMakerControllerAdvice {
private static final Logger logger = LoggerFactory.getLogger(CardMakerControllerAdvice.class);
/**
* Get the map object from json string
*
* @param request HttpServletRequest
*/
@ModelAttribute
public Map<String, Object> preHandle(HttpServletRequest request) throws IOException {
byte[] src = request.getInputStream().readAllBytes();
String outputString = new String(src, StandardCharsets.UTF_8).trim();
logger.info("Request " + request.getRequestURI() + ": " + outputString);
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(outputString, new TypeReference<>() {
});
}
}

View File

@@ -1,45 +0,0 @@
package icu.samnyan.aqua.sega.cardmaker.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.util.jackson.BasicMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component("CardMakerGetClientBookkeepingHandler")
public class GetClientBookkeepingHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetClientBookkeepingHandler.class);
private final BasicMapper mapper;
@Autowired
public GetClientBookkeepingHandler(BasicMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
long placeId = ((Number) request.get("placeId")).longValue();
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("placeId", placeId);
resultMap.put("length", 0);
resultMap.put("clientBookkeepingList", List.of());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,80 +0,0 @@
package icu.samnyan.aqua.sega.cardmaker.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.allnet.KeychipSession;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.cardmaker.model.response.data.GameConnect;
import icu.samnyan.aqua.sega.util.jackson.BasicMapper;
import icu.samnyan.aqua.sega.allnet.TokenChecker;
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.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component("CardMakerGetGameConnectHandler")
public class GetGameConnectHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetGameConnectHandler.class);
private final BasicMapper mapper;
private final String ALLNET_HOST;
private final String ALLNET_PORT;
private final String SERVER_PORT;
@Autowired
public GetGameConnectHandler(BasicMapper mapper, @Value("${allnet.server.host:}") String ALLNET_HOST,
@Value("${allnet.server.port:}") String ALLNET_PORT, @Value("${server.port:}") String SERVER_PORT) {
this.mapper = mapper;
this.ALLNET_HOST = ALLNET_HOST;
this.ALLNET_PORT = ALLNET_PORT;
this.SERVER_PORT = SERVER_PORT;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
int type = ((Number) request.get("type")).intValue(); // Allnet enabled or not
long version = ((Number) request.get("version")).longValue(); // Rom version
KeychipSession session = TokenChecker.Companion.getCurrentSession();
// Unless ip and port is explicitly overridden, use the guessed ip and port as same as AllNet Controller does.
String localAddr;
try {
localAddr = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
// If above didn't work then how did this run? I really don't know.
localAddr = "localhost";
}
String addr = ALLNET_HOST.equals("") ? localAddr : ALLNET_HOST;
String port = ALLNET_PORT.equals("") ? SERVER_PORT : ALLNET_PORT;
String base = session == null ? "/g" : "/gs/" + session.getToken();
List<GameConnect> gameConnectList = new ArrayList<>();
GameConnect chuni = new GameConnect(0, 1, "http://" + addr + ":" + port + base + "/chu3/" + version + "/");
GameConnect mai = new GameConnect(1, 1, "http://" + addr + ":" + port + base + "/mai2/");
GameConnect ongeki = new GameConnect(2, 1, "http://" + addr + ":" + port + base + "/ongeki/");
gameConnectList.add(chuni);
gameConnectList.add(mai);
gameConnectList.add(ongeki);
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("length", gameConnectList.size());
resultMap.put("gameConnectList", gameConnectList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,67 +0,0 @@
package icu.samnyan.aqua.sega.cardmaker.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.cardmaker.model.response.GetGameSettingResp;
import icu.samnyan.aqua.sega.cardmaker.model.response.data.GameSetting;
import icu.samnyan.aqua.sega.util.jackson.BasicMapper;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component("CardMakerGetGameSettingHandler")
public class GetGameSettingHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetGameSettingHandler.class);
private final BasicMapper mapper;
@Autowired
public GetGameSettingHandler(BasicMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(@NotNull Map<String, ?> request) throws JsonProcessingException {
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
LocalDateTime rebootStartTime = LocalDateTime.now().minusHours(3);
LocalDateTime rebootEndTime = LocalDateTime.now().minusHours(2);
GameSetting gameSetting = new GameSetting(
"1.35.0",
"1.32.0",
"1.30.0",
"1.45.0",
false,
10,
rebootStartTime.format(formatter),
rebootEndTime.format(formatter),
false,
100,
100,
100,
false);
GetGameSettingResp resp = new GetGameSettingResp(
gameSetting,
false,
false
);
String json = mapper.write(resp);
logger.info("Response: {}", json);
return json;
}
}

View File

@@ -1,16 +0,0 @@
package icu.samnyan.aqua.sega.cardmaker.model.response;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CodeResp {
private int returnCode;
private String apiName;
}

View File

@@ -1,21 +0,0 @@
package icu.samnyan.aqua.sega.cardmaker.model.response;
import com.fasterxml.jackson.annotation.JsonProperty;
import icu.samnyan.aqua.sega.cardmaker.model.response.data.GameSetting;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GetGameSettingResp {
private GameSetting gameSetting;
@JsonProperty("isDumpUpload")
private boolean isDumpUpload;
@JsonProperty("isAou")
private boolean isAou;
}

View File

@@ -1,14 +0,0 @@
package icu.samnyan.aqua.sega.cardmaker.model.response.data;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GameConnect {
private int modelKind; // 0: chunithm, 1: maimai, 2: ongeki
private int type; // 0: LAN, 1: WAN
private String titleUri;
}

View File

@@ -1,31 +0,0 @@
package icu.samnyan.aqua.sega.cardmaker.model.response.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GameSetting {
private String dataVersion;
private String ongekiCmVersion;
private String chuniCmVersion;
private String maimaiCmVersion;
@JsonProperty("isMaintenance")
private boolean isMaintenance;
private int requestInterval;
private String rebootStartTime;
private String rebootEndTime;
@JsonProperty("isBackgroundDistribute")
private boolean isBackgroundDistribute;
private int maxCountCharacter;
private int maxCountItem;
private int maxCountCard;
private boolean watermark;
}

View File

@@ -1,269 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.chunithm.handler.impl.*;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@RestController
@RequestMapping("/g/chu2/{ROM_VERSION}/{CLIENT_ID}/ChuniServlet")
@AllArgsConstructor
public class ChuniServletController {
private final GameLoginHandler gameLoginHandler;
private final GameLogoutHandler gameLogoutHandler;
private final GetGameChargeHandler getGameChargeHandler;
private final GetGameEventHandler getGameEventHandler;
private final GetGameIdlistHandler getGameIdlistHandler;
private final GetGameMessageHandler getGameMessageHandler;
private final GetGameRankingHandler getGameRankingHandler;
private final GetGameSaleHandler getGameSaleHandler;
private final GetGameSettingHandler getGameSettingHandler;
private final GetTeamCourseRuleHandler getTeamCourseRuleHandler;
private final GetTeamCourseSettingHandler getTeamCourseSettingHandler;
private final GetUserActivityHandler getUserActivityHandler;
private final GetUserCharacterHandler getUserCharacterHandler;
private final GetUserChargeHandler getUserChargeHandler;
private final GetUserCourseHandler getUserCourseHandler;
private final GetUserDataExHandler getUserDataExHandler;
private final GetUserDataHandler getUserDataHandler;
private final GetUserDuelHandler getUserDuelHandler;
private final GetUserFavoriteItemHandler getUserFavoriteItemHandler;
private final GetUserFavoriteMusicHandler getUserFavoriteMusicHandler;
private final GetUserItemHandler getUserItemHandler;
private final GetUserLoginBonusHandler getUserLoginBonusHandler;
private final GetUserMapHandler getUserMapHandler;
private final GetUserMusicHandler getUserMusicHandler;
private final GetUserOptionExHandler getUserOptionExHandler;
private final GetUserOptionHandler getUserOptionHandler;
private final GetUserPreviewHandler getUserPreviewHandler;
private final GetUserRecentRatingHandler getUserRecentRatingHandler;
private final GetUserRegionHandler getUserRegionHandler;
private final GetUserRivalDataHandler getUserRivalDataHandler;
private final GetUserRivalMusicHandler getUserRivalMusicHandler;
private final GetUserTeamHandler getUserTeamHandler;
private final UpsertClientSettingHandler upsertClientSettingHandler;
private final UpsertUserAllHandler upsertUserAllHandler;
private final UpsertUserChargelogHandler upsertUserChargelogHandler;
@PostMapping("GameLoginApi")
String gameLogin(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return gameLoginHandler.handle(request);
}
@PostMapping("GameLogoutApi")
String gameLogout(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return gameLogoutHandler.handle(request);
}
@PostMapping("GetGameChargeApi")
String getGameCharge(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getGameChargeHandler.handle(request);
}
@PostMapping("GetGameEventApi")
String getGameEvent(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getGameEventHandler.handle(request);
}
@PostMapping("GetGameIdlistApi")
String getGameIdList(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getGameIdlistHandler.handle(request);
}
@PostMapping("GetGameMessageApi")
String getGameMessage(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getGameMessageHandler.handle(request);
}
@PostMapping("GetGameRankingApi")
String getGameRanking(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getGameRankingHandler.handle(request);
}
@PostMapping("GetGameSaleApi")
String getGameSale(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getGameSaleHandler.handle(request);
}
/**
* The game start up request
*
* @return json of GameSetting object
*/
@PostMapping("GetGameSettingApi")
String getGameSetting(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getGameSettingHandler.handle(request);
}
@PostMapping("GetTeamCourseRuleApi")
String getTeamCourseRule(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getTeamCourseRuleHandler.handle(request);
}
@PostMapping("GetTeamCourseSettingApi")
String getTeamCourseSetting(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getTeamCourseSettingHandler.handle(request);
}
@PostMapping("GetUserActivityApi")
String getUserActivity(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserActivityHandler.handle(request);
}
@PostMapping("GetUserCharacterApi")
String getUserCharacter(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserCharacterHandler.handle(request);
}
@PostMapping("GetUserChargeApi")
String getUserCharge(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserChargeHandler.handle(request);
}
@PostMapping("GetUserCourseApi")
String getUserCourse(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserCourseHandler.handle(request);
}
@PostMapping("GetUserDataApi")
String getUserData(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserDataHandler.handle(request);
}
@PostMapping("GetUserDataExApi")
String getUserDataEx(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserDataExHandler.handle(request);
}
@PostMapping("GetUserDuelApi")
String getUserDuel(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserDuelHandler.handle(request);
}
@PostMapping("GetUserFavoriteItemApi")
String getUserFavoriteItem(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserFavoriteItemHandler.handle(request);
}
@PostMapping("GetUserFavoriteMusicApi")
public String getUserFavoriteMusic(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserFavoriteMusicHandler.handle(request);
}
@PostMapping("GetUserItemApi")
String getUserItem(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserItemHandler.handle(request);
}
@PostMapping("GetUserLoginBonusApi")
String getUserLoginBonus(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserLoginBonusHandler.handle(request);
}
@PostMapping("GetUserMapApi")
String getUserMap(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserMapHandler.handle(request);
}
@PostMapping("GetUserMusicApi")
String getUserMusic(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserMusicHandler.handle(request);
}
@PostMapping("GetUserOptionApi")
String getUserOption(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserOptionHandler.handle(request);
}
@PostMapping("GetUserOptionExApi")
String getUserOptionEx(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserOptionExHandler.handle(request);
}
// Call when login. Return null if no profile exist
@PostMapping("GetUserPreviewApi")
String getUserPreview(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserPreviewHandler.handle(request);
}
@PostMapping("GetUserRecentRatingApi")
String getUserRecentRating(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserRecentRatingHandler.handle(request);
}
/**
* For older version chunithm
*/
@PostMapping("GetUserRecentPlayerApi")
String getUserRecentPlayerApi(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserRecentRatingHandler.handle(request);
}
@PostMapping("GetUserRegionApi")
String getUserRegion(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserRegionHandler.handle(request);
}
@PostMapping("GetUserRivalDataApi")
String getUserRivalData(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserRivalDataHandler.handle(request);
}
@PostMapping("GetUserRivalMusicApi")
String getUserRivalMusic(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserRivalMusicHandler.handle(request);
}
@PostMapping("GetUserTeamApi")
String getUserTeam(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return getUserTeamHandler.handle(request);
}
@PostMapping("UpsertClientBookkeepingApi")
String upsertClientBookkeeping(@ModelAttribute Map<String, Object> request) {
return "{\"returnCode\":\"1\"}";
}
@PostMapping("UpsertClientDevelopApi")
String upsertClientDevelop(@ModelAttribute Map<String, Object> request) {
return "{\"returnCode\":\"1\"}";
}
@PostMapping("UpsertClientErrorApi")
String upsertClientError(@ModelAttribute Map<String, Object> request) {
return "{\"returnCode\":\"1\"}";
}
@PostMapping("UpsertClientSettingApi")
String upsertClientSetting(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return upsertClientSettingHandler.handle(request);
}
@PostMapping("UpsertClientTestmodeApi")
String upsertClientTestmode(@ModelAttribute Map<String, Object> request) {
return "{\"returnCode\":\"1\"}";
}
@PostMapping("UpsertUserAllApi")
String upsertUserAll(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return upsertUserAllHandler.handle(request);
}
@PostMapping("UpsertUserChargelogApi")
String upsertUserChargelog(@ModelAttribute Map<String, Object> request) throws JsonProcessingException {
return upsertUserChargelogHandler.handle(request);
}
@PostMapping("Ping")
String ping(@ModelAttribute Map<String, Object> request) {
return "{\"returnCode\":\"1\"}";
}
}

View File

@@ -1,47 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.controller;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.HandlerMapping;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import static icu.samnyan.aqua.sega.util.AquaConst.*;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@RestControllerAdvice(basePackages = "icu.samnyan.aqua.sega.chunithm")
public class ChuniServletControllerAdvice {
private static final Logger logger = LoggerFactory.getLogger(ChuniServletControllerAdvice.class);
/**
* Get the map object from json string
*
* @param request HttpServletRequest
*/
@ModelAttribute
public Map<String, Object> preHandle(HttpServletRequest request) throws IOException {
var pathVar = (Map<String, String>) request.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE);
byte[] src = request.getInputStream().readAllBytes();
String outputString = new String(src, StandardCharsets.UTF_8).trim();
logger.info("Request " + request.getRequestURI() + ": " + outputString);
ObjectMapper mapper = new ObjectMapper();
Map<String, Object> result = mapper.readValue(outputString, new TypeReference<>() {
});
result.put(SERIAL_KEY, pathVar.getOrDefault(SERIAL_KEY, DEFAULT_KEYCHIP_ID));
result.put(VERSION_KEY, pathVar.getOrDefault(VERSION_KEY, CHUNI_DEFAULT_VERSION));
return result;
}
}

View File

@@ -1,12 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.gamedata;
import icu.samnyan.aqua.sega.chunithm.model.gamedata.Character;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository("ChuniGameCharacterRepository")
public interface GameCharacterRepository extends JpaRepository<Character, Long> {
}

View File

@@ -1,12 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.gamedata;
import icu.samnyan.aqua.sega.chunithm.model.gamedata.CharacterSkill;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository("ChuniGameCharacterSkillRepository")
public interface GameCharacterSkillRepository extends JpaRepository<CharacterSkill, Long> {
}

View File

@@ -1,12 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.gamedata;
import icu.samnyan.aqua.sega.chunithm.model.gamedata.GameCharge;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository("ChuniGameChargeRepository")
public interface GameChargeRepository extends JpaRepository<GameCharge, Long> {
}

View File

@@ -1,16 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.gamedata;
import icu.samnyan.aqua.sega.chunithm.model.gamedata.GameEvent;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository("ChuniGameEventRepository")
public interface GameEventRepository extends JpaRepository<GameEvent, Integer> {
List<GameEvent> findByEnable(boolean enable);
}

View File

@@ -1,12 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.gamedata;
import icu.samnyan.aqua.sega.chunithm.model.gamedata.GameMessage;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository("ChuniGameMessageRepository")
public interface GameMessageRepository extends JpaRepository<GameMessage, Integer> {
}

View File

@@ -1,16 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.gamedata;
import icu.samnyan.aqua.sega.chunithm.model.gamedata.Music;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository("ChuniGameMusicRepository")
public interface GameMusicRepository extends JpaRepository<Music, Long> {
Optional<Music> findByMusicId(int musicId);
}

View File

@@ -1,22 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserActivity;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserActivityRepository extends JpaRepository<UserActivity, Long> {
Optional<UserActivity> findTopByUserAndActivityIdAndKindOrderByIdDesc(UserData user, int activityId, int kind);
List<UserActivity> findAllByUser_Card_ExtIdAndKindOrderBySortNumberDesc(Long extId, int kind);
List<UserActivity> findAllByUser_Card_ExtId(Long extId);
}

View File

@@ -1,27 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserCharacter;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserCharacterRepository extends JpaRepository<UserCharacter, Long> {
Page<UserCharacter> findByUser_Card_ExtId(Long extId, Pageable pageable);
List<UserCharacter> findByUser_Card_ExtId(Long extId);
Optional<UserCharacter> findTopByUserAndCharacterIdOrderByIdDesc(UserData user, int characterId);
Optional<UserCharacter> findByUser_Card_ExtIdAndCharacterId(Long extId, int characterId);
}

View File

@@ -1,19 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserCharge;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserChargeRepository extends JpaRepository<UserCharge, Long> {
List<UserCharge> findByUser_Card_ExtId(Long extId);
Optional<UserCharge> findByUserAndChargeId(UserData extId, int chargeId);
}

View File

@@ -1,23 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserCourse;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserCourseRepository extends JpaRepository<UserCourse, Long> {
Optional<UserCourse> findTopByUserAndCourseIdOrderByIdDesc(UserData user, int courseId);
Page<UserCourse> findByUser_Card_ExtId(Long extId, Pageable page);
List<UserCourse> findByUser_Card_ExtId(Long extId);
}

View File

@@ -1,19 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserDataEx;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserDataExRepository extends JpaRepository<UserDataEx, Long> {
Optional<UserDataEx> findByUser(UserData user);
Optional<UserDataEx> findByUser_Card_ExtId(Long extId);
}

View File

@@ -1,19 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.general.model.Card;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserDataRepository extends JpaRepository<UserData, Long> {
Optional<UserData> findByCard(Card card);
Optional<UserData> findByCard_ExtId(Long extId);
}

View File

@@ -1,20 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserDuel;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserDuelRepository extends JpaRepository<UserDuel, Long> {
Optional<UserDuel> findTopByUserAndDuelIdOrderByIdDesc(UserData user, int duelId);
List<UserDuel> findByUser_Card_ExtId(Long extId);
}

View File

@@ -1,18 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserGameOptionEx;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserGameOptionExRepository extends JpaRepository<UserGameOptionEx, Long> {
Optional<UserGameOptionEx> findByUser(UserData user);
Optional<UserGameOptionEx> findByUser_Card_ExtId(Long extId);
}

View File

@@ -1,19 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserGameOption;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserGameOptionRepository extends JpaRepository<UserGameOption, Long> {
Optional<UserGameOption> findByUser(UserData user);
Optional<UserGameOption> findByUser_Card_ExtId(Long extId);
}

View File

@@ -1,20 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserGeneralData;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository("ChuniUserGeneralDataRepository")
public interface UserGeneralDataRepository extends JpaRepository<UserGeneralData, Long> {
Optional<UserGeneralData> findByUserAndPropertyKey(UserData user, String key);
Optional<UserGeneralData> findByUser_Card_ExtIdAndPropertyKey(Long extId, String key);
}

View File

@@ -1,26 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserItem;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserItemRepository extends JpaRepository<UserItem, Long> {
Optional<UserItem> findTopByUserAndItemIdAndItemKindOrderByIdDesc(UserData user, int itemId, int itemKind);
Page<UserItem> findAllByUser_Card_ExtIdAndItemKind(Long extId, int itemKind, Pageable pageable);
List<UserItem> findAllByUser_Card_ExtId(Long extId);
Page<UserItem> findByUser_Card_ExtId(Long extId, Pageable pageable);
}

View File

@@ -1,21 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserMap;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserMapRepository extends JpaRepository<UserMap, Long> {
List<UserMap> findAllByUser(UserData user);
List<UserMap> findAllByUser_Card_ExtId(Long extId);
Optional<UserMap> findTopByUserAndMapIdOrderByIdDesc(UserData user, int mapId);
}

View File

@@ -1,26 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserMusicDetail;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserMusicDetailRepository extends JpaRepository<UserMusicDetail, Long> {
Optional<UserMusicDetail> findTopByUserAndMusicIdAndLevelOrderByIdDesc(UserData user, int musicId, int level);
List<UserMusicDetail> findByUser_Card_ExtId(Long extId);
List<UserMusicDetail> findByUser_Card_ExtIdAndMusicId(Long extId, int musicId);
Page<UserMusicDetail> findByUser_Card_ExtId(Long extId, Pageable page);
}

View File

@@ -1,28 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.dao.userdata;
import icu.samnyan.aqua.sega.chunithm.model.response.data.GameRanking;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserPlaylog;
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.stereotype.Repository;
import java.util.List;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Repository
public interface UserPlaylogRepository extends JpaRepository<UserPlaylog, Long> {
List<UserPlaylog> findByUser_Card_ExtIdAndLevelNot(Long extId, int levelNot, Pageable page);
Page<UserPlaylog> findByUser_Card_ExtId(Long extId, Pageable page);
List<UserPlaylog> findByUser_Card_ExtIdAndMusicIdAndLevel(Long extId, int musicId, int level);
List<UserPlaylog> findByUser_Card_ExtId(Long extId);
@Query("SELECT NEW icu.samnyan.aqua.sega.chunithm.model.response.data.GameRanking(c.musicId, COUNT(c.musicId)) FROM ChuniUserPlaylog c WHERE NOT c.level = 4 GROUP BY c.musicId ORDER BY COUNT(c.musicId) DESC")
Page<GameRanking> findGameRankingByPlaylog(Pageable page);
}

View File

@@ -1,43 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.response.CodeResp;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.service.UserDataService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GameLoginHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GameLoginHandler.class);
private final StringMapper mapper;
private final UserDataService userDataService;
public GameLoginHandler(StringMapper mapper, UserDataService userDataService) {
this.mapper = mapper;
this.userDataService = userDataService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Optional<UserData> userDataOptional = userDataService.getUserByExtId(userId);
userDataOptional.ifPresent(userDataService::updateLoginTime);
String json = mapper.write(new CodeResp(1));
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,34 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.response.CodeResp;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GameLogoutHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GameLogoutHandler.class);
private final StringMapper mapper;
public GameLogoutHandler(StringMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String json = mapper.write(new CodeResp(1));
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,46 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.chunithm.dao.gamedata.GameChargeRepository;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.gamedata.GameCharge;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetGameChargeHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetGameChargeHandler.class);
private final GameChargeRepository gameChargeRepository;
private final StringMapper mapper;
@Autowired
public GetGameChargeHandler(GameChargeRepository gameChargeRepository, StringMapper mapper) {
this.gameChargeRepository = gameChargeRepository;
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
List<GameCharge> gameChargeList = gameChargeRepository.findAll();
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("length", gameChargeList.size());
resultMap.put("gameChargeList", gameChargeList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,50 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.chunithm.dao.gamedata.GameEventRepository;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.gamedata.GameEvent;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetGameEventHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetGameEventHandler.class);
private final GameEventRepository gameEventRepository;
private final StringMapper mapper;
@Autowired
public GetGameEventHandler(GameEventRepository gameEventRepository, StringMapper mapper) {
this.gameEventRepository = gameEventRepository;
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String type = (String) request.get("type");
List<GameEvent> gameEventList = gameEventRepository.findByEnable(true);
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("type", type);
resultMap.put("length", gameEventList.size());
resultMap.put("gameEventList", gameEventList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,47 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetGameIdlistHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetGameIdlistHandler.class);
private final StringMapper mapper;
@Autowired
public GetGameIdlistHandler(StringMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String type = (String) request.get("type");
List<Object> gameIdlistList = new ArrayList<>();
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("type", type);
resultMap.put("length", 0);
resultMap.put("gameIdlistList", gameIdlistList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,50 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.chunithm.dao.gamedata.GameMessageRepository;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.gamedata.GameMessage;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetGameMessageHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetGameMessageHandler.class);
private final GameMessageRepository gameMessageRepository;
private final StringMapper mapper;
@Autowired
public GetGameMessageHandler(GameMessageRepository gameMessageRepository, StringMapper mapper) {
this.gameMessageRepository = gameMessageRepository;
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String type = (String) request.get("type");
List<GameMessage> gameMessageList = gameMessageRepository.findAll();
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("type", type);
resultMap.put("length", gameMessageList.size());
resultMap.put("gameMessageList", gameMessageList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,51 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.chunithm.dao.userdata.UserPlaylogRepository;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.response.data.GameRanking;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetGameRankingHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetGameRankingHandler.class);
private final UserPlaylogRepository userPlaylogRepository;
private final StringMapper mapper;
@Autowired
public GetGameRankingHandler(StringMapper mapper, UserPlaylogRepository userPlaylogRepository) {
this.userPlaylogRepository = userPlaylogRepository;
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String type = (String) request.get("type");
Page<GameRanking> rankingPage = userPlaylogRepository.findGameRankingByPlaylog(PageRequest.of(0, 10));
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("type", type);
resultMap.put("gameRankingList", rankingPage.getContent());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,47 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.response.data.GameSale;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetGameSaleHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetGameSaleHandler.class);
private final StringMapper mapper;
@Autowired
public GetGameSaleHandler(StringMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String type = (String) request.get("type");
List<GameSale> gameSaleList = new ArrayList<>();
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("type", type);
resultMap.put("length", 0);
resultMap.put("gameSaleList", gameSaleList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,66 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.response.GetGameSettingResp;
import icu.samnyan.aqua.sega.chunithm.model.response.data.GameSetting;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetGameSettingHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetGameSettingHandler.class);
private final StringMapper mapper;
@Autowired
public GetGameSettingHandler(StringMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
// Fixed reboot time triggers chunithm maintenance lockout, so let's try minime method which sets it dynamically
// Special thanks to skogaby
// Hardcode so that the reboot time always started 3 hours ago and ended 2 hours ago
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("uuuu-MM-dd HH:mm:ss");
LocalDateTime rebootStartTime = LocalDateTime.now().minusHours(3);
LocalDateTime rebootEndTime = LocalDateTime.now().minusHours(2);
GameSetting gameSetting = new GameSetting(
1,
false,
10,
rebootStartTime.format(formatter),
rebootEndTime.format(formatter),
false,
300,
300,
300);
GetGameSettingResp resp = new GetGameSettingResp(
gameSetting,
false,
true
);
String json = mapper.write(resp);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,42 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.response.CodeResp;
import icu.samnyan.aqua.sega.chunithm.model.response.data.GameSale;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.service.UserDataService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.*;
@Component
public class GetTeamCourseRuleHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetTeamCourseRuleHandler.class);
private final StringMapper mapper;
public GetTeamCourseRuleHandler(StringMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", 0);
resultMap.put("nextIndex", 0);
resultMap.put("teamCourseRuleList", List.of());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,41 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Component
public class GetTeamCourseSettingHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetTeamCourseSettingHandler.class);
private final StringMapper mapper;
public GetTeamCourseSettingHandler(StringMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", 0);
resultMap.put("nextIndex", 0);
resultMap.put("teamCourseSettingList", List.of());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,53 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserActivity;
import icu.samnyan.aqua.sega.chunithm.service.UserActivityService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserActivityHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserActivityHandler.class);
private final StringMapper mapper;
private final UserActivityService userActivityService;
@Autowired
public GetUserActivityHandler(StringMapper mapper, UserActivityService userActivityService) {
this.mapper = mapper;
this.userActivityService = userActivityService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
String kind = (String) request.get("kind");
List<UserActivity> userActivityList = userActivityService.getAllByUserIdAndKind(userId, kind);
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", userActivityList.size());
resultMap.put("kind", kind);
resultMap.put("userActivityList", userActivityList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,60 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserCharacter;
import icu.samnyan.aqua.sega.chunithm.service.UserCharacterService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* Handle getUserCharacter request
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserCharacterHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserCharacterHandler.class);
private final StringMapper mapper;
private final UserCharacterService userCharacterService;
@Autowired
public GetUserCharacterHandler(StringMapper mapper, UserCharacterService userCharacterService) {
this.mapper = mapper;
this.userCharacterService = userCharacterService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
int nextIndex = Integer.parseInt((String) request.get("nextIndex"));
int maxCount = Integer.parseInt((String) request.get("maxCount"));
int pageNum = nextIndex / maxCount;
Page<UserCharacter> dbPage = userCharacterService.getByUserId(userId, pageNum, maxCount);
long currentIndex = maxCount * pageNum + dbPage.getNumberOfElements();
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", dbPage.getNumberOfElements());
resultMap.put("nextIndex", dbPage.getNumberOfElements() < maxCount ? -1 : currentIndex);
resultMap.put("userCharacterList", dbPage.getContent());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,50 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserCharge;
import icu.samnyan.aqua.sega.chunithm.service.UserChargeService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserChargeHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserChargeHandler.class);
private final StringMapper mapper;
private final UserChargeService userChargeService;
@Autowired
public GetUserChargeHandler(StringMapper mapper, UserChargeService userChargeService) {
this.mapper = mapper;
this.userChargeService = userChargeService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
List<UserCharge> userChargeList = userChargeService.getByUserId(userId);
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", userChargeList.size());
resultMap.put("userChargeList", userChargeList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,67 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserCourse;
import icu.samnyan.aqua.sega.chunithm.service.UserCourseService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Handle GetUserCourse request
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserCourseHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserCourseHandler.class);
private final StringMapper mapper;
private final UserCourseService userCourseService;
@Autowired
public GetUserCourseHandler(StringMapper mapper, UserCourseService userCourseService) {
this.mapper = mapper;
this.userCourseService = userCourseService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
if(request.containsKey("nextIndex")) {
int nextIndex = Integer.parseInt((String) request.get("nextIndex"));
int maxCount = Integer.parseInt((String) request.get("maxCount"));
int pageNum = nextIndex / maxCount;
Page<UserCourse> dbPage = userCourseService.getByUserId(userId, pageNum, maxCount);
long currentIndex = maxCount * pageNum + dbPage.getNumberOfElements();
resultMap.put("length", dbPage.getNumberOfElements());
resultMap.put("nextIndex", dbPage.getNumberOfElements() < maxCount ? -1 : currentIndex);
resultMap.put("userCourseList", dbPage.getContent());
} else {
List<UserCourse> courseList = userCourseService.getByUserId(userId);
resultMap.put("length", courseList.size());
resultMap.put("userCourseList", courseList);
}
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,53 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserDataEx;
import icu.samnyan.aqua.sega.chunithm.service.UserDataExService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserDataExHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserDataExHandler.class);
private final StringMapper mapper;
private final UserDataExService userDataExService;
@Autowired
public GetUserDataExHandler(StringMapper mapper, UserDataExService userDataExService) {
this.mapper = mapper;
this.userDataExService = userDataExService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Optional<UserDataEx> userDataExOptional = userDataExService.getByExtId(userId);
if (userDataExOptional.isPresent()) {
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("userDataEx", userDataExOptional.get());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
return null;
}
}

View File

@@ -1,69 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.service.UserDataService;
import icu.samnyan.aqua.sega.general.service.ClientSettingService;
import icu.samnyan.aqua.sega.util.VersionUtil;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import static icu.samnyan.aqua.sega.util.AquaConst.SERIAL_KEY;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserDataHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserDataHandler.class);
private final StringMapper mapper;
private final ClientSettingService clientSettingService;
private final UserDataService userDataService;
@Autowired
public GetUserDataHandler(StringMapper mapper,
ClientSettingService clientSettingService, UserDataService userDataService
) {
this.mapper = mapper;
this.clientSettingService = clientSettingService;
this.userDataService = userDataService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Optional<UserData> userDataOptional = userDataService.getUserByExtId(userId);
if (userDataOptional.isPresent()) {
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
UserData user = userDataOptional.get();
var vo = clientSettingService.getSetting((String) request.get(SERIAL_KEY));
if (vo.isPresent()) {
var version = vo.get();
user.setLastRomVersion(VersionUtil.getTargetVersion(user.getLastRomVersion(), version.getRomVersion()));
user.setLastDataVersion(VersionUtil.getTargetVersion(user.getLastDataVersion(), version.getDataVersion()));
}
resultMap.put("userData", user);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
return null;
}
}

View File

@@ -1,55 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserDuel;
import icu.samnyan.aqua.sega.chunithm.service.UserDuelService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Handle GetUserDuel request
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserDuelHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserDuelHandler.class);
private final StringMapper mapper;
private final UserDuelService userDuelService;
@Autowired
public GetUserDuelHandler(StringMapper mapper, UserDuelService userDuelService) {
this.mapper = mapper;
this.userDuelService = userDuelService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
String duelId = (String) request.get("duelId");
String isAllDuel = (String) request.get("isAllDuel");
// TODO:
List<UserDuel> userDuelList = userDuelService.getByUserId(userId);
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", userDuelList.size());
resultMap.put("userDuelList", userDuelList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,51 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Handle GetUserFavoriteItem request
* @author yueou (yueou.xu@gmail.com)
*/
@Component
public class GetUserFavoriteItemHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserFavoriteItemHandler.class);
private final StringMapper mapper;
@Autowired
public GetUserFavoriteItemHandler(StringMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
String kind = (String) request.get("kind");
// TODO:
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", 0);
resultMap.put("kind", kind);
resultMap.put("nextIndex", -1);
resultMap.put("userFavoriteItemList", List.of());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,49 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserDuel;
import icu.samnyan.aqua.sega.chunithm.service.UserDuelService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Handle GetUserDuel request
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserFavoriteMusicHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserFavoriteMusicHandler.class);
private final StringMapper mapper;
@Autowired
public GetUserFavoriteMusicHandler(StringMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
// TODO:
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", 0);
resultMap.put("userFavoriteMusicList", List.of());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,67 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserItem;
import icu.samnyan.aqua.sega.chunithm.service.UserItemService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Handler for getting user item.
* This get call before profile create.
*
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserItemHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserItemHandler.class);
private final StringMapper mapper;
private final UserItemService userItemService;
public GetUserItemHandler(StringMapper mapper, UserItemService userItemService) {
this.mapper = mapper;
this.userItemService = userItemService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Long nextIndexVal = Long.parseLong((String) request.get("nextIndex"));
int maxCount = Integer.parseInt((String) request.get("maxCount"));
Long mul = 10000000000L;
int kind = (int) (nextIndexVal / mul);
int nextIndex = (int) (nextIndexVal % mul);
int pageNum = nextIndex / maxCount;
Page<UserItem> userItemPage = userItemService.getByUserAndItemKind(userId, kind, pageNum, maxCount);
List<UserItem> userItemList = userItemPage.getContent();
long currentIndex = kind * mul + maxCount * pageNum + userItemPage.getNumberOfElements();
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", userItemPage.getNumberOfElements());
resultMap.put("nextIndex", userItemPage.getNumberOfElements() < maxCount ? -1 : currentIndex);
resultMap.put("itemKind", kind);
resultMap.put("userItemList", userItemList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,43 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Component
public class GetUserLoginBonusHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserLoginBonusHandler.class);
private final StringMapper mapper;
@Autowired
public GetUserLoginBonusHandler(StringMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
// TODO:
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", 0);
resultMap.put("userLoginBonusList", List.of());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,51 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserMap;
import icu.samnyan.aqua.sega.chunithm.service.UserMapService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* Handle GetUserMap request
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserMapHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserItemHandler.class);
private final StringMapper mapper;
private final UserMapService userMapService;
@Autowired
public GetUserMapHandler(StringMapper mapper, UserMapService userMapService) {
this.mapper = mapper;
this.userMapService = userMapService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
List<UserMap> userMapList = userMapService.getByUserId(userId);
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", userMapList.size());
resultMap.put("userMapList", userMapList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,97 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.response.data.UserMusicListItem;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserMusicDetail;
import icu.samnyan.aqua.sega.chunithm.service.GameMusicService;
import icu.samnyan.aqua.sega.chunithm.service.UserMusicDetailService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import icu.samnyan.aqua.spring.data.OffsetPageRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
import java.util.*;
/**
* Response:
*
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserMusicHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserMusicHandler.class);
private final StringMapper mapper;
private final UserMusicDetailService userMusicDetailService;
private final GameMusicService gameMusicService;
@Autowired
public GetUserMusicHandler(StringMapper mapper, UserMusicDetailService userMusicDetailService, GameMusicService gameMusicService) {
this.mapper = mapper;
this.userMusicDetailService = userMusicDetailService;
this.gameMusicService = gameMusicService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
int currentIndex = Integer.parseInt((String) request.get("nextIndex"));
int maxCount = Integer.parseInt((String) request.get("maxCount"));
if(currentIndex < 0) {
currentIndex = 0;
}
Page<UserMusicDetail> dbPage = userMusicDetailService
.getByUserId(userId, OffsetPageRequest.of(currentIndex, maxCount, Sort.by("musicId")));
// Convert to result format
// Result Map
Map<Integer, UserMusicListItem> userMusicMap = new LinkedHashMap<>();
dbPage.getContent().forEach(userMusicDetail -> {
UserMusicListItem list;
if (userMusicMap.containsKey(userMusicDetail.getMusicId())) {
list = userMusicMap.get(userMusicDetail.getMusicId());
} else {
list = new UserMusicListItem(0, new ArrayList<>());
userMusicMap.put(userMusicDetail.getMusicId(), list);
}
list.getUserMusicDetailList().add(userMusicDetail);
list.setLength(list.getUserMusicDetailList().size());
});
// Remove the last music id if the result length is the same as maxCount,
// to prevent a music id split across multiple page, which will cause some
// problem with the game.
int lastListSize = 0;
if(dbPage.getNumberOfElements() >= maxCount) {
// Get last key
int lastMusicId = userMusicMap.keySet().stream().reduce((a, b) -> b).orElseThrow();
List<UserMusicDetail> lastList = userMusicMap.get(lastMusicId).getUserMusicDetailList();
lastListSize = lastList.size();
// Remove last one from map
userMusicMap.remove(lastMusicId);
}
long nextIndex = currentIndex + dbPage.getNumberOfElements() - lastListSize;
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", userMusicMap.size());
resultMap.put("nextIndex", dbPage.getNumberOfElements() < maxCount ? -1 : nextIndex);
resultMap.put("userMusicList", userMusicMap.values());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,53 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserGameOptionEx;
import icu.samnyan.aqua.sega.chunithm.service.UserGameOptionExService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserOptionExHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserOptionExHandler.class);
private final StringMapper mapper;
private final UserGameOptionExService userGameOptionExService;
@Autowired
public GetUserOptionExHandler(StringMapper mapper, UserGameOptionExService userGameOptionExService) {
this.mapper = mapper;
this.userGameOptionExService = userGameOptionExService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Optional<UserGameOptionEx> userGameOptionEx = userGameOptionExService.getByUserId(userId);
if (userGameOptionEx.isPresent()) {
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("userGameOptionEx", userGameOptionEx.get());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
return null;
}
}

View File

@@ -1,52 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserGameOption;
import icu.samnyan.aqua.sega.chunithm.service.UserGameOptionService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserOptionHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserOptionHandler.class);
private final StringMapper mapper;
private final UserGameOptionService userGameOptionService;
@Autowired
public GetUserOptionHandler(StringMapper mapper, UserGameOptionService userGameOptionService) {
this.mapper = mapper;
this.userGameOptionService = userGameOptionService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Optional<UserGameOption> userGameOption = userGameOptionService.getByUserId(userId);
if (userGameOption.isPresent()) {
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("userGameOption", userGameOption.get());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
return null;
}
}

View File

@@ -1,114 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.response.GetUserPreviewResp;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserCharacter;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserGameOption;
import icu.samnyan.aqua.sega.chunithm.service.UserCharacterService;
import icu.samnyan.aqua.sega.chunithm.service.UserDataService;
import icu.samnyan.aqua.sega.chunithm.service.UserGameOptionService;
import icu.samnyan.aqua.sega.general.service.ClientSettingService;
import icu.samnyan.aqua.sega.util.VersionUtil;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Map;
import java.util.Optional;
import static icu.samnyan.aqua.sega.util.AquaConst.SERIAL_KEY;
/**
* The handler for loading basic profile information.
* <p>
* return null if no profile exist
*
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserPreviewHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserPreviewHandler.class);
private final StringMapper mapper;
private final ClientSettingService clientSettingService;
private final UserDataService userDataService;
private final UserCharacterService userCharacterService;
private final UserGameOptionService userGameOptionService;
@Autowired
public GetUserPreviewHandler(StringMapper mapper,
ClientSettingService clientSettingService, UserDataService userDataService,
UserCharacterService userCharacterService,
UserGameOptionService userGameOptionService
) {
this.mapper = mapper;
this.clientSettingService = clientSettingService;
this.userDataService = userDataService;
this.userCharacterService = userCharacterService;
this.userGameOptionService = userGameOptionService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Optional<UserData> userData = userDataService.getUserByExtId(userId);
if (userData.isEmpty()) {
return null;
}
UserData user = userData.get();
GetUserPreviewResp resp = new GetUserPreviewResp();
resp.setUserId(userId);
resp.setLogin(false);
resp.setLastLoginDate(user.getLastLoginDate());
resp.setUserName(user.getUserName());
resp.setReincarnationNum(user.getReincarnationNum());
resp.setLevel(user.getLevel());
resp.setExp(user.getExp());
resp.setPlayerRating(user.getPlayerRating());
resp.setLastGameId(user.getLastGameId());
var vo = clientSettingService.getSetting((String) request.get(SERIAL_KEY));
if (vo.isPresent()) {
var version = vo.get();
resp.setLastRomVersion(VersionUtil.getTargetVersion(user.getLastRomVersion(), version.getRomVersion()));
resp.setLastDataVersion(VersionUtil.getTargetVersion(user.getLastDataVersion(), version.getDataVersion()));
} else {
resp.setLastRomVersion(user.getLastRomVersion());
resp.setLastDataVersion(user.getLastDataVersion());
}
resp.setLastPlayDate(user.getLastPlayDate());
resp.setTrophyId(user.getTrophyId());
Optional<UserCharacter> userCharacterOptional = userCharacterService.getByUserAndCharacterId(user, user.getCharacterId());
userCharacterOptional.ifPresent(resp::setUserCharacter);
Optional<UserGameOption> userGameOptionOptional = userGameOptionService.getByUser(user);
userGameOptionOptional.ifPresent(userGameOption -> {
resp.setPlayerLevel(userGameOption.getPlayerLevel());
resp.setRating(userGameOption.getRating());
resp.setHeadphone(userGameOption.getHeadphone());
});
resp.setChargeState(1);
resp.setUserNameEx(user.getUserName());
String json = mapper.write(resp);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,80 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserGeneralData;
import icu.samnyan.aqua.sega.chunithm.service.UserGeneralDataService;
import icu.samnyan.aqua.sega.general.model.response.UserRecentRating;
import icu.samnyan.aqua.sega.chunithm.model.userdata.UserPlaylog;
import icu.samnyan.aqua.sega.chunithm.service.UserPlaylogService;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.*;
import java.util.stream.Collectors;
/**
* Return the recent play to calculate rating. Rating base on top 30 songs plus top 10 in recent 30 plays.
*
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserRecentRatingHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserRecentRatingHandler.class);
private final StringMapper mapper;
private final UserPlaylogService userPlaylogService;
private final UserGeneralDataService userGeneralDataService;
@Autowired
public GetUserRecentRatingHandler(StringMapper mapper, UserPlaylogService userPlaylogService, UserGeneralDataService userGeneralDataService) {
this.mapper = mapper;
this.userPlaylogService = userPlaylogService;
this.userGeneralDataService = userGeneralDataService;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Optional<UserGeneralData> recentOptional = userGeneralDataService.getByUserIdAndKey(userId, "recent_rating_list");
List<UserRecentRating> ratingList;
if(recentOptional.isPresent()) {
ratingList = new LinkedList<>();
String val = recentOptional.get().getPropertyValue();
if(StringUtils.isNotBlank(val) && val.contains(",")) {
String[] records = val.split(",");
for (String record :
records) {
String[] value = record.split(":");
ratingList.add(new UserRecentRating(
Integer.parseInt(value[0]),
Integer.parseInt(value[1]),
"1030000",
Integer.parseInt(value[2])
));
}
}
} else {
List<UserPlaylog> top = userPlaylogService.getRecent30Plays(userId);
ratingList = top.stream().map(log -> new UserRecentRating(log.getMusicId(), log.getLevel(), "1030000", log.getScore()))
.collect(Collectors.toList());
}
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", ratingList.size());
resultMap.put("userRecentRatingList", ratingList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,46 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* @author samnyan (privateamusement@protonmail.com)
*/
@Component
public class GetUserRegionHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserRegionHandler.class);
private final StringMapper mapper;
@Autowired
public GetUserRegionHandler(StringMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
List<Object> userRegionList = new ArrayList<>();
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", 0);
resultMap.put("userRegionList", userRegionList);
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

View File

@@ -1,39 +0,0 @@
package icu.samnyan.aqua.sega.chunithm.handler.impl;
import com.fasterxml.jackson.core.JsonProcessingException;
import icu.samnyan.aqua.sega.general.BaseHandler;
import icu.samnyan.aqua.sega.util.jackson.StringMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@Component
public class GetUserRivalDataHandler implements BaseHandler {
private static final Logger logger = LoggerFactory.getLogger(GetUserRivalDataHandler.class);
private final StringMapper mapper;
public GetUserRivalDataHandler(StringMapper mapper) {
this.mapper = mapper;
}
@Override
public String handle(Map<String, ?> request) throws JsonProcessingException {
String userId = (String) request.get("userId");
Map<String, Object> resultMap = new LinkedHashMap<>();
resultMap.put("userId", userId);
resultMap.put("length", 0);
resultMap.put("userRivalData", List.of());
String json = mapper.write(resultMap);
logger.info("Response: " + json);
return json;
}
}

Some files were not shown because too many files have changed in this diff Show More