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

View File

@@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import { slide, fade } from "svelte/transition"; 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 { t } from "../../libs/i18n.js";
import Icon from "@iconify/svelte"; import Icon from "@iconify/svelte";
import StatusOverlays from "../StatusOverlays.svelte"; import StatusOverlays from "../StatusOverlays.svelte";
@@ -35,169 +35,6 @@
break 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() { function exportData() {
submitting = "export" submitting = "export"
@@ -233,10 +70,6 @@
<Icon icon="bxs:file-export"/> <Icon icon="bxs:file-export"/>
{t('settings.export')} {t('settings.export')}
</button> </button>
<button class="exportBatchManualButton" on:click={exportBatchManual}>
<Icon icon="bxs:file-export"/>
{t('settings.batchManualExport')}
</button>
</div> </div>
<StatusOverlays {error} loading={!values[0] || !!submitting}/> <StatusOverlays {error} loading={!values[0] || !!submitting}/>

View File

@@ -144,35 +144,23 @@ export const EN_REF_HOME = {
export const EN_REF_SETTINGS = { export const EN_REF_SETTINGS = {
'settings.title': 'Settings', 'settings.title': 'Settings',
'settings.tabs.profile': 'Profile', 'settings.tabs.profile': 'Profile',
'settings.tabs.global': 'Global', 'settings.tabs.game': 'Game',
'settings.tabs.chu3': 'Chuni', 'settings.tabs.chu3': 'Chuni',
'settings.tabs.mai2': 'Mai', 'settings.tabs.mai2': 'Mai',
'settings.tabs.ongeki': 'Ongeki', 'settings.tabs.ongeki': 'Ongeki',
'settings.tabs.wacca': 'Wacca', 'settings.tabs.wacca': 'Wacca',
'settings.fields.mai2UnlockMusic.name': 'Unlock All Music', 'settings.fields.unlockMusic.name': 'Unlock All Music',
'settings.fields.mai2UnlockMusic.desc': 'Unlock all music and master difficulty.', 'settings.fields.unlockMusic.desc': 'Unlock all music and master difficulty in game.',
'settings.fields.mai2UnlockChara.name': 'Unlock All Characters', 'settings.fields.unlockChara.name': 'Unlock All Characters',
'settings.fields.mai2UnlockChara.desc': 'Unlock all characters (new characters start at level 1).', 'settings.fields.unlockChara.desc': 'Unlock all characters, voices, and partners in game.',
'settings.fields.mai2UnlockCharaMaxLevel.name': 'Max Character Level', 'settings.fields.unlockCollectables.name': 'Unlock All Collectables',
'settings.fields.mai2UnlockCharaMaxLevel.desc': 'Set all characters to max level.', 'settings.fields.unlockCollectables.desc': 'Unlock all collectables (nameplate, title, icon, frame) in game.',
'settings.fields.mai2UnlockPartners.name': 'Unlock All Partners', 'settings.fields.unlockTickets.name': 'Unlock All Tickets',
'settings.fields.mai2UnlockPartners.desc': 'Unlock all partners.', 'settings.fields.unlockTickets.desc': 'Infinite map/ex tickets (Note: maimai still limits which tickets can be used).',
'settings.fields.mai2UnlockCollectables.name': 'Unlock All Collectables', 'settings.fields.waccaInfiniteWp.name': 'Wacca: Infinite WP',
'settings.fields.mai2UnlockCollectables.desc': 'Unlock all collectables (nameplate, title, icon, frame).', 'settings.fields.waccaInfiniteWp.desc': 'Set WP to 999999',
'settings.fields.mai2UnlockTickets.name': 'Unlock All Tickets', 'settings.fields.waccaAlwaysVip.name': 'Wacca: Always VIP',
'settings.fields.mai2UnlockTickets.desc': 'Infinite tickets (Note: client still limits which tickets can be used).', 'settings.fields.waccaAlwaysVip.desc': 'Set VIP expiration date to 2077-01-01',
'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.chusanTeamName.name': 'Team Name', 'settings.fields.chusanTeamName.name': 'Team Name',
'settings.fields.chusanTeamName.desc': 'Customize the text displayed on the top of your profile.', 'settings.fields.chusanTeamName.desc': 'Customize the text displayed on the top of your profile.',
'settings.fields.chusanInfinitePenguins.name': 'Infinite Penguins', 'settings.fields.chusanInfinitePenguins.name': 'Infinite Penguins',
@@ -208,10 +196,10 @@ export const EN_REF_SETTINGS = {
'settings.export': 'Export Player Data', 'settings.export': 'Export Player Data',
'settings.batchManualExport': "Export in Batch Manual (for Tachi)", '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.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.gameNotice': "These only apply to Mai and Wacca.",
'settings.regionNotice': "These settings are shared amongst Mai, Ongeki and Chuni.", 'settings.regionNotice': "These only apply to Mai, Ongeki and Chuni.",
'settings.regionSelector.title': "Prefecture Selector", '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", 'settings.regionSelector.select': "Select Prefecture",
} }

View File

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

View File

@@ -10,6 +10,7 @@
import { t } from "../libs/i18n"; import { t } from "../libs/i18n";
import ImportDataAction from "./Home/ImportDataAction.svelte"; import ImportDataAction from "./Home/ImportDataAction.svelte";
import Communities from "./Home/Communities.svelte"; import Communities from "./Home/Communities.svelte";
import MigrateAction from "./Home/MigrateAction.svelte";
USER.ensureLoggedIn(); USER.ensureLoggedIn();
@@ -58,6 +59,9 @@
</ActionCard> </ActionCard>
<ImportDataAction/> <ImportDataAction/>
{#if me}
<MigrateAction username={me.username}/>
{/if}
</div> </div>
{:else if tab === 1} {:else if tab === 1}
<div out:fade={FADE_OUT} in:fade={FADE_IN}> <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"> <script lang="ts">
import { onMount } from "svelte";
import { title } from "../libs/ui"; import { title } from "../libs/ui";
import { GAME } from "../libs/sdk"; import { GAME } from "../libs/sdk";
import type { GenericRanking } from "../libs/generalTypes"; import type { GenericRanking } from "../libs/generalTypes";
@@ -9,7 +8,6 @@
import { t } from "../libs/i18n"; import { t } from "../libs/i18n";
import UserCard from "../components/UserCard.svelte"; import UserCard from "../components/UserCard.svelte";
import Tooltip from "../components/Tooltip.svelte"; import Tooltip from "../components/Tooltip.svelte";
import Pagination from "../components/Pagination.svelte";
export let game: GameName = 'mai2'; export let game: GameName = 'mai2';
@@ -17,45 +15,15 @@
let d: { users: GenericRanking[] }; let d: { users: GenericRanking[] };
let error: string | null; 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)]) Promise.all([GAME.ranking(game)])
.then(([users]) => { .then(([users]) => {
d = { users } console.log(users)
totalPages = Math.ceil(users.length / perPage) d = { users };
}) })
.catch((e) => error = e.message); .catch((e) => error = e.message);
let hoveringUser = ""; let hoveringUser = "";
let hoverLoading = false; let hoverLoading = false;
$: paginatedUsers = d ? d.users.slice((page - 1) * perPage, page * perPage) : []
</script> </script>
<main class="content leaderboard"> <main class="content leaderboard">
@@ -69,12 +37,8 @@
</div> </div>
{#if d} {#if d}
{#if page > 1}
<Pagination {page} {totalPages} on:updatePage={handleUpdatePage} />
{/if}
<div class="leaderboard-container"> <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="rank">{t("Leaderboard.Rank")}</span>
<span class="name"></span> <span class="name"></span>
<span class="rating">{t("Leaderboard.Rating")}</span> <span class="rating">{t("Leaderboard.Rating")}</span>
@@ -82,7 +46,7 @@
<span class="fc">{t("Leaderboard.FC")}</span> <span class="fc">{t("Leaderboard.FC")}</span>
<span class="ap">{t("Leaderboard.AP")}</span> <span class="ap">{t("Leaderboard.AP")}</span>
</div> </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" <div class="lb-user" class:alternate={i % 2 === 1} role="listitem"
on:mouseover={() => hoveringUser = user.username} on:focus={() => {}}> on:mouseover={() => hoveringUser = user.username} on:focus={() => {}}>
@@ -106,8 +70,6 @@
{/each} {/each}
</div> </div>
<Pagination {page} {totalPages} on:updatePage={handleUpdatePage} />
<Tooltip triggeredBy=".name" loading={hoverLoading}> <Tooltip triggeredBy=".name" loading={hoverLoading}>
<UserCard username={hoveringUser} {game} setLoading={l => hoverLoading = l} /> <UserCard username={hoveringUser} {game} setLoading={l => hoverLoading = l} />
</Tooltip> </Tooltip>
@@ -170,4 +132,5 @@
&.alternate &.alternate
background-color: vars.$ov-light background-color: vars.$ov-light
</style> </style>

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,6 @@ import kotlinx.coroutines.withContext
import org.apache.tika.Tika import org.apache.tika.Tika
import org.apache.tika.mime.MimeTypes import org.apache.tika.mime.MimeTypes
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.context.ApplicationContext
import org.springframework.http.HttpHeaders import org.springframework.http.HttpHeaders
import org.springframework.http.HttpStatus import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity.BodyBuilder import org.springframework.http.ResponseEntity.BodyBuilder
@@ -35,10 +34,8 @@ import java.util.concurrent.locks.Lock
import kotlin.reflect.KCallable import kotlin.reflect.KCallable
import kotlin.reflect.KClass import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty1 import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.isSubclassOf import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.full.memberProperties import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.jvmErasure import kotlin.reflect.jvm.jvmErasure
typealias RP = RequestParam typealias RP = RequestParam
@@ -83,9 +80,7 @@ annotation class SettingField(
// Reflection // Reflection
@Suppress("UNCHECKED_CAST") @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> } fun <T : Any> KClass<T>.vars() = memberProperties.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>.varsMap() = vars().associateBy { it.name } 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>.getters() = java.methods.filter { it.name.startsWith("get") }
fun <T : Any> KClass<T>.gettersMap() = getters().associateBy { it.name.removePrefix("get").firstCharLower() } 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() val <T> Set<T>.mut get() = toMutableSet()
fun <T> List<T>.unique(fn: (T) -> Any) = distinctBy(fn).ifEmpty { null } fun <T> List<T>.unique(fn: (T) -> Any) = distinctBy(fn).ifEmpty { null }
val <T> Collection<T>.csv get() = joinToString(",")
val IntArray.csv get() = joinToString(",")
// Optionals // Optionals
operator fun <T> Optional<T>.invoke(): T? = orElse(null) operator fun <T> Optional<T>.invoke(): T? = orElse(null)
@@ -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 fun Str.truncate(len: Int) = if (this.length > len) this.take(len) + "..." else this
val Str.some get() = ifBlank { null } val Str.some get() = ifBlank { null }
val ByteArray.hexStr get() = toHexString() val ByteArray.hexStr get() = toHexString()
operator fun StringBuilder.plusAssign(other: String) { this.append(other) }
// Coroutine // Coroutine
suspend fun <T> async(block: suspend kotlinx.coroutines.CoroutineScope.() -> T): T = withContext(Dispatchers.IO) { block() } suspend fun <T> async(block: suspend kotlinx.coroutines.CoroutineScope.() -> T): T = withContext(Dispatchers.IO) { block() }
@@ -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 inline operator fun <reified E> List<Any?>.invoke(i: Int) = get(i) as E
val empty = emptyList<Any>() val empty = emptyList<Any>()
val emptyMap = emptyMap<Any, Any>()
val <F> Pair<F, *>.l get() = component1() val <F> Pair<F, *>.l get() = component1()
val <S> Pair<*, S>.r get() = component2() val <S> Pair<*, S>.r get() = component2()
@@ -269,6 +268,3 @@ val <S> Pair<*, S>.r get() = component2()
val Query.exec get() = resultList.map { (it as Array<*>).toList() } val Query.exec get() = resultList.map { (it as Array<*>).toList() }
fun List<List<Any?>>.numCsv(vararg head: Str) = head.joinToString(",") + "\n" + fun List<List<Any?>>.numCsv(vararg head: Str) = head.joinToString(",") + "\n" +
joinToString("\n") { it.joinToString(",") } 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}" 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) = override fun deserialize(parser: JsonParser, context: DeserializationContext) =
// First try standard formats via asDateTime() method // 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) // Try maimai2 format (yyyy-MM-dd HH:mm:ss.0)
LocalDateTime.parse(parser.text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0")) LocalDateTime.parse(parser.text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0"))
} catch (e: Exception) { } catch (e: Exception) {
400 - "Invalid date time value ${parser.text}" 400 - "Invalid date time value ${parser.text}"
} } }
}) })
val JACKSON = jacksonObjectMapper().apply { val JACKSON = jacksonObjectMapper().apply {
setSerializationInclusion(JsonInclude.Include.NON_NULL) 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.db.AquaUserServices
import icu.samnyan.aqua.net.utils.SUCCESS import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.chusan.model.Chu3Repos import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.model.CardStatus import icu.samnyan.aqua.sega.general.model.CardStatus
import icu.samnyan.aqua.sega.general.model.sensitiveInfo import icu.samnyan.aqua.sega.general.model.sensitiveInfo
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
@@ -52,7 +53,7 @@ class BotController(
secret.checkSecret() secret.checkSecret()
// 1. Find user card // 1. Find user card
val oc = (us.cardRepo.findByLuid(card)() ?: (404 - "Card not found")).maybeGhost() val oc = (us.cardRepo.findByLuid(card) ?: (404 - "Card not found")).maybeGhost()
// 2. Change the status to migrated // 2. Change the status to migrated
us.cardRepo.save(oc.apply { us.cardRepo.save(oc.apply {
@@ -66,7 +67,7 @@ class BotController(
fun clearMigrateFlag(@RP secret: Str, @RP card: Str): Any { fun clearMigrateFlag(@RP secret: Str, @RP card: Str): Any {
secret.checkSecret() secret.checkSecret()
val oc = (us.cardRepo.findByLuid(card)() ?: (404 - "Card not found")).maybeGhost() val oc = (us.cardRepo.findByLuid(card) ?: (404 - "Card not found")).maybeGhost()
us.cardRepo.save(oc.apply { us.cardRepo.save(oc.apply {
status = CardStatus.NORMAL status = CardStatus.NORMAL
@@ -82,14 +83,14 @@ class BotController(
secret.checkSecret() secret.checkSecret()
// 1. Check if the card exist // 1. Check if the card exist
var cards = listOfNotNull( var cards: MutableList<Card> = listOfNotNull(
us.cardRepo.findByLuid(cardId)(), us.cardRepo.findByLuid(cardId),
).mut ).mut
cardId.toLongOrNull()?.let { cardId.toLongOrNull()?.let {
cards += listOfNotNull( cards += listOfNotNull(
us.cardRepo.findById(it)(), us.cardRepo.findById(it)(),
us.cardRepo.findByExtId(it)(), us.cardRepo.findByExtId(it),
) )
cards += listOfNotNull( cards += listOfNotNull(
@@ -110,8 +111,8 @@ class BotController(
return cards.map { card -> return cards.map { card ->
// Find all games played by this card // Find all games played by this card
val chu3 = chu3Db.userData.findByCard_ExtId(card.extId)() val chu3 = chu3Db.userData.findByCard_ExtId(card.extId)
val mai2 = mai2Db.userData.findByCard_ExtId(card.extId)() val mai2 = mai2Db.userData.findByCard_ExtId(card.extId)
val gamesDict = listOfNotNull(chu3, mai2).map { val gamesDict = listOfNotNull(chu3, mai2).map {
// Find the keychip owner // Find the keychip owner
val keychip = it.lastClientId val keychip = it.lastClientId

View File

@@ -8,6 +8,7 @@ import icu.samnyan.aqua.net.games.IUserData
import icu.samnyan.aqua.net.utils.AquaNetProps import icu.samnyan.aqua.net.utils.AquaNetProps
import icu.samnyan.aqua.net.utils.SUCCESS import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo
import icu.samnyan.aqua.sega.diva.PlayerProfileRepository
import icu.samnyan.aqua.sega.general.dao.CardRepository import icu.samnyan.aqua.sega.general.dao.CardRepository
import icu.samnyan.aqua.sega.general.model.Card import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.service.CardService import icu.samnyan.aqua.sega.general.service.CardService
@@ -19,7 +20,6 @@ import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import java.time.LocalDateTime import java.time.LocalDateTime
import kotlin.jvm.optionals.getOrNull
import kotlin.random.Random import kotlin.random.Random
@RestController @RestController
@@ -101,7 +101,8 @@ class CardController(
val games = migrate.split(',') val games = migrate.split(',')
cardGameService.migrate(card, games) cardGameService.migrate(card, games)
fedy.onCardLinked(card.luid, oldExtId = card.extId, ghostExtId = u.ghostCard.extId, games) fedy.onCardLinked(card.luid, oldExtId = card.extId, ghostExtId = u.ghostCard.extId,
games.map { Fedy.getGameName(it) }.filterNotNull())
log.info("Net /card/link : Linked card ${card.id} to user ${u.username} and migrated data to ${games.joinToString()}") log.info("Net /card/link : Linked card ${card.id} to user ${u.username} and migrated data to ${games.joinToString()}")
@@ -203,11 +204,10 @@ class CardGameService(
val chusan: Chu3UserDataRepo, val chusan: Chu3UserDataRepo,
val wacca: WcUserRepo, val wacca: WcUserRepo,
val ongeki: OgkUserDataRepo, val ongeki: OgkUserDataRepo,
val diva: icu.samnyan.aqua.sega.diva.dao.userdata.PlayerProfileRepository, val diva: PlayerProfileRepository,
val safety: AquaNetSafetyService, val safety: AquaNetSafetyService,
val cardRepo: CardRepository, val cardRepo: CardRepository,
val em: EntityManager, val em: EntityManager
val cardService: CardService
) { ) {
companion object { companion object {
val log = logger() val log = logger()
@@ -225,9 +225,7 @@ class CardGameService(
val remainingGames = dataRepos.keys.toMutableSet() val remainingGames = dataRepos.keys.toMutableSet()
games.forEach { game -> games.forEach { game ->
val dataRepo = dataRepos[game] ?: return@forEach val dataRepo = dataRepos[game] ?: return@forEach
if (migrateCard(game, dataRepo, cardRepo, crd)) migrateCard(game, dataRepo, cardRepo, crd)
// Update timestamp for the ghost card (data migrated in)
cardService.updateCardTimestamp(crd.aquaUser!!.ghostCard, game, resetCreatedAt = true)
remainingGames.remove(game) remainingGames.remove(game)
} }
// For remaining games, orphan the data by assigning them to a dummy card // For remaining games, orphan the data by assigning them to a dummy card
@@ -240,7 +238,7 @@ class CardGameService(
"chu3" to getSummaryFor(chusan, card), "chu3" to getSummaryFor(chusan, card),
"ongeki" to getSummaryFor(ongeki, card), "ongeki" to getSummaryFor(ongeki, card),
"wacca" to getSummaryFor(wacca, card), "wacca" to getSummaryFor(wacca, card),
"diva" to diva.findByPdId(card.extId).getOrNull()?.let { "diva" to diva.findByPdId(card.extId)()?.let {
mapOf( mapOf(
"name" to it.playerName, "name" to it.playerName,
"rating" to it.level, "rating" to it.level,

View File

@@ -2,39 +2,36 @@ package icu.samnyan.aqua.net
import ext.* import ext.*
import icu.samnyan.aqua.net.components.EmailProperties 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.components.JWT
import icu.samnyan.aqua.net.db.AquaGameOptions
import icu.samnyan.aqua.net.db.AquaNetUser import icu.samnyan.aqua.net.db.AquaNetUser
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.mai2.Mai2Import
import icu.samnyan.aqua.net.games.ExportOptions import icu.samnyan.aqua.net.games.ExportOptions
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPlaylogHandler as Mai2UploadUserPlaylogHandler
import icu.samnyan.aqua.sega.maimai2.handler.UpsertUserAllHandler as Mai2UpsertUserAllHandler
import icu.samnyan.aqua.net.utils.ApiException
import org.springframework.transaction.PlatformTransactionManager
import org.springframework.transaction.support.TransactionTemplate
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
import icu.samnyan.aqua.net.games.GenericUserDataRepo import icu.samnyan.aqua.net.games.GenericUserDataRepo
import icu.samnyan.aqua.net.games.IUserData import icu.samnyan.aqua.net.games.IUserData
import icu.samnyan.aqua.net.games.mai2.Mai2Import
import icu.samnyan.aqua.net.utils.ApiException
import icu.samnyan.aqua.net.utils.PathProps import icu.samnyan.aqua.net.utils.PathProps
import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo
import icu.samnyan.aqua.sega.general.dao.CardRepository import icu.samnyan.aqua.sega.general.dao.CardRepository
import icu.samnyan.aqua.sega.general.model.Card import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.service.CardService import icu.samnyan.aqua.sega.general.service.CardService
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo
import icu.samnyan.aqua.sega.wacca.model.db.WcUserRepo 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 org.springframework.web.multipart.MultipartFile
import java.time.Instant import java.security.MessageDigest
import java.util.concurrent.CompletableFuture import java.util.concurrent.CompletableFuture
import kotlin.io.path.getLastModifiedTime import kotlin.io.path.getLastModifiedTime
import kotlin.io.path.isRegularFile import kotlin.io.path.isRegularFile
import kotlin.io.path.writeBytes import kotlin.io.path.writeBytes
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPlaylogHandler as Mai2UploadUserPlaylogHandler
import icu.samnyan.aqua.sega.maimai2.handler.UpsertUserAllHandler as Mai2UpsertUserAllHandler
@Configuration @Configuration
@ConfigurationProperties(prefix = "aqua-net.fedy") @ConfigurationProperties(prefix = "aqua-net.fedy")
@@ -44,11 +41,11 @@ class FedyProps {
var remote: String = "" var remote: String = ""
} }
data class UserProfilePicture(val url: Str, val updatedAtMs: Long) data class UserProfilePicture(val url: Str, val lastUpdatedMs: Long)
data class UserBasicInfo( data class UserBasicInfo(
val auId: Long, val ghostExtId: Long, val registrationTimeMs: Long, 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 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) 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"]) @API("/api/v2/fedy", consumes = ["multipart/form-data"])
class Fedy( class Fedy(
val jwt: JWT, val jwt: JWT,
val us: AquaUserServices,
val emailProps: EmailProperties, val emailProps: EmailProperties,
val cardRepo: CardRepository, val cardRepo: CardRepository,
val cardService: CardService,
val mai2Import: Mai2Import,
val mai2UserDataRepo: Mai2UserDataRepo, val mai2UserDataRepo: Mai2UserDataRepo,
val mai2UploadUserPlaylog: Mai2UploadUserPlaylogHandler,
val mai2UpsertUserAll: Mai2UpsertUserAllHandler,
val chu3UserDataRepo: Chu3UserDataRepo, val chu3UserDataRepo: Chu3UserDataRepo,
val ongekiUserDataRepo: OgkUserDataRepo, val ongekiUserDataRepo: OgkUserDataRepo,
val waccaUserDataRepo: WcUserRepo, val waccaUserDataRepo: WcUserRepo,
val props: FedyProps, val props: FedyProps,
val paths: PathProps, val paths: PathProps,
val transactionManager: PlatformTransactionManager, val transactionManager: PlatformTransactionManager
ctx: ApplicationContext
) { ) {
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) } val transaction by lazy { TransactionTemplate(transactionManager) }
private fun Str.checkKey() { private fun Str.checkKey() {
@@ -132,7 +127,7 @@ class Fedy(
} caught { UserRegisterRes(error = it) } } 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) data class UserUpdateRes(val error: FedyErr? = null, val user: UserBasicInfo? = null)
@API("/user/update") @API("/user/update")
fun handleUserUpdate(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: UserUpdateReq, @RT(PFP_PART) pfpFile: MultipartFile?): UserUpdateRes = handleFedy(key) { 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) } if (k == "email") { ru.email = us.validateEmail(v) }
else us.update(ru, k, 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 mime = TIKA.detect(pfpFile.bytes).takeIf { it.startsWith("image/") } ?: (400 - "Invalid file type")
val name = "${ru.auId}${MIMES.forName(mime)?.extension ?: ".jpg"}" val name = "${ru.auId}${MIMES.forName(mime)?.extension ?: ".jpg"}"
(paths.aquaNetPortrait.path() / name).writeBytes(bytes) (paths.aquaNetPortrait.path() / name).writeBytes(bytes)
ru.profilePicture = name 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) us.userRepo.save(ru)
if (fields.containsKey("pwHash") ?: false) { us.clearAllSessions(ru) } if (fields.containsKey("pwHash") ?: false) { us.clearAllSessions(ru) }
UserUpdateRes(user = ru.fedyBasicInfo()) UserUpdateRes(user = ru.fedyBasicInfo())
@@ -166,44 +157,36 @@ class Fedy(
?.let { paths.aquaNetPortrait.path() / it }?.takeIf { it.isRegularFile() } ?.let { paths.aquaNetPortrait.path() / it }?.takeIf { it.isRegularFile() }
?.let { UserProfilePicture( ?.let { UserProfilePicture(
url = "/uploads/net/portrait/${profilePicture}", url = "/uploads/net/portrait/${profilePicture}",
updatedAtMs = it.getLastModifiedTime().toMillis() lastUpdatedMs = it.getLastModifiedTime().toMillis()
) }, ) }
gameOptions?.let { o -> GAME_OPTIONS_FIELDS.mapValues { it.value.get(o) } }
) )
data class DataPullReq(val extId: Long, val game: Str, val createdAtMs: Long, val updatedAtMs: Long, val exportOptions: ExportOptions) data class DataPullReq(val extId: Long, val game: Str, 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: Any? = null)
data class DataPullRes(val error: FedyErr? = null, val result: DataPullResult? = null)
@API("/data/pull") @API("/data/pull")
fun handleDataPull(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: DataPullReq): DataPullRes = handleFedy(key) { 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") ?: (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) { DataPullRes(result = when (req.game) {
"mai2" -> mai2Import.export(card, exportOptions) "mai2" -> mai2Import.export(card, req.exportOptions)
else -> 406 - "Unsupported game" else -> 406 - "Unsupported game"
}, createdAtMs = cardTimestamp.createdAt.toEpochMilli(), updatedAtMs = cardTimestamp.updatedAt.toEpochMilli(), isRebased = isRebased)) })
} caught { DataPullRes(error = it) } } 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") @Suppress("UNCHECKED_CAST")
@API("/data/push") @API("/data/push")
fun handleDataPush(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: DataPushReq): Any = handleFedy(key) { fun handleDataPush(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: DataPushReq): Any = handleFedy(key) {
val extId = req.extId val extId = req.extId
fun<UserData : IUserData, UserRepo : GenericUserDataRepo<UserData>> removeOldData(repo: UserRepo) { fun<UserData : IUserData, UserRepo : GenericUserDataRepo<UserData>> removeOldData(repo: UserRepo) {
val oldData = repo.findByCard_ExtId(extId) repo.findByCard_ExtId(extId)?.let { oldData ->
if (oldData.isPresent) {
log.info("Fedy: Deleting old data for $extId (${req.game})") log.info("Fedy: Deleting old data for $extId (${req.game})")
repo.delete(oldData.get()); repo.delete(oldData);
repo.flush() repo.flush()
} }
} }
val card = cardRepo.findByExtId(extId).orElse(null) ?: (404 - "Card not found")
transaction.execute { when (req.game) { transaction.execute { when (req.game) {
"mai2" -> { "mai2" -> {
if (req.removeOldData) { removeOldData(mai2UserDataRepo) } if (req.removeOldData) { removeOldData(mai2UserDataRepo) }
@@ -214,7 +197,7 @@ class Fedy(
} }
else -> 406 - "Unsupported game" else -> 406 - "Unsupported game"
} } } }
cardService.updateCardTimestamp(card, req.game, now = Instant.ofEpochMilli(req.updatedAtMs), resetCreatedAt = req.removeOldData)
SUCCESS SUCCESS
} }
@@ -237,9 +220,9 @@ class Fedy(
var pairedCard = cardService.tryLookup(req.pairedLuid)?.maybeGhost() var pairedCard = cardService.tryLookup(req.pairedLuid)?.maybeGhost()
if (pairedCard?.extId != card?.extId) { if (pairedCard?.extId != card?.extId) {
var isGhost = pairedCard?.isGhost == true var isGhost = pairedCard?.isGhost == true
var isNonFresh = pairedCard != null && !isCardFresh(pairedCard) var isFresh = pairedCard != null && isCardFresh(pairedCard)
if (isGhost || isNonFresh) isPairedLuidDiverged = true if (isGhost && isFresh) isPairedLuidDiverged = true
else if (card?.isGhost == true) { else if (!isGhost && card?.isGhost == true) {
// Ensure paired card is linked, if the main card is linked // 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 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) } 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})") 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 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 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 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 onCardUnlinked(luid: Str) = maybeNotifyAsync(FedyEvent(cardUnlinked = CardUnlinkedEvent(luid)))
fun onDataUpdated(extId: Long, game: Str, removeOldData: Bool) = maybeNotifyAsync { 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 val card = cardRepo.findByExtId(extId) ?: return@maybeNotifyAsync null // Card not found, nothing to do
FedyEvent(dataUpdated = DataUpdatedEvent(extId, card.isGhost, game, removeOldData)) 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 var event: FedyEvent? = null
try { try {
event = getEvent() 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. // Apparently existing cards could possibly be fresh and never used in any game. Treat them as new cards.
private fun isCardFresh(c: Card): Bool { 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 { return when {
checkForGame(mai2UserDataRepo, c) -> false checkForGame(mai2UserDataRepo, c) -> false
checkForGame(chu3UserDataRepo, c) -> false checkForGame(chu3UserDataRepo, c) -> false
@@ -360,12 +344,18 @@ class Fedy(
const val KEY_HEADER = "X-Fedy-Key" const val KEY_HEADER = "X-Fedy-Key"
const val REQ_PART = "request" const val REQ_PART = "request"
const val PFP_PART = "profilePicture" 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() 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.length != 20) 400 - "Invalid access code"
// if (!accessCode.startsWith("9900")) 400 - "Frontier access code must start with 9900" // if (!accessCode.startsWith("9900")) 400 - "Frontier access code must start with 9900"
if (async { cardService.cardRepo.findByLuid(accessCode) }.isPresent) 400 - "Card already registered" if (async { cardService.cardRepo.findByLuid(accessCode) } != null) 400 - "Card already registered"
val card = async { cardService.registerByAccessCode(accessCode) } val card = async { cardService.registerByAccessCode(accessCode) }

View File

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

View File

@@ -14,8 +14,7 @@ import kotlin.reflect.jvm.jvmErasure
class SettingsApi( class SettingsApi(
val us: AquaUserServices, val us: AquaUserServices,
val userRepo: AquaNetUserRepo, val userRepo: AquaNetUserRepo,
val goRepo: AquaGameOptionsRepo, val goRepo: AquaGameOptionsRepo
val fedy: Fedy
) { ) {
// Get all params with SettingField annotation // Get all params with SettingField annotation
val fields = AquaGameOptions::class.vars() val fields = AquaGameOptions::class.vars()
@@ -42,6 +41,6 @@ class SettingsApi(
} }
// Check field type // Check field type
field.setCast(options, value) 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 ext.*
import icu.samnyan.aqua.net.components.* import icu.samnyan.aqua.net.components.*
import icu.samnyan.aqua.net.db.* import icu.samnyan.aqua.net.db.AquaNetUserRepo
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.db.EmailConfirmationRepo
import icu.samnyan.aqua.net.db.ResetPasswordRepo
import icu.samnyan.aqua.net.utils.PathProps import icu.samnyan.aqua.net.utils.PathProps
import icu.samnyan.aqua.net.utils.SUCCESS import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.general.dao.CardRepository import icu.samnyan.aqua.sega.general.dao.CardRepository
import icu.samnyan.aqua.sega.general.model.CardStatus
import jakarta.servlet.http.HttpServletRequest import jakarta.servlet.http.HttpServletRequest
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Lazy
import org.springframework.security.crypto.password.PasswordEncoder import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile import org.springframework.web.multipart.MultipartFile
@@ -21,7 +27,6 @@ class UserRegistrar(
val hasher: PasswordEncoder, val hasher: PasswordEncoder,
val turnstileService: TurnstileService, val turnstileService: TurnstileService,
val emailService: EmailService, val emailService: EmailService,
val fedy: Fedy,
val geoIP: GeoIP, val geoIP: GeoIP,
val jwt: JWT, val jwt: JWT,
val confirmationRepo: EmailConfirmationRepo, val confirmationRepo: EmailConfirmationRepo,
@@ -31,6 +36,7 @@ class UserRegistrar(
val emailProps: EmailProperties, val emailProps: EmailProperties,
final val paths: PathProps final val paths: PathProps
) { ) {
@Autowired @Lazy lateinit var fedy: Fedy
val portraitPath = paths.aquaNetPortrait.path() val portraitPath = paths.aquaNetPortrait.path()
companion object { companion object {
@@ -86,6 +92,8 @@ class UserRegistrar(
?: (400 - "User not found") ?: (400 - "User not found")
if (!hasher.matches(password, user.pwHash)) 400 - "Invalid password" 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 // Check if email is verified
if (!user.emailConfirmed && emailProps.enable) { if (!user.emailConfirmed && emailProps.enable) {
// Check if last confirmation email was sent within a minute // 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.Bool
import ext.Str import ext.Str
import ext.logger import ext.logger
import icu.samnyan.aqua.net.db.AquaNetUser import icu.samnyan.aqua.net.db.*
import icu.samnyan.aqua.net.db.EmailConfirmation
import icu.samnyan.aqua.net.db.EmailConfirmationRepo
import icu.samnyan.aqua.net.db.ResetPassword
import icu.samnyan.aqua.net.db.ResetPasswordRepo
import org.simplejavamail.api.mailer.Mailer import org.simplejavamail.api.mailer.Mailer
import org.simplejavamail.email.EmailBuilder import org.simplejavamail.email.EmailBuilder
import org.simplejavamail.springsupport.SimpleJavaMailSpringSupport import org.simplejavamail.springsupport.SimpleJavaMailSpringSupport

View File

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

View File

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

View File

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

View File

@@ -184,7 +184,7 @@ class AquaUserServices(
suspend fun cardByName(username: Str) = suspend fun cardByName(username: Str) =
if (username.startsWith("user")) username.substring(4).toLongOrNull() if (username.startsWith("user")) username.substring(4).toLongOrNull()
?.let { cardRepo.findById(it).getOrNull() } ?: (404 - "Card not found") ?.let { cardRepo.findById(it)() } ?: (404 - "Card not found")
else byName(username) { it.ghostCard } else byName(username) { it.ghostCard }
suspend fun <T> cardByName(username: Str, callback: suspend (Card) -> T) = callback(cardByName(username)) suspend fun <T> cardByName(username: Str, callback: suspend (Card) -> T) = callback(cardByName(username))

View File

@@ -5,7 +5,7 @@ import icu.samnyan.aqua.net.BotProps
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.utils.SUCCESS import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.general.model.Card import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.service.CardService import icu.samnyan.aqua.sega.general.model.CardStatus
import jakarta.annotation.PostConstruct import jakarta.annotation.PostConstruct
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired 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 playlogRepo: GenericPlaylogRepo<*>
abstract val userMusicRepo: GenericUserMusicRepo<*> abstract val userMusicRepo: GenericUserMusicRepo<*>
abstract val shownRanks: List<Pair<Int, String>> abstract val shownRanks: List<Pair<Int, String>>
abstract val settableFields: Map<String, (T, String) -> Unit> abstract val settableFields: Map<String, (T, String) -> Unit>
open val gettableFields: Set<String> = setOf() open val gettableFields: Set<String> = setOf()
@Autowired lateinit var cardService: CardService
@API("trend") @API("trend")
abstract suspend fun trend(@RP username: String): List<TrendOut> abstract suspend fun trend(@RP username: String): List<TrendOut>
@API("user-summary") @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 -> 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 // 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 else u
} }
@@ -121,17 +118,19 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
} }
@API("playlog") @API("playlog")
fun playlog(@RP id: Long): IGenericGamePlaylog = playlogRepo.findById(id).getOrNull() ?: (404 - "Playlog not found") fun playlog(@RP id: Long): IGenericGamePlaylog = playlogRepo.findById(id)() ?: (404 - "Playlog not found")
val userDetailFields by lazy { userDataClass.gettersMap().let { vm -> val userDetailFields by lazy { userDataClass.gettersMap().let { vm ->
(settableFields.keys.toSet() + gettableFields) (settableFields.keys.toSet() + gettableFields)
.associateWith { k -> (vm[k] ?: error("Field $k not found")) } .associateWith { k -> (vm[k] ?: error("Field $k not found")) }
} } } }
@API("user-detail") @API("user-detail")
suspend fun userDetail(@RP username: String) = us.cardByName(username) { card -> suspend fun userDetail(@RP username: String) = us.cardByName(username) { card ->
val u = userDataRepo.findByCard(card) ?: (404 - "User not found") val u = userDataRepo.findByCard(card) ?: (404 - "User not found")
userDetailFields.toList().associate { (k, f) -> k to f.invoke(u) } userDetailFields.toList().associate { (k, f) -> k to f.invoke(u) }
} }
@API("user-detail-set") @API("user-detail-set")
suspend fun userDetailSet(@RP token: String, @RP field: String, @RP value: String): Any { suspend fun userDetailSet(@RP token: String, @RP field: String, @RP value: String): Any {
val prop = settableFields[field] ?: (400 - "Invalid field $field") 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") val user = async { userDataRepo.findByCard(u.ghostCard) } ?: (404 - "User not found")
prop(user, value) prop(user, value)
async { userDataRepo.save(user) } async { userDataRepo.save(user) }
cardService.updateCardTimestamp(u.ghostCard, name)
SUCCESS 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") @API("user-music-from-list")
suspend fun userMusicFromList(@RP username: Str, @RB musicList: List<Int>) = us.cardByName(username) { card -> suspend fun userMusicFromList(@RP username: Str, @RB musicList: List<Int>) = us.cardByName(username) { card ->
userMusicRepo.findByUser_Card_ExtIdAndMusicIdIn(card.extId, musicList) userMusicRepo.findByUser_Card_ExtIdAndMusicIdIn(card.extId, musicList)

View File

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

View File

@@ -125,7 +125,7 @@ open class BaseEntity(
@NoRepositoryBean @NoRepositoryBean
interface GenericUserDataRepo<T : IUserData> : JpaRepository<T, Long> { interface GenericUserDataRepo<T : IUserData> : JpaRepository<T, Long> {
fun findByCard(card: Card): T? fun findByCard(card: Card): T?
fun findByCard_ExtId(extId: Long): Optional<T> fun findByCard_ExtId(extId: Long): T?
@Query("select e from #{#entityName} e where e.card.rankingBanned = false") @Query("select e from #{#entityName} e where e.card.rankingBanned = false")
fun findAllNonBanned(): List<T> fun findAllNonBanned(): List<T>

View File

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

View File

@@ -3,15 +3,13 @@ package icu.samnyan.aqua.net.games.chu3
import ext.* import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.games.*
import icu.samnyan.aqua.net.utils.* import icu.samnyan.aqua.net.utils.chu3Scores
import icu.samnyan.aqua.sega.chusan.model.* import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo
import icu.samnyan.aqua.sega.chusan.model.Chu3UserMusicDetailRepo
import icu.samnyan.aqua.sega.chusan.model.Chu3UserPlaylogRepo
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData
import icu.samnyan.aqua.sega.chusan.model.userdata.UserGameOption
import org.springframework.web.bind.annotation.RestController 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 @RestController
@API("api/v2/game/chu3") @API("api/v2/game/chu3")
@@ -38,7 +36,6 @@ class Chusan(
"trophyIdSub2" to { u, v -> u.trophyIdSub2 = v.int }, "trophyIdSub2" to { u, v -> u.trophyIdSub2 = v.int },
"mapIconId" to { u, v -> u.mapIconId = v.int }, "mapIconId" to { u, v -> u.mapIconId = v.int },
"voiceId" to { u, v -> u.voiceId = 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 }, "avatarWear" to { u, v -> u.avatarWear = v.int },
"avatarHead" to { u, v -> u.avatarHead = v.int }, "avatarHead" to { u, v -> u.avatarHead = v.int },
"avatarFace" to { u, v -> u.avatarFace = v.int }, "avatarFace" to { u, v -> u.avatarFace = v.int },
@@ -50,7 +47,7 @@ class Chusan(
"lastRomVersion" to { u, v -> u.lastRomVersion = v }, "lastRomVersion" to { u, v -> u.lastRomVersion = v },
"lastDataVersion" to { u, v -> u.lastDataVersion = v }, "lastDataVersion" to { u, v -> u.lastDataVersion = v },
) } ) }
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 -> override suspend fun userSummary(@RP username: Str, @RP token: String?) = us.cardByName(username) { card ->
// Summary values: total plays, player rating, server-wide ranking // 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 // UserBox related APIs
@API("user-box") @API("user-box")
fun userBox(@RP token: String) = us.jwt.auth(token) { 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 org.springframework.web.bind.annotation.RestController
import kotlin.reflect.full.declaredMembers import kotlin.reflect.full.declaredMembers
@Suppress("UNCHECKED_CAST")
@RestController @RestController
@API("api/v2/game/mai2") @API("api/v2/game/mai2")
class Mai2Import( class Mai2Import(
val repos: Mai2Repos, val repos: Mai2Repos,
) : ImportController<Maimai2DataExport, Mai2UserDetail>( ) : ImportController<Maimai2DataExport, Mai2UserDetail>(
"SDEZ", "mai2", Maimai2DataExport::class, "SDEZ", Maimai2DataExport::class,
exportFields = Maimai2DataExport::class.vars().associateBy { exportFields = Maimai2DataExport::class.vars().associateBy {
it.name.replace("List", "").lowercase() it.name.replace("List", "").lowercase()
}, },
@@ -61,7 +62,7 @@ class Mai2Import(
} }
}, },
Maimai2DataExport::userFavoriteMusicList to { user: Mai2UserDetail, _: ExportOptions -> Maimai2DataExport::userFavoriteMusicList to { user: Mai2UserDetail, _: ExportOptions ->
repos.userGeneralData.findByUserAndPropertyKey(user, "favorite_music").orElse(null) repos.userGeneralData.findByUserAndPropertyKey(user, "favorite_music")
?.propertyValue ?.propertyValue
?.takeIf { it.isNotEmpty() } ?.takeIf { it.isNotEmpty() }
?.split(",") ?.split(",")
@@ -78,7 +79,7 @@ class Mai2Import(
if (favoriteMusicList.isNotEmpty()) { if (favoriteMusicList.isNotEmpty()) {
val key = "favorite_music" val key = "favorite_music"
// This field always imports as incremental, since the userGeneralData field (for backwards compatibility) is processed before this // This field always imports as incremental, since the userGeneralData field (for backwards compatibility) is processed before this
val data = repos.userGeneralData.findByUserAndPropertyKey(user, key).orElse(null) val data = repos.userGeneralData.findByUserAndPropertyKey(user, key)
?: Mai2UserGeneralData().apply { this.user = user; propertyKey = key } ?: Mai2UserGeneralData().apply { this.user = user; propertyKey = key }
repos.userGeneralData.save(data.apply { repos.userGeneralData.save(data.apply {
propertyValue = favoriteMusicList.sortedBy { it.orderId }.map { it.id }.joinToString(",") propertyValue = favoriteMusicList.sortedBy { it.orderId }.map { it.id }.joinToString(",")

View File

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

View File

@@ -3,17 +3,19 @@ package icu.samnyan.aqua.net.games.mai2
import ext.* import ext.*
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.games.*
import icu.samnyan.aqua.net.utils.* import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.net.utils.mai2Scores
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPhotoHandler import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPhotoHandler
import icu.samnyan.aqua.sega.maimai2.model.* import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
import icu.samnyan.aqua.sega.maimai2.model.userdata.* import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserMusicDetailRepo
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserPlaylogRepo
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserDetail
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserGeneralData
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserLoginBonus
import org.springframework.http.MediaType import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RestController import org.springframework.web.bind.annotation.RestController
import java.util.*
import kotlin.jvm.optionals.getOrNull
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.memberProperties
@RestController @RestController
@API("api/v2/game/mai2") @API("api/v2/game/mai2")
@@ -61,9 +63,9 @@ class Maimai2(
us.jwt.auth(t) { u -> us.jwt.auth(t) { u ->
if (u.username == username) return@auth null if (u.username == username) return@auth null
us.cardByName(u.username) { myCard -> us.cardByName(u.username) { myCard ->
val user = repos.userData.findByCardExtId(card.extId).orElse(null) ?: (404 - "User not found") val user = repos.userData.findByCardExtId(card.extId) ?: (404 - "User not found")
val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival") val myRival = (repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival")?.propertyValue?.split(',') ?: emptyList())
.map { it.propertyValue.split(',') }.orElse(emptyList()).filter { it.isNotEmpty() }.map { it.long() } .filter { it.isNotEmpty() }.map { it.long() }
myRival.contains(user.id) myRival.contains(user.id)
} }
} }
@@ -113,7 +115,6 @@ class Maimai2(
val user = userDataRepo.findByCard(card) ?: (404 - "User not found") val user = userDataRepo.findByCard(card) ?: (404 - "User not found")
user.userName = newNameFull user.userName = newNameFull
userDataRepo.save(user) userDataRepo.save(user)
cardService.updateCardTimestamp(card, "mai2")
} }
mapOf("newName" to newNameFull) mapOf("newName" to newNameFull)
} }
@@ -136,36 +137,17 @@ class Maimai2(
if (loginBonus.none { it.bonusId == bonusId }) { if (loginBonus.none { it.bonusId == bonusId }) {
// create one // create one
val newBonus = Mai2UserLoginBonus().apply { val newBonus = Mai2UserLoginBonus().apply {
user = repos.userData.findByCardExtId(card.extId).orElse(null) ?: (404 - "User not found") user = repos.userData.findByCardExtId(card.extId) ?: (404 - "User not found")
this.bonusId = bonusId this.bonusId = bonusId
isCurrent = true isCurrent = true
} }
loginBonus.add(newBonus) loginBonus.add(newBonus)
} }
repos.userLoginBonus.saveAll(loginBonus) repos.userLoginBonus.saveAll(loginBonus)
cardService.updateCardTimestamp(card, "mai2")
} }
SUCCESS 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") @API("owned-items")
suspend fun ownedItems(@RP token: String) = us.jwt.auth(token) { u -> suspend fun ownedItems(@RP token: String) = us.jwt.auth(token) { u ->
us.cardByName(u.username) { card -> 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 -> suspend fun setRival(@RP token: String, @RP rivalUserName: String, @RP isAdd: Boolean) = us.jwt.auth(token) { u ->
us.cardByName(u.username) { myCard -> us.cardByName(u.username) { myCard ->
val rivalCard = us.cardByName(rivalUserName) { it } val rivalCard = us.cardByName(rivalUserName) { it }
val rivalUser = repos.userData.findByCardExtId(rivalCard.extId).orElse(null) ?: (404 - "User not found") val rivalUser = repos.userData.findByCardExtId(rivalCard.extId) ?: (404 - "User not found")
val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival").orElse(null) val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival")
?: Mai2UserGeneralData().apply { ?: Mai2UserGeneralData().apply {
user = repos.userData.findByCardExtId(myCard.extId).orElse(null) ?: (404 - "User not found") user = repos.userData.findByCardExtId(myCard.extId) ?: (404 - "User not found")
propertyKey = "favorite_rival" propertyKey = "favorite_rival"
} }
val myRivalList = myRival.propertyValue.split(',').filter { it.isNotEmpty() }.mut val myRivalList = myRival.propertyValue.split(',').filter { it.isNotEmpty() }.mut
@@ -195,7 +177,6 @@ class Maimai2(
myRival.propertyValue = myRivalList.joinToString(",") myRival.propertyValue = myRivalList.joinToString(",")
repos.userGeneralData.save(myRival) repos.userGeneralData.save(myRival)
cardService.updateCardTimestamp(myCard, "mai2")
} }
SUCCESS SUCCESS
} }

View File

@@ -1,23 +1,15 @@
package icu.samnyan.aqua.net.games.ongeki package icu.samnyan.aqua.net.games.ongeki
import ext.API import ext.API
import ext.RP
import ext.minus
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.games.*
import icu.samnyan.aqua.net.utils.* import icu.samnyan.aqua.net.utils.ongekiScores
import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserGeneralDataRepo import icu.samnyan.aqua.sega.ongeki.OgkUserGeneralDataRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserMusicDetailRepo import icu.samnyan.aqua.sega.ongeki.OgkUserMusicDetailRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserOptionRepo
import icu.samnyan.aqua.sega.ongeki.OgkUserPlaylogRepo import icu.samnyan.aqua.sega.ongeki.OgkUserPlaylogRepo
import icu.samnyan.aqua.sega.ongeki.model.UserData 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 org.springframework.web.bind.annotation.RestController
import kotlin.jvm.optionals.getOrNull
import kotlin.reflect.KMutableProperty1
import kotlin.reflect.full.memberProperties
@RestController @RestController
@API("api/v2/game/ongeki") @API("api/v2/game/ongeki")
@@ -26,8 +18,7 @@ class Ongeki(
override val playlogRepo: OgkUserPlaylogRepo, override val playlogRepo: OgkUserPlaylogRepo,
override val userDataRepo: OgkUserDataRepo, override val userDataRepo: OgkUserDataRepo,
override val userMusicRepo: OgkUserMusicDetailRepo, override val userMusicRepo: OgkUserMusicDetailRepo,
val userGeneralDataRepository: OgkUserGeneralDataRepo, val userGeneralDataRepository: OgkUserGeneralDataRepo
val userOptionRepo: OgkUserOptionRepo
): GameApiController<UserData>("ongeki", UserData::class) { ): GameApiController<UserData>("ongeki", UserData::class) {
override suspend fun trend(username: String) = us.cardByName(username) { card -> override suspend fun trend(username: String) = us.cardByName(username) { card ->
findTrend(playlogRepo.findByUser_Card_ExtId(card.extId) findTrend(playlogRepo.findByUser_Card_ExtId(card.extId)
@@ -54,21 +45,4 @@ class Ongeki(
genericUserSummary(card, ratingComposition) 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 package icu.samnyan.aqua.net.games.wacca
import ext.* import ext.API
import ext.RP
import ext.isoDate
import ext.utc
import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.games.*
import icu.samnyan.aqua.net.utils.waccaScores import icu.samnyan.aqua.net.utils.waccaScores
@@ -34,7 +37,7 @@ class Wacca(
val data = userDataRepo.findByCard_ExtId(card.extId) 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 } override val shownRanks: List<Pair<Int, String>> = waccaScores.filter { it.first > 85 * 10000 }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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