42 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
513 changed files with 5084 additions and 18146 deletions

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

@@ -0,0 +1,32 @@
<script lang="ts">
export let username: string;
export let email: string;
let shouldShow = navigator.language.startsWith('zh');
// 会导致瞬间出现,但是不知道为什么 svelte 的 transition 动画不工作
// if (!shouldShow) {
// fetch('https://47.122.72.135/ip/isChina')
// .then(it => it.json())
// .then(it => shouldShow = it)
// .catch(() => shouldShow = false);
// }
const jump = () => {
const params = new URLSearchParams();
if (username) params.set('username', username);
if (email) params.set('email', email);
location.href = `https://portal.mumur.net/register?${params.toString()}`;
}
</script>
{#if shouldShow}
<div class="cursor-pointer" on:click={jump}>
<h2>MuNET 了解一下!</h2>
<div>
<p>MuNET 是 AquaDX 的继任者,提供更适合中国用户的服务器和更好的游戏体验。</p>
<p>如果你还没有游戏数据,建议在 MuNET 上创建账号并开始游戏。点击立即前往</p>
</div>
</div>
{/if}

View File

@@ -1,60 +0,0 @@
<script lang="ts">
import { createEventDispatcher } from 'svelte'
export let page: number
export let totalPages: number
const dispatch = createEventDispatcher()
let editing = false
let inputPage: number
function updatePage(newPage: number) {
if (newPage > 0 && newPage <= totalPages) dispatch('updatePage', newPage)
}
function startEditing() {
inputPage = page
editing = true
}
function finishEditing() {
editing = false
if (inputPage !== page) updatePage(inputPage)
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Enter') finishEditing()
else if (event.key === 'Escape') editing = false
}
</script>
<div class="pagination">
<button on:click={() => updatePage(page - 1)} disabled={page <= 1}>Previous</button>
{#if editing}
<input bind:value={inputPage} on:blur={finishEditing} on:keydown={handleKeydown} min="1" max={totalPages} autofocus/>
{:else}
<span on:click={startEditing} role="button" tabindex="0" on:keydown={(e) => e.key === 'Enter' && startEditing()}>
Page {page} of {totalPages}
</span>
{/if}
<button on:click={() => updatePage(page + 1)} disabled={page >= totalPages}>Next</button>
</div>
<style lang="sass">
.pagination
display: flex
justify-content: center
align-items: center
margin: 1rem 0
gap: 1rem
input
width: 100px
text-align: center
span[role="button"]
cursor: pointer
</style>

View File

@@ -1,6 +1,7 @@
<script>
import { fade } from "svelte/transition";
import { FADE_IN, FADE_OUT } from "../../libs/config";
import GameSettingFields from "./GameSettingFields.svelte";
import { t, ts } from "../../libs/i18n";
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
import RegionSelector from "./RegionSelector.svelte";
@@ -10,8 +11,9 @@
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="fields">
<blockquote>
{ts("settings.siteNotice")}
{ts("settings.gameNotice")}
</blockquote>
<GameSettingFields game="general"/>
<div class="field">
<div class="bool">
<input id="rounding" type="checkbox" bind:checked={rounding.value}/>

View File

@@ -1,6 +1,6 @@
<script lang="ts">
import { slide, fade } from "svelte/transition";
import { FADE_IN, FADE_OUT, DATA_HOST } from "../../libs/config";
import { FADE_IN, FADE_OUT } from "../../libs/config";
import { t } from "../../libs/i18n.js";
import Icon from "@iconify/svelte";
import StatusOverlays from "../StatusOverlays.svelte";
@@ -35,169 +35,6 @@
break
}
}
async function exportBatchManual() {
submitting = "batchExport"
const DIFFICULTY_MAP: Record<number, string> = {
0: "Basic",
1: "Advanced",
2: "Expert",
3: "Master",
4: "Re:Master"
}
const DAN_MAP: Record<number, string> = {
1: "DAN_1",
2: "DAN_2",
3: "DAN_3",
4: "DAN_4",
5: "DAN_5",
6: "DAN_6",
7: "DAN_7",
8: "DAN_8",
9: "DAN_9",
10: "DAN_10",
11: "SHINDAN_1",
12: "SHINDAN_2",
13: "SHINDAN_3",
14: "SHINDAN_4",
15: "SHINDAN_5",
16: "SHINDAN_6",
17: "SHINDAN_7",
18: "SHINDAN_8",
19: "SHINDAN_9",
20: "SHINDAN_10",
21: "SHINKAIDEN",
22: "URAKAIDEN"
}
const CLASS_MAP: Record<number, string> = {
0: "B5",
1: "B4",
2: "B3",
3: "B2",
4: "B1",
5: "A5",
6: "A4",
7: "A3",
8: "A2",
9: "A1",
10: "S5",
11: "S4",
12: "S3",
13: "S2",
14: "S1",
15: "SS5",
16: "SS4",
17: "SS3",
18: "SS2",
19: "SS1",
20: "SSS5",
21: "SSS4",
22: "SSS3",
23: "SSS2",
24: "SSS1",
25: "LEGEND"
}
let data: any
let musicData: any
let output: any = {
"meta": {
"game": "maimaidx",
"playtype": "Single",
"service": "AquaDX-Manual"
},
"scores": [],
"classes": {}
}
try {
musicData = await fetch(`${DATA_HOST}/d/mai2/00/all-music.json`).then(res => res.json())
} catch (e) {
error = e.message;
submitting = ""
return;
}
try {
data = await GAME.export('mai2');
} catch (e) {
error = e.message;
submitting = ""
return;
}
if (data && "userPlaylogList" in data) {
for (let score of data.userPlaylogList) {
if(score.musicId > 100000){
continue; // UTAGE charts are not supported
}
const musicItem = musicData[score.musicId as string];
if (!musicItem) continue;
let difficulty = null;
if (!(score.level in DIFFICULTY_MAP))
continue;
const isDX = score.musicId >= 10000;
difficulty = isDX ? `DX ${DIFFICULTY_MAP[score.level]}` : DIFFICULTY_MAP[score.level];
const percent = score.achievement/10000;
const pcrit = score.tapCriticalPerfect + score.holdCriticalPerfect + score.slideCriticalPerfect + score.touchCriticalPerfect + score.breakCriticalPerfect;
const perfect = score.tapPerfect + score.holdPerfect + score.slidePerfect + score.touchPerfect + score.breakPerfect;
const great = score.tapGreat + score.holdGreat + score.slideGreat + score.touchGreat + score.breakGreat;
const good = score.tapGood + score.holdGood + score.slideGood + score.touchGood + score.breakGood;
const miss = score.tapMiss + score.holdMiss + score.slideMiss + score.touchMiss + score.breakMiss;
const judgements = {
"pcrit": pcrit,
"perfect": perfect,
"great": great,
"good": good,
"miss": miss
}
let lamp = null;
if (score.isAllPerfect) {
lamp = "ALL PERFECT";
if (score.percent == 101.0) {
lamp = "ALL PERFECT+";
}
} else if (score.isFullCombo) {
lamp = "FULL COMBO";
if (good == 0 && great == 0) {
lamp = "FULL COMBO+";
}
} else if (score.isClear) {
lamp = "CLEAR";
} else {
lamp = "FAILED";
}
const optional = {
"fast": score.fastCount,
"slow": score.lateCount,
"maxCombo": score.maxCombo
}
output.scores.push({
"percent": percent,
"lamp": lamp,
"matchType": "inGameID",
"identifier": score.musicId.toString(),
"difficulty": difficulty,
"timeAchieved": new Date(score.userPlayDate).getTime(),
"judgements": judgements,
"optional": optional
})
}
}
if(data.userData.courseRank in DAN_MAP){
output.classes["dan"] = DAN_MAP[data.userData.courseRank]
}
if(data.userData.classRank in CLASS_MAP){
output.classes["matchingClass"] = CLASS_MAP[data.userData.classRank]
}
download(JSON.stringify(output), `AquaDX_maimai2_BatchManualExport_${values[0]}.json`)
submitting = ""
}
function exportData() {
submitting = "export"
@@ -233,10 +70,6 @@
<Icon icon="bxs:file-export"/>
{t('settings.export')}
</button>
<button class="exportBatchManualButton" on:click={exportBatchManual}>
<Icon icon="bxs:file-export"/>
{t('settings.batchManualExport')}
</button>
</div>
<StatusOverlays {error} loading={!values[0] || !!submitting}/>

View File

@@ -144,35 +144,23 @@ export const EN_REF_HOME = {
export const EN_REF_SETTINGS = {
'settings.title': 'Settings',
'settings.tabs.profile': 'Profile',
'settings.tabs.global': 'Global',
'settings.tabs.game': 'Game',
'settings.tabs.chu3': 'Chuni',
'settings.tabs.mai2': 'Mai',
'settings.tabs.ongeki': 'Ongeki',
'settings.tabs.wacca': 'Wacca',
'settings.fields.mai2UnlockMusic.name': 'Unlock All Music',
'settings.fields.mai2UnlockMusic.desc': 'Unlock all music and master difficulty.',
'settings.fields.mai2UnlockChara.name': 'Unlock All Characters',
'settings.fields.mai2UnlockChara.desc': 'Unlock all characters (new characters start at level 1).',
'settings.fields.mai2UnlockCharaMaxLevel.name': 'Max Character Level',
'settings.fields.mai2UnlockCharaMaxLevel.desc': 'Set all characters to max level.',
'settings.fields.mai2UnlockPartners.name': 'Unlock All Partners',
'settings.fields.mai2UnlockPartners.desc': 'Unlock all partners.',
'settings.fields.mai2UnlockCollectables.name': 'Unlock All Collectables',
'settings.fields.mai2UnlockCollectables.desc': 'Unlock all collectables (nameplate, title, icon, frame).',
'settings.fields.mai2UnlockTickets.name': 'Unlock All Tickets',
'settings.fields.mai2UnlockTickets.desc': 'Infinite tickets (Note: client still limits which tickets can be used).',
'settings.fields.waccaUnlockMusic.name': 'Unlock All Music',
'settings.fields.waccaUnlockMusic.desc': 'Unlock all music.',
'settings.fields.waccaUnlockPlates.name': 'Unlock All Plates',
'settings.fields.waccaUnlockPlates.desc': 'Unlock all plates.',
'settings.fields.waccaUnlockCollectables.name': 'Unlock All Collectables',
'settings.fields.waccaUnlockCollectables.desc': 'Unlock all collectables (icon, trophy).',
'settings.fields.waccaUnlockTickets.name': 'Infinite Tickets',
'settings.fields.waccaUnlockTickets.desc': 'Infinite tickets.',
'settings.fields.waccaInfiniteWp.name': 'Infinite WP',
'settings.fields.waccaInfiniteWp.desc': 'Set WP to 999999.',
'settings.fields.waccaAlwaysVip.name': 'Always VIP',
'settings.fields.waccaAlwaysVip.desc': 'Set VIP expiration date to 2077-01-01.',
'settings.fields.unlockMusic.name': 'Unlock All Music',
'settings.fields.unlockMusic.desc': 'Unlock all music and master difficulty in game.',
'settings.fields.unlockChara.name': 'Unlock All Characters',
'settings.fields.unlockChara.desc': 'Unlock all characters, voices, and partners in game.',
'settings.fields.unlockCollectables.name': 'Unlock All Collectables',
'settings.fields.unlockCollectables.desc': 'Unlock all collectables (nameplate, title, icon, frame) in game.',
'settings.fields.unlockTickets.name': 'Unlock All Tickets',
'settings.fields.unlockTickets.desc': 'Infinite map/ex tickets (Note: maimai still limits which tickets can be used).',
'settings.fields.waccaInfiniteWp.name': 'Wacca: Infinite WP',
'settings.fields.waccaInfiniteWp.desc': 'Set WP to 999999',
'settings.fields.waccaAlwaysVip.name': 'Wacca: Always VIP',
'settings.fields.waccaAlwaysVip.desc': 'Set VIP expiration date to 2077-01-01',
'settings.fields.chusanTeamName.name': 'Team Name',
'settings.fields.chusanTeamName.desc': 'Customize the text displayed on the top of your profile.',
'settings.fields.chusanInfinitePenguins.name': 'Infinite Penguins',
@@ -208,10 +196,10 @@ export const EN_REF_SETTINGS = {
'settings.export': 'Export Player Data',
'settings.batchManualExport': "Export in Batch Manual (for Tachi)",
'settings.cabNotice': "Note: These settings will only affect your own cab/setup. If you're playing on someone else's setup, please contact them to change these settings.",
'settings.siteNotice': "These settings only apply to the website.",
'settings.regionNotice': "These settings are shared amongst Mai, Ongeki and Chuni.",
'settings.gameNotice': "These only apply to Mai and Wacca.",
'settings.regionNotice': "These only apply to Mai, Ongeki and Chuni.",
'settings.regionSelector.title': "Prefecture Selector",
'settings.regionSelector.desc': "Select the region where you want the game to identify you",
'settings.regionSelector.desc': "Select the region where you want the game to think you are playing",
'settings.regionSelector.select': "Select Prefecture",
}

View File

@@ -156,35 +156,23 @@ const zhHome: typeof EN_REF_HOME = {
const zhSettings: typeof EN_REF_SETTINGS = {
'settings.title': '用户设置',
'settings.tabs.profile': '个人资料',
'settings.tabs.global': '全局',
'settings.tabs.game': '游戏设置',
'settings.tabs.chu3': '中二',
'settings.tabs.mai2': '舞萌',
'settings.tabs.ongeki': '音击',
'settings.tabs.wacca': '华卡',
'settings.fields.mai2UnlockMusic.name': '解锁谱面',
'settings.fields.mai2UnlockMusic.desc': '解锁所有曲目和大师难度谱面。',
'settings.fields.mai2UnlockChara.name': '解锁角色',
'settings.fields.mai2UnlockChara.desc': '解锁所有角色(新角色从 1 级开始)。',
'settings.fields.mai2UnlockCharaMaxLevel.name': '角色满级',
'settings.fields.mai2UnlockCharaMaxLevel.desc': '将所有角色设置为满级。',
'settings.fields.mai2UnlockPartners.name': '解锁搭档',
'settings.fields.mai2UnlockPartners.desc': '解锁所有搭档。',
'settings.fields.mai2UnlockCollectables.name': '解锁收藏品',
'settings.fields.mai2UnlockCollectables.desc': '解锁所有收藏品(姓名框、称号、头像、背景)。',
'settings.fields.mai2UnlockTickets.name': '解锁功能票',
'settings.fields.mai2UnlockTickets.desc': '无限功能票(注:客户端仍限制一些功能票不能使用)。',
'settings.fields.waccaUnlockMusic.name': '解锁谱面',
'settings.fields.waccaUnlockMusic.desc': '解锁所有曲目。',
'settings.fields.waccaUnlockPlates.name': '解锁铭牌',
'settings.fields.waccaUnlockPlates.desc': '解锁所有铭牌。',
'settings.fields.waccaUnlockCollectables.name': '解锁收藏品',
'settings.fields.waccaUnlockCollectables.desc': '解锁所有收藏品。',
'settings.fields.waccaUnlockTickets.name': '无限解锁券',
'settings.fields.waccaUnlockTickets.desc': '无限解锁券。',
'settings.fields.waccaInfiniteWp.name': '无限 WP',
'settings.fields.waccaInfiniteWp.desc': '将 WP 设置为 999999。',
'settings.fields.waccaAlwaysVip.name': '永久会员',
'settings.fields.waccaAlwaysVip.desc': '将 VIP 到期时间设置为 2077-01-01。',
'settings.fields.unlockMusic.name': '解锁谱面',
'settings.fields.unlockMusic.desc': '在游戏中解锁所有曲目和大师难度谱面。',
'settings.fields.unlockChara.name': '解锁角色',
'settings.fields.unlockChara.desc': '在游戏中解锁所有角色、语音和伙伴。',
'settings.fields.unlockCollectables.name': '解锁收藏品',
'settings.fields.unlockCollectables.desc': '在游戏中解锁所有收藏品(名牌、称号、图标、背景图)。',
'settings.fields.unlockTickets.name': '解锁游戏券',
'settings.fields.unlockTickets.desc': '无限跑图券/解锁券maimai 客户端仍限制一些券不能使用)。',
'settings.fields.waccaInfiniteWp.name': '华卡:无限 WP',
'settings.fields.waccaInfiniteWp.desc': '将 WP 设置为 999999',
'settings.fields.waccaAlwaysVip.name': '华卡:永久会员',
'settings.fields.waccaAlwaysVip.desc': '将 VIP 到期时间设置为 2077-01-01',
'settings.fields.chusanTeamName.name': '队伍名称',
'settings.fields.chusanTeamName.desc': '自定义显示在个人资料顶部的文本。',
'settings.fields.chusanInfinitePenguins.name': '我是桐谷遥',
@@ -220,10 +208,9 @@ const zhSettings: typeof EN_REF_SETTINGS = {
'settings.export': '导出玩家数据',
'settings.batchManualExport': "导出 Batch Manual 格式(用于 Tachi",
'settings.cabNotice': '注意:下面这些设置只会影响你自己的机器,如果你是在其他人的机器上玩的话,请联系机主来改设置',
'settings.gameNotice': "这些设置仅对舞萌和华卡生效。",
// AI
'settings.siteNotice': "这些设置仅适用于网站。",
// AI
'settings.regionNotice': "这些设置在舞萌、音击和中二节奏之间共享。",
'settings.regionNotice': "这些设置仅适用于舞萌、音击和中二。",
// AI
'settings.regionSelector.title': "地区选择器",
// AI

View File

@@ -10,6 +10,7 @@
import { t } from "../libs/i18n";
import ImportDataAction from "./Home/ImportDataAction.svelte";
import Communities from "./Home/Communities.svelte";
import MigrateAction from "./Home/MigrateAction.svelte";
USER.ensureLoggedIn();
@@ -58,6 +59,9 @@
</ActionCard>
<ImportDataAction/>
{#if me}
<MigrateAction username={me.username}/>
{/if}
</div>
{:else if tab === 1}
<div out:fade={FADE_OUT} in:fade={FADE_IN}>

View File

@@ -0,0 +1,69 @@
<script lang="ts">
import { fade } from "svelte/transition"
import { t } from "../../libs/i18n";
import ActionCard from "../../components/ActionCard.svelte";
import { CARD, GAME, USER } from "../../libs/sdk";
export let username: string;
let shouldShow = navigator.language.startsWith('zh');
let showWarning = false;
let isCardBindIssue = false;
if (!shouldShow) {
fetch('https://47.122.72.135/ip/isChina')
.then(it => it.json())
.then(it => shouldShow = it)
.catch(() => shouldShow = false);
}
CARD.userGames(username).then(games => {
if (!Object.values(games).some(it => it)) {
isCardBindIssue = true;
}
})
const handleClick = () => {
if (isCardBindIssue) {
showWarning = true;
return
}
jump()
}
const jump = () => {
const token = localStorage.getItem('token')
location.href = `https://portal.mumur.net/migrateFromAquaDx/${token}`
}
</script>
{#if shouldShow}
<ActionCard color="190, 149, 255" icon="system-uicons:jump-up" on:click={handleClick}>
<h3>迁移到 MuNET</h3>
<span>更适合中国宝宝体质的服务器AquaDX 的继任者。点击查看详情</span>
</ActionCard>
{/if}
{#if showWarning}
<div class="overlay" transition:fade>
<div>
<h2>提示</h2>
<p>看起来你在 AquaDX 还没有游戏数据,也许是因为没有绑卡或者绑定的卡不是在游戏中点击“查看卡号”获取的…</p>
<p>现在迁移的话,大概会导致你的游戏数据无法被正确的迁移。建议你先去检查一下吧</p>
<div class="buttons">
<button on:click={() => showWarning = false}>{t('action.cancel')}</button>
<button on:click={jump}>继续</button>
</div>
</div>
</div>
{/if}
<style lang="sass">
h3
font-size: 1.3rem
margin: 0
.buttons
display: grid
grid-template-columns: 1fr 1fr
gap: 1rem
</style>

View File

@@ -1,5 +1,4 @@
<script lang="ts">
import { onMount } from "svelte";
import { title } from "../libs/ui";
import { GAME } from "../libs/sdk";
import type { GenericRanking } from "../libs/generalTypes";
@@ -9,7 +8,6 @@
import { t } from "../libs/i18n";
import UserCard from "../components/UserCard.svelte";
import Tooltip from "../components/Tooltip.svelte";
import Pagination from "../components/Pagination.svelte";
export let game: GameName = 'mai2';
@@ -17,45 +15,15 @@
let d: { users: GenericRanking[] };
let error: string | null;
let page = 1
const perPage = 50
let totalPages = 1
function handleUpdatePage(event: CustomEvent<number>) {
page = event.detail;
const url = new URL(window.location.toString())
url.searchParams.set('page', page.toString())
history.pushState({}, '', url.toString())
window.scrollTo(0, 0)
}
onMount(() => {
const url = new URL(window.location.toString())
const pageParam = url.searchParams.get('page')
if (pageParam) {
page = parseInt(pageParam, 10) || 1
}
window.addEventListener('popstate', () => {
const url = new URL(window.location.toString())
const pageParam = url.searchParams.get('page')
page = parseInt(pageParam, 10) || 1
window.scrollTo(0, 0)
})
})
Promise.all([GAME.ranking(game)])
.then(([users]) => {
d = { users }
totalPages = Math.ceil(users.length / perPage)
console.log(users)
d = { users };
})
.catch((e) => error = e.message);
let hoveringUser = "";
let hoverLoading = false;
$: paginatedUsers = d ? d.users.slice((page - 1) * perPage, page * perPage) : []
</script>
<main class="content leaderboard">
@@ -69,12 +37,8 @@
</div>
{#if d}
{#if page > 1}
<Pagination {page} {totalPages} on:updatePage={handleUpdatePage} />
{/if}
<div class="leaderboard-container">
<div class="lb-user" on:mouseenter={() => hoveringUser = paginatedUsers[0]?.username} role="heading" aria-level="2">
<div class="lb-user" on:mouseenter={() => hoveringUser = d.users[0].username} role="heading" aria-level="2">
<span class="rank">{t("Leaderboard.Rank")}</span>
<span class="name"></span>
<span class="rating">{t("Leaderboard.Rating")}</span>
@@ -82,7 +46,7 @@
<span class="fc">{t("Leaderboard.FC")}</span>
<span class="ap">{t("Leaderboard.AP")}</span>
</div>
{#each paginatedUsers as user, i (user.rank)}
{#each d.users as user, i (user.rank)}
<div class="lb-user" class:alternate={i % 2 === 1} role="listitem"
on:mouseover={() => hoveringUser = user.username} on:focus={() => {}}>
@@ -106,8 +70,6 @@
{/each}
</div>
<Pagination {page} {totalPages} on:updatePage={handleUpdatePage} />
<Tooltip triggeredBy=".name" loading={hoverLoading}>
<UserCard username={hoveringUser} {game} setLoading={l => hoverLoading = l} />
</Tooltip>
@@ -170,4 +132,5 @@
&.alternate
background-color: vars.$ov-light
</style>

View File

@@ -23,7 +23,7 @@
let error: string;
let submitting = ""
let tab = 0
let tabs = ['profile']
let tabs = [ 'profile', 'game' ]
const profileFields = [
[ 'displayName', t('settings.profile.name') ],
@@ -45,11 +45,18 @@
me = m
CARD.userGames(m.username).then(games => {
tabs = [
...tabs,
...['chu3', 'mai2','wacca', 'ongeki'].filter(v => games[v as keyof typeof games]), // :xdx:
'global'
]
if (games.chu3 && !tabs.includes('chu3')) {
tabs = [...tabs, 'chu3']
}
if (games.mai2 && !tabs.includes('mai2')) {
tabs = [...tabs, 'mai2']
}
if (games.wacca && !tabs.includes('wacca')) {
tabs = [...tabs, 'wacca']
}
if (games.ongeki && !tabs.includes('ongeki')) {
tabs = [...tabs, 'ongeki']
}
})
}).catch(e => error = e.message)
getMe()
@@ -210,7 +217,7 @@
<WaccaSettings />
{:else if tabs[tab] === 'ongeki'}
<OngekiSettings />
{:else if tabs[tab] === 'global'}
{:else if tabs[tab] === 'game'}
<GeneralGameSettings />
{/if}

View File

@@ -5,6 +5,7 @@
import Icon from "@iconify/svelte";
import { USER } from "../libs/sdk";
import { t } from "../libs/i18n"
import MunetRegisterBanner from "../components/MunetRegisterBanner.svelte";
let params = new URLSearchParams(window.location.search)
@@ -99,6 +100,9 @@
state = 'verify'
verifyMsg = t("welcome.verify-state-2")
}
else if (e.message === 'Login not allowed: Card has been migrated to Minato.') {
location.href = `https://portal.mumur.net/login?username=${encodeURIComponent(email)}`
}
else {
error = e.message
submitting = false // unnecessary? see line 113, same for both reset functions
@@ -158,7 +162,7 @@
return submitting = false
}
// Send request to server
// Send request to server
await USER.changePassword({ token, password })
.then(() => {
state = 'verify'
@@ -217,6 +221,9 @@
on:turnstile-expired={_ => window.location.reload()}
on:turnstile-timeout={_ => console.log(error = t('welcome.turnstile-timeout'))} />
{/if}
{#if isSignup}
<MunetRegisterBanner username={username} email={email}/>
{/if}
</div>
{:else if state === "submitreset"}
<div class="login-form" transition:slide>
@@ -257,7 +264,7 @@
{#if error}
<span class="error">{error}</span>
{/if}
<div class="login-form" transition:slide>
<div class="login-form" transition:slide>
<input type="password" placeholder={t('new-password')} bind:value={password}>
<button on:click={changePassword}>
{#if submitting}

View File

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

View File

@@ -15,7 +15,6 @@ import kotlinx.coroutines.withContext
import org.apache.tika.Tika
import org.apache.tika.mime.MimeTypes
import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationContext
import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity.BodyBuilder
@@ -35,10 +34,8 @@ import java.util.concurrent.locks.Lock
import kotlin.reflect.KCallable
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.jvmErasure
typealias RP = RequestParam
@@ -83,9 +80,7 @@ annotation class SettingField(
// Reflection
@Suppress("UNCHECKED_CAST")
fun <T : Any> KClass<T>.ownVars() = declaredMemberProperties.sortedBy { it.javaField?.declaringClass?.declaredFields?.indexOf(it.javaField) ?: Int.MAX_VALUE }.mapNotNull { it as? Var<T, Any> }
@Suppress("UNCHECKED_CAST")
fun <T : Any> KClass<T>.vars(): List<Var<T, Any>> = supertypes.mapNotNull { it.classifier as? KClass<*> }.filter { !it.java.isInterface }.flatMap{ it.vars() as List<Var<T, Any>> } + ownVars()
fun <T : Any> KClass<T>.vars() = memberProperties.mapNotNull { it as? Var<T, Any> }
fun <T : Any> KClass<T>.varsMap() = vars().associateBy { it.name }
fun <T : Any> KClass<T>.getters() = java.methods.filter { it.name.startsWith("get") }
fun <T : Any> KClass<T>.gettersMap() = getters().associateBy { it.name.removePrefix("get").firstCharLower() }
@@ -217,6 +212,8 @@ val <K, V> Map<K, V>.mut get() = toMutableMap()
val <T> Set<T>.mut get() = toMutableSet()
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
operator fun <T> Optional<T>.invoke(): T? = orElse(null)
@@ -233,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
val Str.some get() = ifBlank { null }
val ByteArray.hexStr get() = toHexString()
operator fun StringBuilder.plusAssign(other: String) { this.append(other) }
// Coroutine
suspend fun <T> async(block: suspend kotlinx.coroutines.CoroutineScope.() -> T): T = withContext(Dispatchers.IO) { block() }
@@ -261,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
val empty = emptyList<Any>()
val emptyMap = emptyMap<Any, Any>()
val <F> Pair<F, *>.l get() = component1()
val <S> Pair<*, S>.r get() = component2()
@@ -269,6 +268,3 @@ val <S> Pair<*, S>.r get() = component2()
val Query.exec get() = resultList.map { (it as Array<*>).toList() }
fun List<List<Any?>>.numCsv(vararg head: Str) = head.joinToString(",") + "\n" +
joinToString("\n") { it.joinToString(",") }
// DI
inline fun <reified T> ApplicationContext.lazy() = lazy { getBean(T::class.java) }

View File

@@ -21,15 +21,15 @@ val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, obj
else -> 400 - "Invalid boolean value ${parser.text}"
}
})
val JSON_DATETIME = SimpleModule().addDeserializer(java.time.LocalDateTime::class.java, object : JsonDeserializer<LocalDateTime>() {
val JSON_DATETIME = SimpleModule().addDeserializer(java.time.LocalDateTime::class.java, object : JsonDeserializer<java.time.LocalDateTime>() {
override fun deserialize(parser: JsonParser, context: DeserializationContext) =
// First try standard formats via asDateTime() method
parser.text.takeIf { it.isNotEmpty() }?.run { asDateTime() ?: try {
parser.text.asDateTime() ?: try {
// Try maimai2 format (yyyy-MM-dd HH:mm:ss.0)
LocalDateTime.parse(parser.text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0"))
} catch (e: Exception) {
400 - "Invalid date time value ${parser.text}"
} }
}
})
val JACKSON = jacksonObjectMapper().apply {
setSerializationInclusion(JsonInclude.Include.NON_NULL)

View File

@@ -4,6 +4,7 @@ import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.utils.SUCCESS
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.sensitiveInfo
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
@@ -52,7 +53,7 @@ class BotController(
secret.checkSecret()
// 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
us.cardRepo.save(oc.apply {
@@ -66,7 +67,7 @@ class BotController(
fun clearMigrateFlag(@RP secret: Str, @RP card: Str): Any {
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 {
status = CardStatus.NORMAL
@@ -82,14 +83,14 @@ class BotController(
secret.checkSecret()
// 1. Check if the card exist
var cards = listOfNotNull(
us.cardRepo.findByLuid(cardId)(),
var cards: MutableList<Card> = listOfNotNull(
us.cardRepo.findByLuid(cardId),
).mut
cardId.toLongOrNull()?.let {
cards += listOfNotNull(
us.cardRepo.findById(it)(),
us.cardRepo.findByExtId(it)(),
us.cardRepo.findByExtId(it),
)
cards += listOfNotNull(
@@ -110,8 +111,8 @@ class BotController(
return cards.map { card ->
// Find all games played by this card
val chu3 = chu3Db.userData.findByCard_ExtId(card.extId)()
val mai2 = mai2Db.userData.findByCard_ExtId(card.extId)()
val chu3 = chu3Db.userData.findByCard_ExtId(card.extId)
val mai2 = mai2Db.userData.findByCard_ExtId(card.extId)
val gamesDict = listOfNotNull(chu3, mai2).map {
// Find the keychip owner
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.SUCCESS
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.model.Card
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.web.bind.annotation.RestController
import java.time.LocalDateTime
import kotlin.jvm.optionals.getOrNull
import kotlin.random.Random
@RestController
@@ -101,7 +101,8 @@ class CardController(
val games = migrate.split(',')
cardGameService.migrate(card, games)
fedy.onCardLinked(card.luid, oldExtId = card.extId, ghostExtId = u.ghostCard.extId, 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()}")
@@ -203,11 +204,10 @@ class CardGameService(
val chusan: Chu3UserDataRepo,
val wacca: WcUserRepo,
val ongeki: OgkUserDataRepo,
val diva: icu.samnyan.aqua.sega.diva.dao.userdata.PlayerProfileRepository,
val diva: PlayerProfileRepository,
val safety: AquaNetSafetyService,
val cardRepo: CardRepository,
val em: EntityManager,
val cardService: CardService
val em: EntityManager
) {
companion object {
val log = logger()
@@ -225,9 +225,7 @@ class CardGameService(
val remainingGames = dataRepos.keys.toMutableSet()
games.forEach { game ->
val dataRepo = dataRepos[game] ?: return@forEach
if (migrateCard(game, dataRepo, cardRepo, crd))
// Update timestamp for the ghost card (data migrated in)
cardService.updateCardTimestamp(crd.aquaUser!!.ghostCard, game, resetCreatedAt = true)
migrateCard(game, dataRepo, cardRepo, crd)
remainingGames.remove(game)
}
// For remaining games, orphan the data by assigning them to a dummy card
@@ -240,7 +238,7 @@ class CardGameService(
"chu3" to getSummaryFor(chusan, card),
"ongeki" to getSummaryFor(ongeki, card),
"wacca" to getSummaryFor(wacca, card),
"diva" to diva.findByPdId(card.extId).getOrNull()?.let {
"diva" to diva.findByPdId(card.extId)()?.let {
mapOf(
"name" to it.playerName,
"rating" to it.level,

View File

@@ -2,39 +2,36 @@ package icu.samnyan.aqua.net
import ext.*
import icu.samnyan.aqua.net.components.EmailProperties
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.utils.SUCCESS
import icu.samnyan.aqua.net.components.JWT
import icu.samnyan.aqua.net.db.AquaGameOptions
import icu.samnyan.aqua.net.db.AquaNetUser
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.sega.maimai2.handler.UploadUserPlaylogHandler as Mai2UploadUserPlaylogHandler
import icu.samnyan.aqua.sega.maimai2.handler.UpsertUserAllHandler as Mai2UpsertUserAllHandler
import icu.samnyan.aqua.net.utils.ApiException
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.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.context.ApplicationContext
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.time.Instant
import java.security.MessageDigest
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
@ConfigurationProperties(prefix = "aqua-net.fedy")
@@ -44,11 +41,11 @@ class FedyProps {
var remote: String = ""
}
data class UserProfilePicture(val url: Str, val updatedAtMs: Long)
data class UserProfilePicture(val url: Str, val lastUpdatedMs: Long)
data class UserBasicInfo(
val auId: Long, val ghostExtId: Long, val registrationTimeMs: Long,
val username: Str, val displayName: Str, val email: Str, val passwordHash: Str, val profileBio: Str,
val profilePicture: UserProfilePicture?, val gameOptions: Map<Str, Any?>?,
val profilePicture: UserProfilePicture?,
)
private data class UserUpdatedEvent(val user: UserBasicInfo, val isNewlyCreated: Bool)
@@ -69,23 +66,21 @@ private data class FedyEvent(
@API("/api/v2/fedy", consumes = ["multipart/form-data"])
class Fedy(
val jwt: JWT,
val us: AquaUserServices,
val emailProps: EmailProperties,
val cardRepo: CardRepository,
val cardService: CardService,
val mai2Import: Mai2Import,
val mai2UserDataRepo: Mai2UserDataRepo,
val mai2UploadUserPlaylog: Mai2UploadUserPlaylogHandler,
val mai2UpsertUserAll: Mai2UpsertUserAllHandler,
val chu3UserDataRepo: Chu3UserDataRepo,
val ongekiUserDataRepo: OgkUserDataRepo,
val waccaUserDataRepo: WcUserRepo,
val props: FedyProps,
val paths: PathProps,
val transactionManager: PlatformTransactionManager,
ctx: ApplicationContext
val transactionManager: PlatformTransactionManager
) {
val us by ctx.lazy<AquaUserServices>()
val cardService by ctx.lazy<CardService>()
val mai2Import by ctx.lazy<Mai2Import>()
val mai2UploadUserPlaylog by ctx.lazy<Mai2UploadUserPlaylogHandler>()
val mai2UpsertUserAll by ctx.lazy<Mai2UpsertUserAllHandler>()
val transaction by lazy { TransactionTemplate(transactionManager) }
private fun Str.checkKey() {
@@ -132,7 +127,7 @@ class Fedy(
} caught { UserRegisterRes(error = it) }
}
data class UserUpdateReq(val auId: Long, val fields: Map<Str, Str?>?, val gameOptions: Map<Str, Any?>?)
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) {
@@ -143,16 +138,12 @@ class Fedy(
if (k == "email") { ru.email = us.validateEmail(v) }
else us.update(ru, k, v)
}
pfpFile?.apply {
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
}
req.gameOptions?.apply {
val options = ru.gameOptions ?: AquaGameOptions().also { ru.gameOptions = it }
forEach { (k, v) -> v?.let { GAME_OPTIONS_FIELDS[k]?.set(options, it) } }
}
us.userRepo.save(ru)
if (fields.containsKey("pwHash") ?: false) { us.clearAllSessions(ru) }
UserUpdateRes(user = ru.fedyBasicInfo())
@@ -166,44 +157,36 @@ class Fedy(
?.let { paths.aquaNetPortrait.path() / it }?.takeIf { it.isRegularFile() }
?.let { UserProfilePicture(
url = "/uploads/net/portrait/${profilePicture}",
updatedAtMs = it.getLastModifiedTime().toMillis()
) },
gameOptions?.let { o -> GAME_OPTIONS_FIELDS.mapValues { it.value.get(o) } }
lastUpdatedMs = it.getLastModifiedTime().toMillis()
) }
)
data class DataPullReq(val extId: Long, val game: Str, val createdAtMs: Long, val updatedAtMs: Long, val exportOptions: ExportOptions)
data class DataPullResult(val data: Any?, val createdAtMs: Long, val updatedAtMs: Long, val isRebased: Bool)
data class DataPullRes(val error: FedyErr? = null, val result: DataPullResult? = null)
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).orElse(null)
val card = cardRepo.findByExtId(req.extId)
?: (404 - "Card with extId ${req.extId} not found")
val cardTimestamp = cardService.getCardTimestamp(card, req.game)
if (cardTimestamp.updatedAt.toEpochMilli() == req.updatedAtMs) return@handleFedy DataPullRes(error = null, result = null) // No changes
val isRebased = req.createdAtMs > 0 && cardTimestamp.createdAt.toEpochMilli() > req.createdAtMs
val exportOptions = if (!isRebased) { req.exportOptions } else { req.exportOptions.copy(playlogAfter = null) }
{
DataPullRes(result = DataPullResult(data = when (req.game) {
"mai2" -> mai2Import.export(card, exportOptions)
DataPullRes(result = when (req.game) {
"mai2" -> mai2Import.export(card, req.exportOptions)
else -> 406 - "Unsupported game"
}, createdAtMs = cardTimestamp.createdAt.toEpochMilli(), updatedAtMs = cardTimestamp.updatedAt.toEpochMilli(), isRebased = isRebased))
})
} caught { DataPullRes(error = it) }
}
data class DataPushReq(val extId: Long, val game: Str, val data: JDict, val removeOldData: Bool, val updatedAtMs: Long)
data class DataPushReq(val extId: Long, val game: Str, val data: JDict, val removeOldData: Bool)
@Suppress("UNCHECKED_CAST")
@API("/data/push")
fun handleDataPush(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: DataPushReq): Any = handleFedy(key) {
val extId = req.extId
fun<UserData : IUserData, UserRepo : GenericUserDataRepo<UserData>> removeOldData(repo: UserRepo) {
val oldData = repo.findByCard_ExtId(extId)
if (oldData.isPresent) {
repo.findByCard_ExtId(extId)?.let { oldData ->
log.info("Fedy: Deleting old data for $extId (${req.game})")
repo.delete(oldData.get());
repo.delete(oldData);
repo.flush()
}
}
val card = cardRepo.findByExtId(extId).orElse(null) ?: (404 - "Card not found")
transaction.execute { when (req.game) {
"mai2" -> {
if (req.removeOldData) { removeOldData(mai2UserDataRepo) }
@@ -214,7 +197,7 @@ class Fedy(
}
else -> 406 - "Unsupported game"
} }
cardService.updateCardTimestamp(card, req.game, now = Instant.ofEpochMilli(req.updatedAtMs), resetCreatedAt = req.removeOldData)
SUCCESS
}
@@ -237,9 +220,9 @@ class Fedy(
var pairedCard = cardService.tryLookup(req.pairedLuid)?.maybeGhost()
if (pairedCard?.extId != card?.extId) {
var isGhost = pairedCard?.isGhost == true
var isNonFresh = pairedCard != null && !isCardFresh(pairedCard)
if (isGhost || isNonFresh) isPairedLuidDiverged = true
else if (card?.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) }
@@ -292,16 +275,17 @@ class Fedy(
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).orElse(null) ?: return@maybeNotifyAsync null // Card not found, nothing to do
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(getEvent: () -> FedyEvent?) = if (!props.enabled || suppressEvents.get()) {} else CompletableFuture.runAsync {
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()
@@ -341,7 +325,7 @@ 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
fun <T : IUserData> checkForGame(repo: GenericUserDataRepo<T>, card: Card): Bool = repo.findByCard(card) == null
return when {
checkForGame(mai2UserDataRepo, c) -> false
checkForGame(chu3UserDataRepo, c) -> false
@@ -360,12 +344,18 @@ class Fedy(
const val KEY_HEADER = "X-Fedy-Key"
const val REQ_PART = "request"
const val PFP_PART = "profilePicture"
@Suppress("UNCHECKED_CAST")
val GAME_OPTIONS_FIELDS = listOf(
O::mai2UnlockMusic, O::mai2UnlockChara, O::mai2UnlockCharaMaxLevel, O::mai2UnlockPartners, O::mai2UnlockCollectables, O::mai2UnlockTickets
).map { it as Var<O, Any?> }.associateBy { it.name }
val log = logger()
fun getGameName(gameId: Str) = when (gameId) {
"mai2" -> "mai2"
"SDEZ" -> "mai2"
"chu3" -> "chu3"
"SDHD" -> "chu3"
"ongeki" -> "mu3"
"SDDT" -> "mu3"
"wacca" -> "wacca"
"SDFE" -> "wacca"
else -> null // Not supported
}
}
}
typealias O = AquaGameOptions

View File

@@ -33,7 +33,7 @@ class Frontier(
if (accessCode.length != 20) 400 - "Invalid access code"
// 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) }

View File

@@ -1,7 +1,6 @@
package icu.samnyan.aqua.net
import ext.JACKSON
import ext.invoke
import ext.logger
import ext.parse
import icu.samnyan.aqua.net.db.AquaNetUserRepo
@@ -24,7 +23,7 @@ class Migrations(
@PostConstruct
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 old = p.size
@@ -47,7 +46,7 @@ class Migrations(
if (c.extId > max) {
var new = c.extId and max
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")
new++
}

View File

@@ -14,8 +14,7 @@ import kotlin.reflect.jvm.jvmErasure
class SettingsApi(
val us: AquaUserServices,
val userRepo: AquaNetUserRepo,
val goRepo: AquaGameOptionsRepo,
val fedy: Fedy
val goRepo: AquaGameOptionsRepo
) {
// Get all params with SettingField annotation
val fields = AquaGameOptions::class.vars()
@@ -42,6 +41,6 @@ class SettingsApi(
}
// Check field type
field.setCast(options, value)
goRepo.save(options).also { fedy.onUserUpdated(u) }
goRepo.save(options)
}
}

View File

@@ -2,12 +2,18 @@ package icu.samnyan.aqua.net
import ext.*
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
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.SUCCESS
import icu.samnyan.aqua.sega.general.dao.CardRepository
import icu.samnyan.aqua.sega.general.model.CardStatus
import jakarta.servlet.http.HttpServletRequest
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.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
@@ -21,7 +27,6 @@ class UserRegistrar(
val hasher: PasswordEncoder,
val turnstileService: TurnstileService,
val emailService: EmailService,
val fedy: Fedy,
val geoIP: GeoIP,
val jwt: JWT,
val confirmationRepo: EmailConfirmationRepo,
@@ -31,6 +36,7 @@ class UserRegistrar(
val emailProps: EmailProperties,
final val paths: PathProps
) {
@Autowired @Lazy lateinit var fedy: Fedy
val portraitPath = paths.aquaNetPortrait.path()
companion object {
@@ -86,6 +92,8 @@ class UserRegistrar(
?: (400 - "User not found")
if (!hasher.matches(password, user.pwHash)) 400 - "Invalid password"
if (user.ghostCard.status == CardStatus.MIGRATED_TO_MINATO) 400 - "Login not allowed: Card has been migrated to Minato."
// Check if email is verified
if (!user.emailConfirmed && emailProps.enable) {
// Check if last confirmation email was sent within a minute

View File

@@ -3,11 +3,7 @@ package icu.samnyan.aqua.net.components
import ext.Bool
import ext.Str
import ext.logger
import icu.samnyan.aqua.net.db.AquaNetUser
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 icu.samnyan.aqua.net.db.*
import org.simplejavamail.api.mailer.Mailer
import org.simplejavamail.email.EmailBuilder
import org.simplejavamail.springsupport.SimpleJavaMailSpringSupport

View File

@@ -2,11 +2,7 @@ package icu.samnyan.aqua.net.components
import ext.Str
import ext.minus
import icu.samnyan.aqua.net.db.AquaNetUser
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 icu.samnyan.aqua.net.db.*
import io.jsonwebtoken.JwtParser
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.security.Keys

View File

@@ -2,7 +2,10 @@ package icu.samnyan.aqua.net.db
import com.fasterxml.jackson.annotation.JsonIgnore
import ext.SettingField
import jakarta.persistence.*
import jakarta.persistence.Entity
import jakarta.persistence.GeneratedValue
import jakarta.persistence.GenerationType
import jakarta.persistence.Id
import org.springframework.data.jpa.repository.JpaRepository
@Entity
@@ -11,29 +14,21 @@ class AquaGameOptions(
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
@SettingField("mai2") @Column(name = "mai2_unlock_music")
var mai2UnlockMusic: Boolean = false,
@SettingField("mai2") @Column(name = "mai2_unlock_chara")
var mai2UnlockChara: Boolean = false,
@SettingField("mai2") @Column(name = "mai2_unlock_chara_max_level")
var mai2UnlockCharaMaxLevel: Boolean = false,
@SettingField("mai2") @Column(name = "mai2_unlock_partners")
var mai2UnlockPartners: Boolean = false,
@SettingField("mai2") @Column(name = "mai2_unlock_collectables")
var mai2UnlockCollectables: Boolean = false,
@SettingField("mai2") @Column(name = "mai2_unlock_tickets")
var mai2UnlockTickets: Boolean = false,
@SettingField("general")
var unlockMusic: Boolean = false,
@SettingField("general")
var unlockChara: Boolean = false,
@SettingField("general")
var unlockCollectables: Boolean = false,
@SettingField("general")
var unlockTickets: Boolean = false,
@SettingField("wacca")
var waccaUnlockMusic: Boolean = false,
@SettingField("wacca")
var waccaUnlockPlates: Boolean = false,
@SettingField("wacca")
var waccaUnlockCollectables: Boolean = false,
@SettingField("wacca")
var waccaUnlockTickets: Boolean = false,
@SettingField("wacca")
var waccaInfiniteWp: Boolean = false,
@SettingField("wacca")
var waccaAlwaysVip: Boolean = false,

View File

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

View File

@@ -184,7 +184,7 @@ class AquaUserServices(
suspend fun cardByName(username: Str) =
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 }
suspend fun <T> cardByName(username: Str, callback: suspend (Card) -> T) = callback(cardByName(username))

View File

@@ -5,7 +5,7 @@ import icu.samnyan.aqua.net.BotProps
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.service.CardService
import icu.samnyan.aqua.sega.general.model.CardStatus
import jakarta.annotation.PostConstruct
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
@@ -27,12 +27,9 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
abstract val playlogRepo: GenericPlaylogRepo<*>
abstract val userMusicRepo: GenericUserMusicRepo<*>
abstract val shownRanks: List<Pair<Int, String>>
abstract val settableFields: Map<String, (T, String) -> Unit>
open val gettableFields: Set<String> = setOf()
@Autowired lateinit var cardService: CardService
@API("trend")
abstract suspend fun trend(@RP username: String): List<TrendOut>
@API("user-summary")
@@ -59,7 +56,7 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
val reqUser = token?.let { us.jwt.auth(it) }?.let { u ->
// Optimization: If the user is not banned, we don't need to process user information
if (!u.ghostCard.rankingBanned && !u.cards.any { it.rankingBanned } && u.ghostCard.status.isNormal) null
if (!u.ghostCard.rankingBanned && !u.cards.any { it.rankingBanned } && u.ghostCard.status == CardStatus.NORMAL) null
else u
}
@@ -121,17 +118,19 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
}
@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 ->
(settableFields.keys.toSet() + gettableFields)
.associateWith { k -> (vm[k] ?: error("Field $k not found")) }
} }
@API("user-detail")
suspend fun userDetail(@RP username: String) = us.cardByName(username) { card ->
val u = userDataRepo.findByCard(card) ?: (404 - "User not found")
userDetailFields.toList().associate { (k, f) -> k to f.invoke(u) }
}
@API("user-detail-set")
suspend fun userDetailSet(@RP token: String, @RP field: String, @RP value: String): Any {
val prop = settableFields[field] ?: (400 - "Invalid field $field")
@@ -140,16 +139,10 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
val user = async { userDataRepo.findByCard(u.ghostCard) } ?: (404 - "User not found")
prop(user, value)
async { userDataRepo.save(user) }
cardService.updateCardTimestamp(u.ghostCard, name)
SUCCESS
}
}
@API("user-option")
open suspend fun userOption(@RP token: String): Any? = 400 - "Unsupported by this game"
@API("user-option-set")
open suspend fun userOptionSet(@RP token: String, @RP field: String, @RP value: Int): Any = 400 - "Unsupported by this game"
@API("user-music-from-list")
suspend fun userMusicFromList(@RP username: Str, @RB musicList: List<Int>) = us.cardByName(username) { card ->
userMusicRepo.findByUser_Card_ExtIdAndMusicIdIn(card.extId, musicList)

View File

@@ -1,20 +1,19 @@
package icu.samnyan.aqua.net.games
import ext.*
import icu.samnyan.aqua.net.Fedy
import icu.samnyan.aqua.net.db.AquaNetUser
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.SUCCESS
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.service.CardService
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Lazy
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.repository.NoRepositoryBean
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.support.TransactionTemplate
import java.time.LocalDateTime
import java.util.*
import kotlin.io.path.Path
import kotlin.io.path.writeText
import kotlin.reflect.KClass
@@ -43,14 +42,13 @@ interface IExportClass<UserModel: IUserData> {
@NoRepositoryBean
interface IUserRepo<UserModel, ThisModel>: JpaRepository<ThisModel, Long> {
fun findByUser(user: UserModel): List<ThisModel>
fun findSingleByUser(user: UserModel): Optional<ThisModel>
fun findSingleByUser(user: UserModel): ThisModel?
}
/**
* Import controller for a game
*
* @param game: 4-letter Game ID
* @param gameName: mai2/chu3/ongeki
* @param exportFields: Mapping of type names to variables in the export model
* (e.g. "Mai2UserCharacter" -> Mai2DataExport::userCharacterList)
* @param exportRepos: Mapping of variables to repositories that can be used to find the data
@@ -58,7 +56,6 @@ interface IUserRepo<UserModel, ThisModel>: JpaRepository<ThisModel, Long> {
*/
abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel: IUserData>(
val game: String,
val gameName: String,
val exportClass: KClass<ExportModel>,
val exportFields: Map<String, Var<ExportModel, Any>>,
val exportRepos: Map<Var<ExportModel, Any>, IUserRepo<UserModel, *>>,
@@ -73,7 +70,7 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
@Autowired lateinit var netProps: AquaNetProps
@Autowired lateinit var transManager: PlatformTransactionManager
val trans by lazy { TransactionTemplate(transManager) }
@Autowired lateinit var cardService: CardService
@Autowired @Lazy lateinit var fedy: Fedy
init {
artemisRenames.values.forEach {
@@ -91,7 +88,7 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
userData = userDataRepo.findByCard(c) ?: (404 - "User not found")
exportRepos.forEach { (f, u) ->
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) ->
exporter(userData, options)?.let { f.set(this, it) }
@@ -150,7 +147,7 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
}
}
cardService.updateCardTimestamp(u.ghostCard, gameName, resetCreatedAt = true)
Fedy.getGameName(game)?.let { fedy.onDataUpdated(u.ghostCard.extId, it, true) }
SUCCESS
}

View File

@@ -125,7 +125,7 @@ open class BaseEntity(
@NoRepositoryBean
interface GenericUserDataRepo<T : IUserData> : JpaRepository<T, Long> {
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")
fun findAllNonBanned(): List<T>

View File

@@ -17,7 +17,7 @@ import kotlin.reflect.full.declaredMembers
class Chu3Import(
val repos: Chu3Repos,
) : ImportController<Chu3DataExport, Chu3UserData>(
"SDHD", "chu3", Chu3DataExport::class,
"SDHD", Chu3DataExport::class,
exportFields = Chu3DataExport::class.vars().associateBy {
it.name.replace("List", "").lowercase()
},

View File

@@ -3,15 +3,13 @@ package icu.samnyan.aqua.net.games.chu3
import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.*
import icu.samnyan.aqua.net.utils.*
import icu.samnyan.aqua.sega.chusan.model.*
import icu.samnyan.aqua.net.utils.chu3Scores
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.UserGameOption
import org.springframework.web.bind.annotation.RestController
import kotlin.jvm.optionals.getOrNull
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.memberProperties
@RestController
@API("api/v2/game/chu3")
@@ -38,7 +36,6 @@ class Chusan(
"trophyIdSub2" to { u, v -> u.trophyIdSub2 = v.int },
"mapIconId" to { u, v -> u.mapIconId = v.int },
"voiceId" to { u, v -> u.voiceId = v.int },
"characterId" to { u, v -> u.characterId = v.int },
"avatarWear" to { u, v -> u.avatarWear = v.int },
"avatarHead" to { u, v -> u.avatarHead = v.int },
"avatarFace" to { u, v -> u.avatarFace = v.int },
@@ -50,7 +47,7 @@ class Chusan(
"lastRomVersion" to { u, v -> u.lastRomVersion = v },
"lastDataVersion" to { u, v -> u.lastDataVersion = v },
) }
override val gettableFields: Set<String> = setOf("level", "playerRating")
override val gettableFields: Set<String> = setOf("level", "playerRating", "characterId")
override suspend fun userSummary(@RP username: Str, @RP token: String?) = us.cardByName(username) { card ->
// Summary values: total plays, player rating, server-wide ranking
@@ -100,23 +97,6 @@ class Chusan(
)
}
@API("user-option")
override suspend fun userOption(@RP token: String): Any? = us.jwt.auth(token) { u ->
rp.userGameOption.findByUser_Card_ExtId(u.ghostCard.extId).getOrNull(0)
}
@API("user-option-set")
override suspend fun userOptionSet(@RP token: String, @RP field: String, @RP value: Int): Any = us.jwt.auth(token) { u ->
val gameOptions = rp.userGameOption.findSingleByUser_Card_ExtId(u.ghostCard.extId).getOrNull()
val property = UserGameOption::class.memberProperties.filterIsInstance<KMutableProperty1<Any, Any?>>().find{ it.name == field }
if (property != null && gameOptions != null) {
property.setter.call(gameOptions, value)
rp.userGameOption.save(gameOptions)
200 - "Success"
} else
400 - "Invalid parameters"
}
// UserBox related APIs
@API("user-box")
fun userBox(@RP token: String) = us.jwt.auth(token) {

View File

@@ -14,12 +14,13 @@ import icu.samnyan.aqua.sega.maimai2.model.userdata.*
import org.springframework.web.bind.annotation.RestController
import kotlin.reflect.full.declaredMembers
@Suppress("UNCHECKED_CAST")
@RestController
@API("api/v2/game/mai2")
class Mai2Import(
val repos: Mai2Repos,
) : ImportController<Maimai2DataExport, Mai2UserDetail>(
"SDEZ", "mai2", Maimai2DataExport::class,
"SDEZ", Maimai2DataExport::class,
exportFields = Maimai2DataExport::class.vars().associateBy {
it.name.replace("List", "").lowercase()
},
@@ -61,7 +62,7 @@ class Mai2Import(
}
},
Maimai2DataExport::userFavoriteMusicList to { user: Mai2UserDetail, _: ExportOptions ->
repos.userGeneralData.findByUserAndPropertyKey(user, "favorite_music").orElse(null)
repos.userGeneralData.findByUserAndPropertyKey(user, "favorite_music")
?.propertyValue
?.takeIf { it.isNotEmpty() }
?.split(",")
@@ -78,7 +79,7 @@ class Mai2Import(
if (favoriteMusicList.isNotEmpty()) {
val key = "favorite_music"
// 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 }
repos.userGeneralData.save(data.apply {
propertyValue = favoriteMusicList.sortedBy { it.orderId }.map { it.id }.joinToString(",")

View File

@@ -1,9 +1,11 @@
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.utils.SUCCESS
import icu.samnyan.aqua.sega.general.service.CardService
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserMusicDetail
import org.springframework.web.bind.annotation.PostMapping
@@ -14,34 +16,26 @@ import org.springframework.web.bind.annotation.RestController
class Mai2MusicDetailImport(
val us: AquaUserServices,
val repos: Mai2Repos,
val cardService: CardService,
) {
@PostMapping("import-music-detail")
suspend fun importMusicDetail(@RP token: String, @RB data: List<Mai2UserMusicDetail>) = us.jwt.auth(token) { u ->
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 ->
val musicRec = repos.userMusicDetail.findByUserAndMusicIdAndLevel(user, newMusic.musicId, newMusic.level)
if (musicRec.isPresent) {
val music = musicRec.get()
newMusic.user = user
repos.userMusicDetail.findByUserAndMusicIdAndLevel(user, newMusic.musicId, newMusic.level)?.let { m ->
newMusic.apply {
id = music.id
this.user = user
achievement = achievement.coerceAtLeast(music.achievement)
scoreRank = scoreRank.coerceAtLeast(music.scoreRank)
comboStatus = comboStatus.coerceAtLeast(music.comboStatus)
syncStatus = syncStatus.coerceAtLeast(music.syncStatus)
deluxscoreMax = deluxscoreMax.coerceAtLeast(music.deluxscoreMax)
playCount = playCount.coerceAtLeast(music.playCount)
}
} else {
newMusic.apply {
this.user = user
id = m.id
achievement = achievement.coerceAtLeast(m.achievement)
scoreRank = scoreRank.coerceAtLeast(m.scoreRank)
comboStatus = comboStatus.coerceAtLeast(m.comboStatus)
syncStatus = syncStatus.coerceAtLeast(m.syncStatus)
deluxscoreMax = deluxscoreMax.coerceAtLeast(m.deluxscoreMax)
playCount = playCount.coerceAtLeast(m.playCount)
}
}
}
repos.userMusicDetail.saveAll(data)
cardService.updateCardTimestamp(card, "mai2")
SUCCESS
}
}

View File

@@ -3,17 +3,19 @@ package icu.samnyan.aqua.net.games.mai2
import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices
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.model.*
import icu.samnyan.aqua.sega.maimai2.model.userdata.*
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
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.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController
import java.util.*
import kotlin.jvm.optionals.getOrNull
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.memberProperties
@RestController
@API("api/v2/game/mai2")
@@ -61,9 +63,9 @@ class Maimai2(
us.jwt.auth(t) { u ->
if (u.username == username) return@auth null
us.cardByName(u.username) { myCard ->
val user = repos.userData.findByCardExtId(card.extId).orElse(null) ?: (404 - "User not found")
val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival")
.map { it.propertyValue.split(',') }.orElse(emptyList()).filter { it.isNotEmpty() }.map { it.long() }
val user = repos.userData.findByCardExtId(card.extId) ?: (404 - "User not found")
val myRival = (repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival")?.propertyValue?.split(',') ?: emptyList())
.filter { it.isNotEmpty() }.map { it.long() }
myRival.contains(user.id)
}
}
@@ -113,7 +115,6 @@ class Maimai2(
val user = userDataRepo.findByCard(card) ?: (404 - "User not found")
user.userName = newNameFull
userDataRepo.save(user)
cardService.updateCardTimestamp(card, "mai2")
}
mapOf("newName" to newNameFull)
}
@@ -136,36 +137,17 @@ class Maimai2(
if (loginBonus.none { it.bonusId == bonusId }) {
// create one
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
isCurrent = true
}
loginBonus.add(newBonus)
}
repos.userLoginBonus.saveAll(loginBonus)
cardService.updateCardTimestamp(card, "mai2")
}
SUCCESS
}
@API("user-option")
override suspend fun userOption(@RP token: String) = us.jwt.auth(token) { u ->
repos.userOption.findByUser_Card_ExtId(u.ghostCard.extId).getOrNull(0)
}
@API("user-option-set")
override suspend fun userOptionSet(@RP token: String, @RP field: String, @RP value: Int): Any = us.jwt.auth(token) { u ->
val gameOptions = repos.userOption.findSingleByUser_Card_ExtId(u.ghostCard.extId).getOrNull()
val property = Mai2UserOption::class.memberProperties.filterIsInstance<KMutableProperty1<Any, Any?>>().find{ it.name == field }
if (property != null && gameOptions != null) {
property.setter.call(gameOptions, value)
repos.userOption.save(gameOptions)
200 - "Success"
} else
400 - "Invalid parameters"
}
@API("owned-items")
suspend fun ownedItems(@RP token: String) = us.jwt.auth(token) { u ->
us.cardByName(u.username) { card ->
@@ -177,10 +159,10 @@ class Maimai2(
suspend fun setRival(@RP token: String, @RP rivalUserName: String, @RP isAdd: Boolean) = us.jwt.auth(token) { u ->
us.cardByName(u.username) { myCard ->
val rivalCard = us.cardByName(rivalUserName) { it }
val rivalUser = repos.userData.findByCardExtId(rivalCard.extId).orElse(null) ?: (404 - "User not found")
val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival").orElse(null)
val rivalUser = repos.userData.findByCardExtId(rivalCard.extId) ?: (404 - "User not found")
val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival")
?: 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"
}
val myRivalList = myRival.propertyValue.split(',').filter { it.isNotEmpty() }.mut
@@ -195,7 +177,6 @@ class Maimai2(
myRival.propertyValue = myRivalList.joinToString(",")
repos.userGeneralData.save(myRival)
cardService.updateCardTimestamp(myCard, "mai2")
}
SUCCESS
}

View File

@@ -1,23 +1,15 @@
package icu.samnyan.aqua.net.games.ongeki
import ext.API
import ext.RP
import ext.minus
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.*
import icu.samnyan.aqua.net.utils.*
import icu.samnyan.aqua.net.utils.ongekiScores
import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserGeneralDataRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserMusicDetailRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserOptionRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserPlaylogRepo
import icu.samnyan.aqua.sega.ongeki.model.UserData
import icu.samnyan.aqua.sega.ongeki.model.UserOption
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import kotlin.jvm.optionals.getOrNull
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.memberProperties
@RestController
@API("api/v2/game/ongeki")
@@ -26,8 +18,7 @@ class Ongeki(
override val playlogRepo: OgkUserPlaylogRepo,
override val userDataRepo: OgkUserDataRepo,
override val userMusicRepo: OgkUserMusicDetailRepo,
val userGeneralDataRepository: OgkUserGeneralDataRepo,
val userOptionRepo: OgkUserOptionRepo
val userGeneralDataRepository: OgkUserGeneralDataRepo
): GameApiController<UserData>("ongeki", UserData::class) {
override suspend fun trend(username: String) = us.cardByName(username) { card ->
findTrend(playlogRepo.findByUser_Card_ExtId(card.extId)
@@ -54,21 +45,4 @@ class Ongeki(
genericUserSummary(card, ratingComposition)
}
@API("user-option")
override suspend fun userOption(@RP token: String) = us.jwt.auth(token) { u ->
userOptionRepo.findByUser_Card_ExtId(u.ghostCard.extId).getOrNull(0)
}
@API("user-option-set")
override suspend fun userOptionSet(@RP token: String, @RP field: String, @RP value: Int): Any = us.jwt.auth(token) { u ->
val gameOptions = userOptionRepo.findSingleByUser_Card_ExtId(u.ghostCard.extId).getOrNull()
val property = UserOption::class.memberProperties.filterIsInstance<KMutableProperty1<Any, Any?>>().find{ it.name == field }
if (property != null && gameOptions != null) {
property.setter.call(gameOptions, value)
userOptionRepo.save(gameOptions)
200 - "Success"
} else
400 - "Invalid parameters"
}
}

View File

@@ -1,6 +1,9 @@
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.games.*
import icu.samnyan.aqua.net.utils.waccaScores
@@ -34,7 +37,7 @@ class Wacca(
val data = userDataRepo.findByCard_ExtId(card.extId)
genericUserSummary(card, mapOf(), null, if (data.isPresent) data.get().favoriteSongs else null)
genericUserSummary(card, mapOf(), null, data?.favoriteSongs)
}
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.post
import ext.request
import java.net.URI
import icu.samnyan.aqua.sega.aimedb.AimeDbClient
import icu.samnyan.aqua.sega.allnet.AllNetBillingDecoder
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})")

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.ongeki.model.OngekiUpsertUserAll
import icu.samnyan.aqua.sega.ongeki.model.UserItem
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
import icu.samnyan.aqua.sega.util.jackson.IMapper
import icu.samnyan.aqua.sega.util.jackson.StringMapper
import icu.samnyan.aqua.sega.util.BasicMapper
import icu.samnyan.aqua.sega.util.IMapper
import icu.samnyan.aqua.sega.util.StringMapper
abstract class DataBroker(

View File

@@ -1,6 +1,7 @@
package icu.samnyan.aqua.sega.aimedb
import ext.*
import ext.logger
import ext.toHex
import icu.samnyan.aqua.net.Fedy
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.sega.allnet.AllNetProps
@@ -125,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
us.cardRepo.save(card.apply { accessTime = LocalDateTime.now() }).extId
} ?: -1
@@ -196,7 +197,7 @@ class AimeDB(
var status = 0
var aimeId = 0L
if (us.cardRepo.findByLuid(luid).isEmpty) {
if (us.cardRepo.findByLuid(luid) == null) {
val card: Card = cardService.registerByAccessCode(luid)
status = 1

View File

@@ -1,12 +1,30 @@
package icu.samnyan.aqua.sega.aimedb
import icu.samnyan.aqua.sega.util.ByteBufUtil
import io.netty.buffer.ByteBuf
import io.netty.buffer.Unpooled.copiedBuffer
import java.nio.charset.StandardCharsets
import javax.crypto.Cipher
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)
*/
@@ -15,7 +33,6 @@ object AimeDbEncryption {
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) }
fun decrypt(src: ByteBuf) = copiedBuffer(dec.doFinal(ByteBufUtil.toBytes(src)))
fun encrypt(src: ByteBuf) = copiedBuffer(enc.doFinal(ByteBufUtil.toAllBytes(src)))
fun decrypt(src: ByteBuf) = copiedBuffer(dec.doFinal(src.toBytes()))
fun encrypt(src: ByteBuf) = copiedBuffer(enc.doFinal(src.toAllBytes()))
}

View File

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

View File

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

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