forked from Cookies_Github_mirror/AquaDX
Compare commits
44 Commits
7d1d6e502d
...
v1-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ddb72e28e | ||
|
|
25dbd5c874 | ||
|
|
50a9a2bdd0 | ||
|
|
6c4c9337e7 | ||
|
|
6776353556 | ||
|
|
3b19257ab1 | ||
|
|
a0cd7456ee | ||
|
|
aeafa6a396 | ||
|
|
448426a96d | ||
|
|
dfa6176689 | ||
|
|
d996fba291 | ||
|
|
0cb2a95ff3 | ||
|
|
be5220fd51 | ||
|
|
f23c0d6fe1 | ||
|
|
5aca650602 | ||
|
|
7c72348016 | ||
|
|
5eee6505f9 | ||
|
|
43b7ea65a5 | ||
|
|
85149dcd03 | ||
|
|
a767c8949c | ||
|
|
bbeb476a62 | ||
|
|
c444350cef | ||
|
|
13a318d519 | ||
|
|
e744d96c96 | ||
|
|
491044d37a | ||
|
|
9d30cf1e7d | ||
|
|
d2608472d8 | ||
|
|
34aae0c87a | ||
|
|
69bd35a579 | ||
|
|
3e6c0b4159 | ||
|
|
a33ec8b11c | ||
|
|
dd03ca38a1 | ||
|
|
1cac5e451a | ||
|
|
010d4592e4 | ||
|
|
b0d0f8ef7d | ||
|
|
967d311ee4 | ||
|
|
d5b777d720 | ||
|
|
2ab2666ad0 | ||
|
|
4971f2be78 | ||
|
|
b0a49d6626 | ||
|
|
d830854eaa | ||
|
|
68820d5a86 | ||
|
|
8b079bc40b | ||
|
|
b0dd9b845f |
49
.github/workflows/build.yml
vendored
49
.github/workflows/build.yml
vendored
@@ -1,49 +0,0 @@
|
||||
name: Gradle Build
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ v1-dev ]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: macos-arm64
|
||||
env:
|
||||
GIT_SSL_NO_VERIFY: true
|
||||
|
||||
steps:
|
||||
- name: Force Git to use HTTP/1.1 (Experimental)
|
||||
run: |
|
||||
git config --global http.version HTTP/1.1
|
||||
git config --global http.sslVerify false
|
||||
git config --global http.sslVerify false
|
||||
git config --global https.sslVerify false
|
||||
git config --global http.proxy 'http://127.0.0.1:7890'
|
||||
git config --global https.proxy 'http://127.0.0.1:7890'
|
||||
export NODE_TLS_REJECT_UNAUTHORIZED='0'
|
||||
|
||||
- name: Disable SSL verification (Temporary Fix)
|
||||
run: git config --global http.sslVerify false
|
||||
|
||||
- name: Checkout repository
|
||||
uses: https://gitee.com/github-actions/checkout@v4
|
||||
|
||||
|
||||
- name: Set up JDK
|
||||
uses: https://gitea.com/actions/setup-java@v3
|
||||
with:
|
||||
java-version: '21'
|
||||
distribution: 'temurin'
|
||||
|
||||
- name: Build with Gradle
|
||||
run: |
|
||||
mkdir data
|
||||
bash ./src/main/resources/meta/update.sh
|
||||
chmod +x gradlew
|
||||
./gradlew build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: https://gitee.com/actions-mirror/upload-artifact@v3
|
||||
with:
|
||||
name: AquaDX-1.0.0.jar
|
||||
path: build/libs
|
||||
@@ -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
|
||||
@@ -1,32 +0,0 @@
|
||||
<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}
|
||||
|
||||
60
AquaNet/src/components/Pagination.svelte
Normal file
60
AquaNet/src/components/Pagination.svelte
Normal file
@@ -0,0 +1,60 @@
|
||||
<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>
|
||||
@@ -1,7 +1,6 @@
|
||||
<script>
|
||||
import { fade } from "svelte/transition";
|
||||
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
import { t, ts } from "../../libs/i18n";
|
||||
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
|
||||
import RegionSelector from "./RegionSelector.svelte";
|
||||
@@ -11,9 +10,8 @@
|
||||
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN} class="fields">
|
||||
<blockquote>
|
||||
{ts("settings.gameNotice")}
|
||||
{ts("settings.siteNotice")}
|
||||
</blockquote>
|
||||
<GameSettingFields game="general"/>
|
||||
<div class="field">
|
||||
<div class="bool">
|
||||
<input id="rounding" type="checkbox" bind:checked={rounding.value}/>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { slide, fade } from "svelte/transition";
|
||||
import { FADE_IN, FADE_OUT } from "../../libs/config";
|
||||
import { FADE_IN, FADE_OUT, DATA_HOST } from "../../libs/config";
|
||||
import { t } from "../../libs/i18n.js";
|
||||
import Icon from "@iconify/svelte";
|
||||
import StatusOverlays from "../StatusOverlays.svelte";
|
||||
@@ -35,6 +35,169 @@
|
||||
break
|
||||
}
|
||||
}
|
||||
async function exportBatchManual() {
|
||||
submitting = "batchExport"
|
||||
const DIFFICULTY_MAP: Record<number, string> = {
|
||||
0: "Basic",
|
||||
1: "Advanced",
|
||||
2: "Expert",
|
||||
3: "Master",
|
||||
4: "Re:Master"
|
||||
}
|
||||
|
||||
const DAN_MAP: Record<number, string> = {
|
||||
1: "DAN_1",
|
||||
2: "DAN_2",
|
||||
3: "DAN_3",
|
||||
4: "DAN_4",
|
||||
5: "DAN_5",
|
||||
6: "DAN_6",
|
||||
7: "DAN_7",
|
||||
8: "DAN_8",
|
||||
9: "DAN_9",
|
||||
10: "DAN_10",
|
||||
11: "SHINDAN_1",
|
||||
12: "SHINDAN_2",
|
||||
13: "SHINDAN_3",
|
||||
14: "SHINDAN_4",
|
||||
15: "SHINDAN_5",
|
||||
16: "SHINDAN_6",
|
||||
17: "SHINDAN_7",
|
||||
18: "SHINDAN_8",
|
||||
19: "SHINDAN_9",
|
||||
20: "SHINDAN_10",
|
||||
21: "SHINKAIDEN",
|
||||
22: "URAKAIDEN"
|
||||
}
|
||||
|
||||
const CLASS_MAP: Record<number, string> = {
|
||||
0: "B5",
|
||||
1: "B4",
|
||||
2: "B3",
|
||||
3: "B2",
|
||||
4: "B1",
|
||||
5: "A5",
|
||||
6: "A4",
|
||||
7: "A3",
|
||||
8: "A2",
|
||||
9: "A1",
|
||||
10: "S5",
|
||||
11: "S4",
|
||||
12: "S3",
|
||||
13: "S2",
|
||||
14: "S1",
|
||||
15: "SS5",
|
||||
16: "SS4",
|
||||
17: "SS3",
|
||||
18: "SS2",
|
||||
19: "SS1",
|
||||
20: "SSS5",
|
||||
21: "SSS4",
|
||||
22: "SSS3",
|
||||
23: "SSS2",
|
||||
24: "SSS1",
|
||||
25: "LEGEND"
|
||||
}
|
||||
|
||||
let data: any
|
||||
let musicData: any
|
||||
let output: any = {
|
||||
"meta": {
|
||||
"game": "maimaidx",
|
||||
"playtype": "Single",
|
||||
"service": "AquaDX-Manual"
|
||||
},
|
||||
"scores": [],
|
||||
"classes": {}
|
||||
}
|
||||
try {
|
||||
musicData = await fetch(`${DATA_HOST}/d/mai2/00/all-music.json`).then(res => res.json())
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
submitting = ""
|
||||
return;
|
||||
}
|
||||
try {
|
||||
data = await GAME.export('mai2');
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
submitting = ""
|
||||
return;
|
||||
}
|
||||
if (data && "userPlaylogList" in data) {
|
||||
for (let score of data.userPlaylogList) {
|
||||
if(score.musicId > 100000){
|
||||
continue; // UTAGE charts are not supported
|
||||
}
|
||||
const musicItem = musicData[score.musicId as string];
|
||||
if (!musicItem) continue;
|
||||
let difficulty = null;
|
||||
|
||||
if (!(score.level in DIFFICULTY_MAP))
|
||||
continue;
|
||||
|
||||
const isDX = score.musicId >= 10000;
|
||||
difficulty = isDX ? `DX ${DIFFICULTY_MAP[score.level]}` : DIFFICULTY_MAP[score.level];
|
||||
|
||||
const percent = score.achievement/10000;
|
||||
|
||||
const pcrit = score.tapCriticalPerfect + score.holdCriticalPerfect + score.slideCriticalPerfect + score.touchCriticalPerfect + score.breakCriticalPerfect;
|
||||
const perfect = score.tapPerfect + score.holdPerfect + score.slidePerfect + score.touchPerfect + score.breakPerfect;
|
||||
const great = score.tapGreat + score.holdGreat + score.slideGreat + score.touchGreat + score.breakGreat;
|
||||
const good = score.tapGood + score.holdGood + score.slideGood + score.touchGood + score.breakGood;
|
||||
const miss = score.tapMiss + score.holdMiss + score.slideMiss + score.touchMiss + score.breakMiss;
|
||||
const judgements = {
|
||||
"pcrit": pcrit,
|
||||
"perfect": perfect,
|
||||
"great": great,
|
||||
"good": good,
|
||||
"miss": miss
|
||||
}
|
||||
let lamp = null;
|
||||
if (score.isAllPerfect) {
|
||||
lamp = "ALL PERFECT";
|
||||
if (score.percent == 101.0) {
|
||||
lamp = "ALL PERFECT+";
|
||||
}
|
||||
} else if (score.isFullCombo) {
|
||||
lamp = "FULL COMBO";
|
||||
if (good == 0 && great == 0) {
|
||||
lamp = "FULL COMBO+";
|
||||
}
|
||||
} else if (score.isClear) {
|
||||
lamp = "CLEAR";
|
||||
} else {
|
||||
lamp = "FAILED";
|
||||
}
|
||||
|
||||
const optional = {
|
||||
"fast": score.fastCount,
|
||||
"slow": score.lateCount,
|
||||
"maxCombo": score.maxCombo
|
||||
}
|
||||
|
||||
output.scores.push({
|
||||
"percent": percent,
|
||||
"lamp": lamp,
|
||||
"matchType": "inGameID",
|
||||
"identifier": score.musicId.toString(),
|
||||
"difficulty": difficulty,
|
||||
"timeAchieved": new Date(score.userPlayDate).getTime(),
|
||||
"judgements": judgements,
|
||||
"optional": optional
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if(data.userData.courseRank in DAN_MAP){
|
||||
output.classes["dan"] = DAN_MAP[data.userData.courseRank]
|
||||
}
|
||||
if(data.userData.classRank in CLASS_MAP){
|
||||
output.classes["matchingClass"] = CLASS_MAP[data.userData.classRank]
|
||||
}
|
||||
download(JSON.stringify(output), `AquaDX_maimai2_BatchManualExport_${values[0]}.json`)
|
||||
submitting = ""
|
||||
}
|
||||
|
||||
function exportData() {
|
||||
submitting = "export"
|
||||
@@ -70,6 +233,10 @@
|
||||
<Icon icon="bxs:file-export"/>
|
||||
{t('settings.export')}
|
||||
</button>
|
||||
<button class="exportBatchManualButton" on:click={exportBatchManual}>
|
||||
<Icon icon="bxs:file-export"/>
|
||||
{t('settings.batchManualExport')}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<StatusOverlays {error} loading={!values[0] || !!submitting}/>
|
||||
|
||||
@@ -107,7 +107,8 @@ export interface GenericGameSummary {
|
||||
lastVersion: string
|
||||
ratingComposition: { [key: string]: any }
|
||||
recent: GenericGamePlaylog[]
|
||||
rival?: boolean
|
||||
rival?: boolean,
|
||||
favorites?: number[]
|
||||
}
|
||||
|
||||
export interface MusicMeta {
|
||||
|
||||
@@ -28,6 +28,7 @@ export const EN_REF_USER = {
|
||||
'UserHome.RemoveRival': "Remove from Rival",
|
||||
'UserHome.InvalidGame': "Game ${game} is not supported on the web UI yet. We only support maimai, chunithm, wacca, and ongeki for now.",
|
||||
'UserHome.ShowMoreRecent': 'Show more',
|
||||
'UserHome.FavoriteSongs': 'Favorite Songs'
|
||||
}
|
||||
|
||||
export const EN_REF_Welcome = {
|
||||
@@ -143,23 +144,35 @@ export const EN_REF_HOME = {
|
||||
export const EN_REF_SETTINGS = {
|
||||
'settings.title': 'Settings',
|
||||
'settings.tabs.profile': 'Profile',
|
||||
'settings.tabs.game': 'Game',
|
||||
'settings.tabs.global': 'Global',
|
||||
'settings.tabs.chu3': 'Chuni',
|
||||
'settings.tabs.mai2': 'Mai',
|
||||
'settings.tabs.ongeki': 'Ongeki',
|
||||
'settings.tabs.wacca': 'Wacca',
|
||||
'settings.fields.unlockMusic.name': 'Unlock All Music',
|
||||
'settings.fields.unlockMusic.desc': 'Unlock all music and master difficulty in game.',
|
||||
'settings.fields.unlockChara.name': 'Unlock All Characters',
|
||||
'settings.fields.unlockChara.desc': 'Unlock all characters, voices, and partners in game.',
|
||||
'settings.fields.unlockCollectables.name': 'Unlock All Collectables',
|
||||
'settings.fields.unlockCollectables.desc': 'Unlock all collectables (nameplate, title, icon, frame) in game.',
|
||||
'settings.fields.unlockTickets.name': 'Unlock All Tickets',
|
||||
'settings.fields.unlockTickets.desc': 'Infinite map/ex tickets (Note: maimai still limits which tickets can be used).',
|
||||
'settings.fields.waccaInfiniteWp.name': 'Wacca: Infinite WP',
|
||||
'settings.fields.waccaInfiniteWp.desc': 'Set WP to 999999',
|
||||
'settings.fields.waccaAlwaysVip.name': 'Wacca: Always VIP',
|
||||
'settings.fields.waccaAlwaysVip.desc': 'Set VIP expiration date to 2077-01-01',
|
||||
'settings.fields.mai2UnlockMusic.name': 'Unlock All Music',
|
||||
'settings.fields.mai2UnlockMusic.desc': 'Unlock all music and master difficulty.',
|
||||
'settings.fields.mai2UnlockChara.name': 'Unlock All Characters',
|
||||
'settings.fields.mai2UnlockChara.desc': 'Unlock all characters (new characters start at level 1).',
|
||||
'settings.fields.mai2UnlockCharaMaxLevel.name': 'Max Character Level',
|
||||
'settings.fields.mai2UnlockCharaMaxLevel.desc': 'Set all characters to max level.',
|
||||
'settings.fields.mai2UnlockPartners.name': 'Unlock All Partners',
|
||||
'settings.fields.mai2UnlockPartners.desc': 'Unlock all partners.',
|
||||
'settings.fields.mai2UnlockCollectables.name': 'Unlock All Collectables',
|
||||
'settings.fields.mai2UnlockCollectables.desc': 'Unlock all collectables (nameplate, title, icon, frame).',
|
||||
'settings.fields.mai2UnlockTickets.name': 'Unlock All Tickets',
|
||||
'settings.fields.mai2UnlockTickets.desc': 'Infinite tickets (Note: client still limits which tickets can be used).',
|
||||
'settings.fields.waccaUnlockMusic.name': 'Unlock All Music',
|
||||
'settings.fields.waccaUnlockMusic.desc': 'Unlock all music.',
|
||||
'settings.fields.waccaUnlockPlates.name': 'Unlock All Plates',
|
||||
'settings.fields.waccaUnlockPlates.desc': 'Unlock all plates.',
|
||||
'settings.fields.waccaUnlockCollectables.name': 'Unlock All Collectables',
|
||||
'settings.fields.waccaUnlockCollectables.desc': 'Unlock all collectables (icon, trophy).',
|
||||
'settings.fields.waccaUnlockTickets.name': 'Infinite Tickets',
|
||||
'settings.fields.waccaUnlockTickets.desc': 'Infinite tickets.',
|
||||
'settings.fields.waccaInfiniteWp.name': 'Infinite WP',
|
||||
'settings.fields.waccaInfiniteWp.desc': 'Set WP to 999999.',
|
||||
'settings.fields.waccaAlwaysVip.name': 'Always VIP',
|
||||
'settings.fields.waccaAlwaysVip.desc': 'Set VIP expiration date to 2077-01-01.',
|
||||
'settings.fields.chusanTeamName.name': 'Team Name',
|
||||
'settings.fields.chusanTeamName.desc': 'Customize the text displayed on the top of your profile.',
|
||||
'settings.fields.chusanInfinitePenguins.name': 'Infinite Penguins',
|
||||
@@ -195,10 +208,10 @@ export const EN_REF_SETTINGS = {
|
||||
'settings.export': 'Export Player Data',
|
||||
'settings.batchManualExport': "Export in Batch Manual (for Tachi)",
|
||||
'settings.cabNotice': "Note: These settings will only affect your own cab/setup. If you're playing on someone else's setup, please contact them to change these settings.",
|
||||
'settings.gameNotice': "These only apply to Mai and Wacca.",
|
||||
'settings.regionNotice': "These only apply to Mai, Ongeki and Chuni.",
|
||||
'settings.siteNotice': "These settings only apply to the website.",
|
||||
'settings.regionNotice': "These settings are shared amongst Mai, Ongeki and Chuni.",
|
||||
'settings.regionSelector.title': "Prefecture Selector",
|
||||
'settings.regionSelector.desc': "Select the region where you want the game to think you are playing",
|
||||
'settings.regionSelector.desc': "Select the region where you want the game to identify you",
|
||||
'settings.regionSelector.select': "Select Prefecture",
|
||||
}
|
||||
|
||||
|
||||
@@ -40,6 +40,7 @@ const zhUser: typeof EN_REF_USER = {
|
||||
'UserHome.RemoveRival': "移除劲敌",
|
||||
'UserHome.InvalidGame': "游戏 ${game} 还不支持网页端查看。我们目前只支持舞萌、中二、华卡和音击。",
|
||||
'UserHome.ShowMoreRecent': "显示更多",
|
||||
'UserHome.FavoriteSongs': "收藏歌曲"
|
||||
}
|
||||
|
||||
const zhWelcome: typeof EN_REF_Welcome = {
|
||||
@@ -155,23 +156,35 @@ const zhHome: typeof EN_REF_HOME = {
|
||||
const zhSettings: typeof EN_REF_SETTINGS = {
|
||||
'settings.title': '用户设置',
|
||||
'settings.tabs.profile': '个人资料',
|
||||
'settings.tabs.game': '游戏设置',
|
||||
'settings.tabs.global': '全局',
|
||||
'settings.tabs.chu3': '中二',
|
||||
'settings.tabs.mai2': '舞萌',
|
||||
'settings.tabs.ongeki': '音击',
|
||||
'settings.tabs.wacca': '华卡',
|
||||
'settings.fields.unlockMusic.name': '解锁谱面',
|
||||
'settings.fields.unlockMusic.desc': '在游戏中解锁所有曲目和大师难度谱面。',
|
||||
'settings.fields.unlockChara.name': '解锁角色',
|
||||
'settings.fields.unlockChara.desc': '在游戏中解锁所有角色、语音和伙伴。',
|
||||
'settings.fields.unlockCollectables.name': '解锁收藏品',
|
||||
'settings.fields.unlockCollectables.desc': '在游戏中解锁所有收藏品(名牌、称号、图标、背景图)。',
|
||||
'settings.fields.unlockTickets.name': '解锁游戏券',
|
||||
'settings.fields.unlockTickets.desc': '无限跑图券/解锁券(注:maimai 客户端仍限制一些券不能使用)。',
|
||||
'settings.fields.waccaInfiniteWp.name': '华卡:无限 WP',
|
||||
'settings.fields.waccaInfiniteWp.desc': '将 WP 设置为 999999',
|
||||
'settings.fields.waccaAlwaysVip.name': '华卡:永久会员',
|
||||
'settings.fields.waccaAlwaysVip.desc': '将 VIP 到期时间设置为 2077-01-01',
|
||||
'settings.fields.mai2UnlockMusic.name': '解锁谱面',
|
||||
'settings.fields.mai2UnlockMusic.desc': '解锁所有曲目和大师难度谱面。',
|
||||
'settings.fields.mai2UnlockChara.name': '解锁角色',
|
||||
'settings.fields.mai2UnlockChara.desc': '解锁所有角色(新角色从 1 级开始)。',
|
||||
'settings.fields.mai2UnlockCharaMaxLevel.name': '角色满级',
|
||||
'settings.fields.mai2UnlockCharaMaxLevel.desc': '将所有角色设置为满级。',
|
||||
'settings.fields.mai2UnlockPartners.name': '解锁搭档',
|
||||
'settings.fields.mai2UnlockPartners.desc': '解锁所有搭档。',
|
||||
'settings.fields.mai2UnlockCollectables.name': '解锁收藏品',
|
||||
'settings.fields.mai2UnlockCollectables.desc': '解锁所有收藏品(姓名框、称号、头像、背景)。',
|
||||
'settings.fields.mai2UnlockTickets.name': '解锁功能票',
|
||||
'settings.fields.mai2UnlockTickets.desc': '无限功能票(注:客户端仍限制一些功能票不能使用)。',
|
||||
'settings.fields.waccaUnlockMusic.name': '解锁谱面',
|
||||
'settings.fields.waccaUnlockMusic.desc': '解锁所有曲目。',
|
||||
'settings.fields.waccaUnlockPlates.name': '解锁铭牌',
|
||||
'settings.fields.waccaUnlockPlates.desc': '解锁所有铭牌。',
|
||||
'settings.fields.waccaUnlockCollectables.name': '解锁收藏品',
|
||||
'settings.fields.waccaUnlockCollectables.desc': '解锁所有收藏品。',
|
||||
'settings.fields.waccaUnlockTickets.name': '无限解锁券',
|
||||
'settings.fields.waccaUnlockTickets.desc': '无限解锁券。',
|
||||
'settings.fields.waccaInfiniteWp.name': '无限 WP',
|
||||
'settings.fields.waccaInfiniteWp.desc': '将 WP 设置为 999999。',
|
||||
'settings.fields.waccaAlwaysVip.name': '永久会员',
|
||||
'settings.fields.waccaAlwaysVip.desc': '将 VIP 到期时间设置为 2077-01-01。',
|
||||
'settings.fields.chusanTeamName.name': '队伍名称',
|
||||
'settings.fields.chusanTeamName.desc': '自定义显示在个人资料顶部的文本。',
|
||||
'settings.fields.chusanInfinitePenguins.name': '我是桐谷遥',
|
||||
@@ -207,9 +220,10 @@ const zhSettings: typeof EN_REF_SETTINGS = {
|
||||
'settings.export': '导出玩家数据',
|
||||
'settings.batchManualExport': "导出 Batch Manual 格式(用于 Tachi)",
|
||||
'settings.cabNotice': '注意:下面这些设置只会影响你自己的机器,如果你是在其他人的机器上玩的话,请联系机主来改设置',
|
||||
'settings.gameNotice': "这些设置仅对舞萌和华卡生效。",
|
||||
// AI
|
||||
'settings.regionNotice': "这些设置仅适用于舞萌、音击和中二。",
|
||||
'settings.siteNotice': "这些设置仅适用于网站。",
|
||||
// AI
|
||||
'settings.regionNotice': "这些设置在舞萌、音击和中二节奏之间共享。",
|
||||
// AI
|
||||
'settings.regionSelector.title': "地区选择器",
|
||||
// AI
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
import { t } from "../libs/i18n";
|
||||
import ImportDataAction from "./Home/ImportDataAction.svelte";
|
||||
import Communities from "./Home/Communities.svelte";
|
||||
import MigrateAction from "./Home/MigrateAction.svelte";
|
||||
|
||||
USER.ensureLoggedIn();
|
||||
|
||||
@@ -59,9 +58,6 @@
|
||||
</ActionCard>
|
||||
|
||||
<ImportDataAction/>
|
||||
{#if me}
|
||||
<MigrateAction username={me.username}/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if tab === 1}
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<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>
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { onMount } from "svelte";
|
||||
import { title } from "../libs/ui";
|
||||
import { GAME } from "../libs/sdk";
|
||||
import type { GenericRanking } from "../libs/generalTypes";
|
||||
@@ -8,6 +9,7 @@
|
||||
import { t } from "../libs/i18n";
|
||||
import UserCard from "../components/UserCard.svelte";
|
||||
import Tooltip from "../components/Tooltip.svelte";
|
||||
import Pagination from "../components/Pagination.svelte";
|
||||
|
||||
export let game: GameName = 'mai2';
|
||||
|
||||
@@ -15,15 +17,45 @@
|
||||
|
||||
let d: { users: GenericRanking[] };
|
||||
let error: string | null;
|
||||
|
||||
let page = 1
|
||||
const perPage = 50
|
||||
let totalPages = 1
|
||||
|
||||
function handleUpdatePage(event: CustomEvent<number>) {
|
||||
page = event.detail;
|
||||
const url = new URL(window.location.toString())
|
||||
url.searchParams.set('page', page.toString())
|
||||
history.pushState({}, '', url.toString())
|
||||
window.scrollTo(0, 0)
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
const url = new URL(window.location.toString())
|
||||
const pageParam = url.searchParams.get('page')
|
||||
if (pageParam) {
|
||||
page = parseInt(pageParam, 10) || 1
|
||||
}
|
||||
|
||||
window.addEventListener('popstate', () => {
|
||||
const url = new URL(window.location.toString())
|
||||
const pageParam = url.searchParams.get('page')
|
||||
page = parseInt(pageParam, 10) || 1
|
||||
window.scrollTo(0, 0)
|
||||
})
|
||||
})
|
||||
|
||||
Promise.all([GAME.ranking(game)])
|
||||
.then(([users]) => {
|
||||
console.log(users)
|
||||
d = { users };
|
||||
d = { users }
|
||||
totalPages = Math.ceil(users.length / perPage)
|
||||
})
|
||||
.catch((e) => error = e.message);
|
||||
|
||||
let hoveringUser = "";
|
||||
let hoverLoading = false;
|
||||
|
||||
$: paginatedUsers = d ? d.users.slice((page - 1) * perPage, page * perPage) : []
|
||||
</script>
|
||||
|
||||
<main class="content leaderboard">
|
||||
@@ -37,8 +69,12 @@
|
||||
</div>
|
||||
|
||||
{#if d}
|
||||
{#if page > 1}
|
||||
<Pagination {page} {totalPages} on:updatePage={handleUpdatePage} />
|
||||
{/if}
|
||||
|
||||
<div class="leaderboard-container">
|
||||
<div class="lb-user" on:mouseenter={() => hoveringUser = d.users[0].username} role="heading" aria-level="2">
|
||||
<div class="lb-user" on:mouseenter={() => hoveringUser = paginatedUsers[0]?.username} role="heading" aria-level="2">
|
||||
<span class="rank">{t("Leaderboard.Rank")}</span>
|
||||
<span class="name"></span>
|
||||
<span class="rating">{t("Leaderboard.Rating")}</span>
|
||||
@@ -46,7 +82,7 @@
|
||||
<span class="fc">{t("Leaderboard.FC")}</span>
|
||||
<span class="ap">{t("Leaderboard.AP")}</span>
|
||||
</div>
|
||||
{#each d.users as user, i (user.rank)}
|
||||
{#each paginatedUsers as user, i (user.rank)}
|
||||
<div class="lb-user" class:alternate={i % 2 === 1} role="listitem"
|
||||
on:mouseover={() => hoveringUser = user.username} on:focus={() => {}}>
|
||||
|
||||
@@ -70,6 +106,8 @@
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<Pagination {page} {totalPages} on:updatePage={handleUpdatePage} />
|
||||
|
||||
<Tooltip triggeredBy=".name" loading={hoverLoading}>
|
||||
<UserCard username={hoveringUser} {game} setLoading={l => hoverLoading = l} />
|
||||
</Tooltip>
|
||||
@@ -132,5 +170,4 @@
|
||||
&.alternate
|
||||
background-color: vars.$ov-light
|
||||
|
||||
|
||||
</style>
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
let error: string;
|
||||
let submitting = ""
|
||||
let tab = 0
|
||||
let tabs = [ 'profile', 'game' ]
|
||||
let tabs = ['profile']
|
||||
|
||||
const profileFields = [
|
||||
[ 'displayName', t('settings.profile.name') ],
|
||||
@@ -45,18 +45,11 @@
|
||||
me = m
|
||||
|
||||
CARD.userGames(m.username).then(games => {
|
||||
if (games.chu3 && !tabs.includes('chu3')) {
|
||||
tabs = [...tabs, 'chu3']
|
||||
}
|
||||
if (games.mai2 && !tabs.includes('mai2')) {
|
||||
tabs = [...tabs, 'mai2']
|
||||
}
|
||||
if (games.wacca && !tabs.includes('wacca')) {
|
||||
tabs = [...tabs, 'wacca']
|
||||
}
|
||||
if (games.ongeki && !tabs.includes('ongeki')) {
|
||||
tabs = [...tabs, 'ongeki']
|
||||
}
|
||||
tabs = [
|
||||
...tabs,
|
||||
...['chu3', 'mai2','wacca', 'ongeki'].filter(v => games[v as keyof typeof games]), // :xdx:
|
||||
'global'
|
||||
]
|
||||
})
|
||||
}).catch(e => error = e.message)
|
||||
getMe()
|
||||
@@ -80,11 +73,12 @@
|
||||
// Don't know why this isn't just a part of the cropper module. Have to do this myself.. What a shame
|
||||
let canvas = document.createElement("canvas");
|
||||
let ctx = canvas.getContext("2d");
|
||||
canvas.width = 256;
|
||||
canvas.height = 256;
|
||||
const size = Math.round(Math.min(pfpCrop.width, pfpCrop.height, 1024));
|
||||
canvas.width = size;
|
||||
canvas.height = size;
|
||||
let img = document.createElement("img");
|
||||
img.onload = () => {
|
||||
ctx?.drawImage(img, pfpCrop.x, pfpCrop.y, pfpCrop.width, pfpCrop.height, 0, 0, 256, 256);
|
||||
ctx?.drawImage(img, pfpCrop.x, pfpCrop.y, pfpCrop.width, pfpCrop.height, 0, 0, size, size);
|
||||
canvas.toBlob(blob => {
|
||||
if (!blob) return;
|
||||
submitting = 'profilePicture'
|
||||
@@ -216,7 +210,7 @@
|
||||
<WaccaSettings />
|
||||
{:else if tabs[tab] === 'ongeki'}
|
||||
<OngekiSettings />
|
||||
{:else if tabs[tab] === 'game'}
|
||||
{:else if tabs[tab] === 'global'}
|
||||
<GeneralGameSettings />
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -325,7 +325,6 @@
|
||||
<RatingComposition title="Recent 10" comp={d.user.ratingComposition.recent10} {allMusics} game={game != "auto" ? game : "mai2"} top={10}/>
|
||||
{/if}
|
||||
|
||||
|
||||
<div class="recent">
|
||||
<h2>{t('UserHome.RecentScores')}</h2>
|
||||
<div class="scores">
|
||||
@@ -368,6 +367,22 @@
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if d.user.favorites != null && d.user.favorites.length > 0}
|
||||
<div class="favorites">
|
||||
<h2>{t('UserHome.FavoriteSongs')}</h2>
|
||||
<div class="scores">
|
||||
{#each d.user.favorites as favoriteSongId, i}
|
||||
<div>
|
||||
<img src={`${DATA_HOST}/d/${game}/music/00${favoriteSongId.toString().padStart(6, '0').substring(2)}.png`} alt="" on:error={coverNotFound} />
|
||||
<div class="info">
|
||||
<div class="song-title">{allMusics[favoriteSongId.toString()] ? allMusics[favoriteSongId.toString()].name : t("UserHome.UnknownSong")}</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<StatusOverlays {error} loading={!d || isLoading} />
|
||||
@@ -554,6 +569,57 @@
|
||||
flex-direction: row
|
||||
justify-content: space-between
|
||||
|
||||
.favorites
|
||||
.scores
|
||||
display: grid
|
||||
grid-template-columns: repeat(auto-fill, minmax(260px, 1fr))
|
||||
gap: 20px
|
||||
|
||||
// Image and song info
|
||||
> div
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 20px
|
||||
|
||||
background-color: rgba(white, 0.03)
|
||||
border-radius: vars.$border-radius
|
||||
|
||||
img
|
||||
width: 50px
|
||||
height: 50px
|
||||
border-radius: vars.$border-radius
|
||||
object-fit: cover
|
||||
|
||||
// Song info and score
|
||||
> div.info
|
||||
flex: 1
|
||||
display: flex
|
||||
justify-content: space-between
|
||||
overflow: hidden
|
||||
flex-direction: column
|
||||
|
||||
.first-line
|
||||
display: flex
|
||||
flex-direction: row
|
||||
|
||||
// Limit song name to one line
|
||||
.song-title
|
||||
max-width: 90%
|
||||
overflow: hidden
|
||||
text-overflow: ellipsis
|
||||
white-space: nowrap
|
||||
|
||||
// Make song score and rank not wrap
|
||||
> div:last-child
|
||||
white-space: nowrap
|
||||
|
||||
@media (max-width: vars.$w-mobile)
|
||||
flex-direction: column
|
||||
gap: 0
|
||||
|
||||
.rank-text
|
||||
text-align: left
|
||||
|
||||
// Recent Scores section
|
||||
.recent
|
||||
.scores
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import Icon from "@iconify/svelte";
|
||||
import { USER } from "../libs/sdk";
|
||||
import { t } from "../libs/i18n"
|
||||
import MunetRegisterBanner from "../components/MunetRegisterBanner.svelte";
|
||||
|
||||
let params = new URLSearchParams(window.location.search)
|
||||
|
||||
@@ -100,9 +99,6 @@
|
||||
state = 'verify'
|
||||
verifyMsg = t("welcome.verify-state-2")
|
||||
}
|
||||
else if (e.message === 'Login not allowed: Card has been migrated to Minato.') {
|
||||
location.href = `https://portal.mumur.net/login?username=${encodeURIComponent(email)}`
|
||||
}
|
||||
else {
|
||||
error = e.message
|
||||
submitting = false // unnecessary? see line 113, same for both reset functions
|
||||
@@ -221,9 +217,6 @@
|
||||
on:turnstile-expired={_ => window.location.reload()}
|
||||
on:turnstile-timeout={_ => console.log(error = t('welcome.turnstile-timeout'))} />
|
||||
{/if}
|
||||
{#if isSignup}
|
||||
<MunetRegisterBanner username={username} email={email}/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if state === "submitreset"}
|
||||
<div class="login-form" transition:slide>
|
||||
|
||||
64
README.md
64
README.md
@@ -22,59 +22,61 @@ selling it on any platform is strictly prohibited as per the CC By-NC-SA license
|
||||
|
||||
Multipurpose game server for ALL.Net games.
|
||||
|
||||
### Related Projects
|
||||
## Related Projects
|
||||
|
||||
* [AquaMai](https://github.com/MewoLab/AquaMai): A maimai DX mod that adds many features to the game.
|
||||
* [AquaNet](./AquaNet): A new web frontend for the modern age.
|
||||
* [AquaMai](https://github.com/MuNET-OSS/AquaMai): A maimai DX mod that adds many features to the game.
|
||||
* [AquaNet](./AquaNet): The primary AquaDX web frontend, hosted publically at [aquadx.net](https://aquadx.net/)
|
||||
|
||||
### Supported Games
|
||||
## Supported Games
|
||||
|
||||
Below is a list of games supported by this server.
|
||||
> [!WARNING]
|
||||
> CHUNITHM pre-NEW!! and maimai pre-DX are no longer supported after February 6th, 2026 and all associated data will be removed.
|
||||
|
||||
| Game | Ver | Codename | Thanks to |
|
||||
|------------------------|------|-------------|------------------------------------------------------|
|
||||
| SDHD: CHUNITHM | 2.30 | VERSE | |
|
||||
| SDEZ: MaiMai DX | 1.55 | PRiSM Plus | |
|
||||
| SDGA: MaiMai DX (Intl) | 1.50 | PRiSM | [@Clansty](https://github.com/clansty) |
|
||||
| SDED: Card Maker | 1.39 | | [@Becods](https://github.com/Becods) |
|
||||
| SDDT: O.N.G.E.K.I. | 1.50 | Re:Fresh | [@PenguinCaptain](https://github.com/PenguinCaptain) |
|
||||
| SBZV: Project DIVA | 7.10 | Future Tone | |
|
||||
| SDFE: Wacca (*ALPHA) | 3.07 | Reverse | |
|
||||
| Game | Latest Ver. | Initial Ver. | Notes |
|
||||
|------------------------|---------------------|---------------------|-------------------------------------------------------------|
|
||||
| SDHD: CHUNITHM | 2.40 (X-VERSE) | 2.00 (NEW) | Missing some X-VERSE features |
|
||||
| SDEZ: MaiMai DX | 1.60 (CiRCLE) | 1.00 (DX) | Missing circle (teams) support |
|
||||
| SDGA: MaiMai DX (Intl) | 1.60 (CiRCLE) | 1.00 (DX) | Thanks [@Clansty](https://github.com/clansty) |
|
||||
| SDED: Card Maker | 1.39 | N/A | Thanks [@Becods](https://github.com/Becods) |
|
||||
| SDDT: O.N.G.E.K.I. | 1.50 (Re:Fresh) | N/A | Thanks [@PenguinCaptain](https://github.com/PenguinCaptain) |
|
||||
| SBZV: Project DIVA | 7.10 | N/A | No web interface provided |
|
||||
| SDFE: Wacca | 3.07 (Reverse) | N/A | Later versions are EOS patches, network will not work |
|
||||
|
||||
<!-- A majority of them have been left as N/A for initial version as they do not appear to have restrictions -->
|
||||
|
||||
Check out these docs for more information.
|
||||
* [Game specific notes](docs/game_specific_notes.md)
|
||||
* [Frequently asked questions](docs/frequently_asked_questions.md)
|
||||
|
||||
> [!TIP]
|
||||
> Some games may require additional patches and these will not be provided in this project and repository. You already found this, so you know where to find related resources too.
|
||||
|
||||
## Usage
|
||||
If you own a cab or controller and just want to play the game, follow the instructions below:
|
||||
|
||||
1. Make sure you have obtained game files on your own (we will not provide them).
|
||||
2. Go to [aquadx.net](https://aquadx.net) and make an account.
|
||||
3. Click on "Setup Connection" in the home page, and follow the instructions.
|
||||
4. Play a coin with your card.
|
||||
(Either a physical card or the `aime.txt` / `felica.txt` in your segatools)
|
||||
5. Pet your cat 🐱
|
||||
6. Link your card on the website.
|
||||
### Public Instance
|
||||
|
||||
If you encounter any issue, please report in the [issue tracker](https://MewoLab/AquaDX/issues).
|
||||
1. Ensure your game can boot to title screen.
|
||||
2. Go to [https://aquadx.net](https://aquadx.net) and sign up (or log in).
|
||||
3. Access the Setup Connection page and follow the instructions provided.
|
||||
|
||||
If you encounter any issue, please report via Discord, QQ, (both available on the website) or the GitHub issue tracker.
|
||||
|
||||
> [!TIP]
|
||||
> If you don't know your card ID, there's always a button on the login screen of the game that can read a card's access code.
|
||||
> Your card's access code can be identified in all supported games on their title screen.<br>
|
||||
> Press the "access code" and scan your card to retrieve it.
|
||||
|
||||
## Self Hosting (Advanced)
|
||||
### Self Hosting (Advanced)
|
||||
|
||||
Please read the [self-hosting guide](docs/self-hosting.md) if you want to host your own server. This is only for advanced users and developers. Do not ask for support if you are not familiar with programming or networking.
|
||||
Please read the [self-hosting guide](docs/self-hosting.md) if you want to host your own server.
|
||||
This is only for advanced users and developers.
|
||||
Do not ask for support if you are not familiar with programming or networking.
|
||||
|
||||
## License: [CC By-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en)
|
||||
## License
|
||||
|
||||
AquaDX uses the [CC By-NC-SA](https://creativecommons.org/licenses/by-nc-sa/4.0/deed.en) license:
|
||||
|
||||
* **Attribution** — You must give appropriate credit, provide a link to the license, and indicate if changes were made. You may do so in any reasonable manner, but not in any way that suggests the licensor endorses you or your use.
|
||||
* **NonCommercial** — You may not use the material for commercial purposes.
|
||||
* **ShareAlike** — If you remix, transform, or build upon the material, you must distribute your contributions under the same license as the original.
|
||||
|
||||
### Credit
|
||||
## Attributions
|
||||
* **samnyan**: The creator and developer of the original Aqua server
|
||||
* **Akasaka Ryuunosuke**: providing all the DIVA protocol information
|
||||
* **Dom Eori**: Developer of forked Aqua server, from v0.0.17 and up
|
||||
|
||||
@@ -7,14 +7,12 @@ plugins {
|
||||
val ktVer = "2.1.10"
|
||||
|
||||
java
|
||||
kotlin("plugin.lombok") version ktVer
|
||||
kotlin("jvm") version ktVer
|
||||
kotlin("plugin.spring") version ktVer
|
||||
kotlin("plugin.jpa") version ktVer
|
||||
kotlin("plugin.serialization") version ktVer
|
||||
kotlin("plugin.allopen") version ktVer
|
||||
kotlin("kapt") version ktVer
|
||||
id("io.freefair.lombok") version "8.6"
|
||||
id("org.springframework.boot") version "3.2.3"
|
||||
id("com.github.ben-manes.versions") version "0.51.0"
|
||||
id("org.hibernate.orm") version "6.4.4.Final"
|
||||
|
||||
@@ -15,6 +15,7 @@ import kotlinx.coroutines.withContext
|
||||
import org.apache.tika.Tika
|
||||
import org.apache.tika.mime.MimeTypes
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity.BodyBuilder
|
||||
@@ -34,12 +35,15 @@ import java.util.concurrent.locks.Lock
|
||||
import kotlin.reflect.KCallable
|
||||
import kotlin.reflect.KClass
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.reflect.full.isSubclassOf
|
||||
import kotlin.reflect.full.memberProperties
|
||||
import kotlin.reflect.jvm.javaField
|
||||
import kotlin.reflect.jvm.jvmErasure
|
||||
|
||||
typealias RP = RequestParam
|
||||
typealias RB = RequestBody
|
||||
typealias RT = RequestPart
|
||||
typealias RH = RequestHeader
|
||||
typealias PV = PathVariable
|
||||
typealias API = RequestMapping
|
||||
@@ -79,7 +83,9 @@ annotation class SettingField(
|
||||
|
||||
// Reflection
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Any> KClass<T>.vars() = memberProperties.mapNotNull { it as? Var<T, Any> }
|
||||
fun <T : Any> KClass<T>.ownVars() = declaredMemberProperties.sortedBy { it.javaField?.declaringClass?.declaredFields?.indexOf(it.javaField) ?: Int.MAX_VALUE }.mapNotNull { it as? Var<T, Any> }
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T : Any> KClass<T>.vars(): List<Var<T, Any>> = supertypes.mapNotNull { it.classifier as? KClass<*> }.filter { !it.java.isInterface }.flatMap{ it.vars() as List<Var<T, Any>> } + ownVars()
|
||||
fun <T : Any> KClass<T>.varsMap() = vars().associateBy { it.name }
|
||||
fun <T : Any> KClass<T>.getters() = java.methods.filter { it.name.startsWith("get") }
|
||||
fun <T : Any> KClass<T>.gettersMap() = getters().associateBy { it.name.removePrefix("get").firstCharLower() }
|
||||
@@ -211,6 +217,8 @@ val <K, V> Map<K, V>.mut get() = toMutableMap()
|
||||
val <T> Set<T>.mut get() = toMutableSet()
|
||||
|
||||
fun <T> List<T>.unique(fn: (T) -> Any) = distinctBy(fn).ifEmpty { null }
|
||||
val <T> Collection<T>.csv get() = joinToString(",")
|
||||
val IntArray.csv get() = joinToString(",")
|
||||
|
||||
// Optionals
|
||||
operator fun <T> Optional<T>.invoke(): T? = orElse(null)
|
||||
@@ -227,6 +235,7 @@ fun Str.fromChusanUsername() = String(this.toByteArray(StandardCharsets.ISO_8859
|
||||
fun Str.truncate(len: Int) = if (this.length > len) this.take(len) + "..." else this
|
||||
val Str.some get() = ifBlank { null }
|
||||
val ByteArray.hexStr get() = toHexString()
|
||||
operator fun StringBuilder.plusAssign(other: String) { this.append(other) }
|
||||
|
||||
// Coroutine
|
||||
suspend fun <T> async(block: suspend kotlinx.coroutines.CoroutineScope.() -> T): T = withContext(Dispatchers.IO) { block() }
|
||||
@@ -255,6 +264,7 @@ operator fun <E> List<E>.component13(): E = get(12)
|
||||
|
||||
inline operator fun <reified E> List<Any?>.invoke(i: Int) = get(i) as E
|
||||
val empty = emptyList<Any>()
|
||||
val emptyMap = emptyMap<Any, Any>()
|
||||
|
||||
val <F> Pair<F, *>.l get() = component1()
|
||||
val <S> Pair<*, S>.r get() = component2()
|
||||
@@ -263,3 +273,6 @@ val <S> Pair<*, S>.r get() = component2()
|
||||
val Query.exec get() = resultList.map { (it as Array<*>).toList() }
|
||||
fun List<List<Any?>>.numCsv(vararg head: Str) = head.joinToString(",") + "\n" +
|
||||
joinToString("\n") { it.joinToString(",") }
|
||||
|
||||
// DI
|
||||
inline fun <reified T> ApplicationContext.lazy() = lazy { getBean(T::class.java) }
|
||||
|
||||
@@ -21,15 +21,15 @@ val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, obj
|
||||
else -> 400 - "Invalid boolean value ${parser.text}"
|
||||
}
|
||||
})
|
||||
val JSON_DATETIME = SimpleModule().addDeserializer(java.time.LocalDateTime::class.java, object : JsonDeserializer<java.time.LocalDateTime>() {
|
||||
val JSON_DATETIME = SimpleModule().addDeserializer(java.time.LocalDateTime::class.java, object : JsonDeserializer<LocalDateTime>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext) =
|
||||
// First try standard formats via asDateTime() method
|
||||
parser.text.asDateTime() ?: try {
|
||||
parser.text.takeIf { it.isNotEmpty() }?.run { asDateTime() ?: try {
|
||||
// Try maimai2 format (yyyy-MM-dd HH:mm:ss.0)
|
||||
LocalDateTime.parse(parser.text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0"))
|
||||
} catch (e: Exception) {
|
||||
400 - "Invalid date time value ${parser.text}"
|
||||
}
|
||||
} }
|
||||
})
|
||||
val JACKSON = jacksonObjectMapper().apply {
|
||||
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
|
||||
@@ -4,6 +4,7 @@ import ext.*
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.model.CardStatus
|
||||
import icu.samnyan.aqua.sega.general.model.sensitiveInfo
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
|
||||
@@ -52,7 +53,7 @@ class BotController(
|
||||
secret.checkSecret()
|
||||
|
||||
// 1. Find user card
|
||||
val oc = (us.cardRepo.findByLuid(card)() ?: (404 - "Card not found")).maybeGhost()
|
||||
val oc = (us.cardRepo.findByLuid(card) ?: (404 - "Card not found")).maybeGhost()
|
||||
|
||||
// 2. Change the status to migrated
|
||||
us.cardRepo.save(oc.apply {
|
||||
@@ -66,7 +67,7 @@ class BotController(
|
||||
fun clearMigrateFlag(@RP secret: Str, @RP card: Str): Any {
|
||||
secret.checkSecret()
|
||||
|
||||
val oc = (us.cardRepo.findByLuid(card)() ?: (404 - "Card not found")).maybeGhost()
|
||||
val oc = (us.cardRepo.findByLuid(card) ?: (404 - "Card not found")).maybeGhost()
|
||||
|
||||
us.cardRepo.save(oc.apply {
|
||||
status = CardStatus.NORMAL
|
||||
@@ -82,14 +83,14 @@ class BotController(
|
||||
secret.checkSecret()
|
||||
|
||||
// 1. Check if the card exist
|
||||
var cards = listOfNotNull(
|
||||
us.cardRepo.findByLuid(cardId)(),
|
||||
var cards: MutableList<Card> = listOfNotNull(
|
||||
us.cardRepo.findByLuid(cardId),
|
||||
).mut
|
||||
|
||||
cardId.toLongOrNull()?.let {
|
||||
cards += listOfNotNull(
|
||||
us.cardRepo.findById(it)(),
|
||||
us.cardRepo.findByExtId(it)(),
|
||||
us.cardRepo.findByExtId(it),
|
||||
)
|
||||
|
||||
cards += listOfNotNull(
|
||||
@@ -110,8 +111,8 @@ class BotController(
|
||||
|
||||
return cards.map { card ->
|
||||
// Find all games played by this card
|
||||
val chu3 = chu3Db.userData.findByCard_ExtId(card.extId)()
|
||||
val mai2 = mai2Db.userData.findByCard_ExtId(card.extId)()
|
||||
val chu3 = chu3Db.userData.findByCard_ExtId(card.extId)
|
||||
val mai2 = mai2Db.userData.findByCard_ExtId(card.extId)
|
||||
val gamesDict = listOfNotNull(chu3, mai2).map {
|
||||
// Find the keychip owner
|
||||
val keychip = it.lastClientId
|
||||
|
||||
@@ -8,6 +8,7 @@ import icu.samnyan.aqua.net.games.IUserData
|
||||
import icu.samnyan.aqua.net.utils.AquaNetProps
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo
|
||||
import icu.samnyan.aqua.sega.diva.PlayerProfileRepository
|
||||
import icu.samnyan.aqua.sega.general.dao.CardRepository
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
@@ -19,7 +20,6 @@ import org.springframework.scheduling.annotation.Scheduled
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import kotlin.random.Random
|
||||
|
||||
@RestController
|
||||
@@ -30,7 +30,8 @@ class CardController(
|
||||
val cardService: CardService,
|
||||
val cardGameService: CardGameService,
|
||||
val cardRepository: CardRepository,
|
||||
val props: AquaNetProps
|
||||
val props: AquaNetProps,
|
||||
val fedy: Fedy
|
||||
) {
|
||||
companion object {
|
||||
val log = logger()
|
||||
@@ -80,10 +81,12 @@ class CardController(
|
||||
val id = cardService.sanitizeCardId(cardId)
|
||||
|
||||
// Create a new card
|
||||
cardService.registerByAccessCode(id, u)
|
||||
val newCard = cardService.registerByAccessCode(id, u)
|
||||
|
||||
log.info("Net /card/link : Created new card $id for user ${u.username}")
|
||||
|
||||
fedy.onCardLinked(newCard.luid, oldExtId = null, ghostExtId = u.ghostCard.extId, emptyList())
|
||||
|
||||
return SUCCESS
|
||||
}
|
||||
|
||||
@@ -98,6 +101,8 @@ class CardController(
|
||||
val games = migrate.split(',')
|
||||
cardGameService.migrate(card, games)
|
||||
|
||||
fedy.onCardLinked(card.luid, oldExtId = card.extId, ghostExtId = u.ghostCard.extId, games)
|
||||
|
||||
log.info("Net /card/link : Linked card ${card.id} to user ${u.username} and migrated data to ${games.joinToString()}")
|
||||
|
||||
SUCCESS
|
||||
@@ -115,10 +120,14 @@ class CardController(
|
||||
// Ghost cards cannot be unlinked
|
||||
if (card.isGhost) 400 - "Account virtual cards cannot be unlinked"
|
||||
|
||||
val luid = card.luid
|
||||
|
||||
// Unbind the card
|
||||
card.aquaUser = null
|
||||
async { cardRepository.save(card) }
|
||||
|
||||
fedy.onCardUnlinked(luid)
|
||||
|
||||
log.info("Net /card/unlink : Unlinked card ${card.id} from user ${u.username}")
|
||||
|
||||
SUCCESS
|
||||
@@ -136,7 +145,7 @@ class CardController(
|
||||
*
|
||||
* Assumption: The card is already linked to the user.
|
||||
*/
|
||||
suspend fun <T : IUserData> migrateCard(repo: GenericUserDataRepo<T>, cardRepo: CardRepository, card: Card): Bool {
|
||||
suspend fun <T : IUserData> migrateCard(gameName: Str, repo: GenericUserDataRepo<T>, cardRepo: CardRepository, card: Card): Bool {
|
||||
val ghost = card.aquaUser!!.ghostCard
|
||||
|
||||
// Check if data already exists in the user's ghost card
|
||||
@@ -144,7 +153,7 @@ suspend fun <T : IUserData> migrateCard(repo: GenericUserDataRepo<T>, cardRepo:
|
||||
// Create a new dummy card for deleted data
|
||||
it.card = async {
|
||||
cardRepo.save(Card().apply {
|
||||
luid = "Migrated data of ghost card ${ghost.id} for user ${card.aquaUser!!.auId} on ${utcNow().isoDateTime()}"
|
||||
luid = "Migrated data of ghost card ${ghost.id} for user ${card.aquaUser!!.auId} on ${utcNow().isoDateTime()} (${gameName})"
|
||||
// Randomize an extId outside the normal range
|
||||
extId = Random.nextLong(0x7FFFFFF7L shl 32, 0x7FFFFFFFL shl 32)
|
||||
registerTime = LocalDateTime.now()
|
||||
@@ -162,6 +171,23 @@ suspend fun <T : IUserData> migrateCard(repo: GenericUserDataRepo<T>, cardRepo:
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun <T : IUserData> orphanData(gameName: Str, repo: GenericUserDataRepo<T>, cardRepo: CardRepository, card: Card) {
|
||||
// Orphan the data by assigning them to a dummy card
|
||||
repo.findByCard(card)?.let {
|
||||
// Create a new dummy card for orphaned data
|
||||
it.card = async {
|
||||
cardRepo.save(Card().apply {
|
||||
luid = "Unmigrated data of card ${card.luid} for user ${card.aquaUser!!.auId} on ${utcNow().isoDateTime()} (${gameName})"
|
||||
// Randomize an extId outside the normal range
|
||||
extId = Random.nextLong(0x7FFFFFF7L shl 32, 0x7FFFFFFFL shl 32)
|
||||
registerTime = LocalDateTime.now()
|
||||
accessTime = registerTime
|
||||
})
|
||||
}
|
||||
async { repo.save(it) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSummaryFor(repo: GenericUserDataRepo<*>, card: Card): Map<Str, Any>? {
|
||||
val data = async { repo.findByCard(card) } ?: return null
|
||||
return mapOf(
|
||||
@@ -177,10 +203,11 @@ class CardGameService(
|
||||
val chusan: Chu3UserDataRepo,
|
||||
val wacca: WcUserRepo,
|
||||
val ongeki: OgkUserDataRepo,
|
||||
val diva: icu.samnyan.aqua.sega.diva.dao.userdata.PlayerProfileRepository,
|
||||
val diva: PlayerProfileRepository,
|
||||
val safety: AquaNetSafetyService,
|
||||
val cardRepo: CardRepository,
|
||||
val em: EntityManager
|
||||
val em: EntityManager,
|
||||
val cardService: CardService
|
||||
) {
|
||||
companion object {
|
||||
val log = logger()
|
||||
@@ -189,18 +216,22 @@ class CardGameService(
|
||||
suspend fun migrate(crd: Card, games: List<String>) = async {
|
||||
// Migrate data from the card to the user's ghost card
|
||||
// An easy migration is to change the UserData card field to the user's ghost card
|
||||
val dataRepos = mapOf(
|
||||
"mai2" to maimai2,
|
||||
"chu3" to chusan,
|
||||
"ongeki" to ongeki,
|
||||
"wacca" to wacca,
|
||||
)
|
||||
val remainingGames = dataRepos.keys.toMutableSet()
|
||||
games.forEach { game ->
|
||||
when (game) {
|
||||
"mai2" -> migrateCard(maimai2, cardRepo, crd)
|
||||
"chu3" -> migrateCard(chusan, cardRepo, crd)
|
||||
"ongeki" -> migrateCard(ongeki, cardRepo, crd)
|
||||
"wacca" -> migrateCard(wacca, cardRepo, crd)
|
||||
// TODO: diva
|
||||
// "diva" -> diva.findByPdId(card.extId.toInt()).getOrNull()?.let {
|
||||
// it.pdId = card.aquaUser!!.ghostCard
|
||||
// }
|
||||
}
|
||||
val dataRepo = dataRepos[game] ?: return@forEach
|
||||
if (migrateCard(game, dataRepo, cardRepo, crd))
|
||||
// Update timestamp for the ghost card (data migrated in)
|
||||
cardService.updateCardTimestamp(crd.aquaUser!!.ghostCard, game, resetCreatedAt = true)
|
||||
remainingGames.remove(game)
|
||||
}
|
||||
// For remaining games, orphan the data by assigning them to a dummy card
|
||||
remainingGames.forEach { game -> orphanData(game, dataRepos[game]!!, cardRepo, crd) }
|
||||
}
|
||||
|
||||
suspend fun getSummary(card: Card) = async {
|
||||
@@ -209,7 +240,7 @@ class CardGameService(
|
||||
"chu3" to getSummaryFor(chusan, card),
|
||||
"ongeki" to getSummaryFor(ongeki, card),
|
||||
"wacca" to getSummaryFor(wacca, card),
|
||||
"diva" to diva.findByPdId(card.extId).getOrNull()?.let {
|
||||
"diva" to diva.findByPdId(card.extId)()?.let {
|
||||
mapOf(
|
||||
"name" to it.playerName,
|
||||
"rating" to it.level,
|
||||
|
||||
@@ -1,32 +1,40 @@
|
||||
package icu.samnyan.aqua.net
|
||||
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.security.MessageDigest
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserRepo
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserFedyRepo
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.net.components.EmailProperties
|
||||
import icu.samnyan.aqua.net.components.JWT
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserFedy
|
||||
import icu.samnyan.aqua.net.db.AquaGameOptions
|
||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||
import icu.samnyan.aqua.net.games.ImportController
|
||||
import icu.samnyan.aqua.net.games.mai2.Mai2Import
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.games.ExportOptions
|
||||
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPlaylogHandler as Mai2UploadUserPlaylogHandler
|
||||
import icu.samnyan.aqua.sega.maimai2.handler.UpsertUserAllHandler as Mai2UpsertUserAllHandler
|
||||
import icu.samnyan.aqua.net.utils.ApiException
|
||||
import java.util.Arrays
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.transaction.PlatformTransactionManager
|
||||
import org.springframework.transaction.support.TransactionTemplate
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
|
||||
import icu.samnyan.aqua.net.games.GenericUserDataRepo
|
||||
import icu.samnyan.aqua.net.games.IUserData
|
||||
import icu.samnyan.aqua.net.games.mai2.Mai2Import
|
||||
import icu.samnyan.aqua.net.utils.ApiException
|
||||
import icu.samnyan.aqua.net.utils.PathProps
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo
|
||||
import icu.samnyan.aqua.sega.general.dao.CardRepository
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
|
||||
import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo
|
||||
import icu.samnyan.aqua.sega.wacca.model.db.WcUserRepo
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.transaction.PlatformTransactionManager
|
||||
import org.springframework.transaction.support.TransactionTemplate
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import java.time.Instant
|
||||
import java.security.MessageDigest
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.io.path.getLastModifiedTime
|
||||
import kotlin.io.path.isRegularFile
|
||||
import kotlin.io.path.writeBytes
|
||||
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPlaylogHandler as Mai2UploadUserPlaylogHandler
|
||||
import icu.samnyan.aqua.sega.maimai2.handler.UpsertUserAllHandler as Mai2UpsertUserAllHandler
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "aqua-net.fedy")
|
||||
@@ -36,26 +44,48 @@ class FedyProps {
|
||||
var remote: String = ""
|
||||
}
|
||||
|
||||
enum class FedyEvent {
|
||||
Linked,
|
||||
Unlinked,
|
||||
Upserted,
|
||||
Imported,
|
||||
}
|
||||
data class UserProfilePicture(val url: Str, val updatedAtMs: Long)
|
||||
data class UserBasicInfo(
|
||||
val auId: Long, val ghostExtId: Long, val registrationTimeMs: Long,
|
||||
val username: Str, val displayName: Str, val email: Str, val passwordHash: Str, val profileBio: Str,
|
||||
val profilePicture: UserProfilePicture?, val gameOptions: Map<Str, Any?>?,
|
||||
)
|
||||
|
||||
private data class UserUpdatedEvent(val user: UserBasicInfo, val isNewlyCreated: Bool)
|
||||
private data class CardCreatedEvent(val luid: Str, val extId: Long)
|
||||
private data class CardLinkedEvent(val luid: Str, val oldExtId: Long?, val ghostExtId: Long, val migratedGames: List<Str>)
|
||||
private data class CardUnlinkedEvent(val luid: Str)
|
||||
private data class DataUpdatedEvent(val extId: Long, val isGhostCard: Bool, val game: Str, val removeOldData: Bool)
|
||||
|
||||
private data class FedyEvent(
|
||||
var userUpdated: UserUpdatedEvent? = null,
|
||||
var cardCreated: CardCreatedEvent? = null,
|
||||
var cardLinked: CardLinkedEvent? = null,
|
||||
var cardUnlinked: CardUnlinkedEvent? = null,
|
||||
var dataUpdated: DataUpdatedEvent? = null,
|
||||
)
|
||||
|
||||
@RestController
|
||||
@API("/api/v2/fedy")
|
||||
@API("/api/v2/fedy", consumes = ["multipart/form-data"])
|
||||
class Fedy(
|
||||
val jwt: JWT,
|
||||
val userRepo: AquaNetUserRepo,
|
||||
val userFedyRepo: AquaNetUserFedyRepo,
|
||||
val mai2Import: Mai2Import,
|
||||
val emailProps: EmailProperties,
|
||||
val cardRepo: CardRepository,
|
||||
val mai2UserDataRepo: Mai2UserDataRepo,
|
||||
val mai2UploadUserPlaylog: Mai2UploadUserPlaylogHandler,
|
||||
val mai2UpsertUserAll: Mai2UpsertUserAllHandler,
|
||||
val chu3UserDataRepo: Chu3UserDataRepo,
|
||||
val ongekiUserDataRepo: OgkUserDataRepo,
|
||||
val waccaUserDataRepo: WcUserRepo,
|
||||
val props: FedyProps,
|
||||
val transactionManager: PlatformTransactionManager
|
||||
val paths: PathProps,
|
||||
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) }
|
||||
|
||||
private fun Str.checkKey() {
|
||||
@@ -63,81 +93,116 @@ class Fedy(
|
||||
if (!MessageDigest.isEqual(this.toByteArray(), props.key.toByteArray())) 403 - "Invalid Key"
|
||||
}
|
||||
|
||||
@API("/status")
|
||||
fun handleStatus(@RP token: Str): Any {
|
||||
val user = jwt.auth(token)
|
||||
val userFedy = userFedyRepo.findByAquaNetUserAuId(user.auId)
|
||||
return mapOf("linkedAt" to (userFedy?.createdAt?.toEpochMilli() ?: 0))
|
||||
}
|
||||
|
||||
@API("/link")
|
||||
fun handleLink(@RP token: Str, @RP nonce: Str): Any {
|
||||
val user = jwt.auth(token)
|
||||
|
||||
if (userFedyRepo.findByAquaNetUserAuId(user.auId) != null) 412 - "User already linked"
|
||||
val userFedy = AquaNetUserFedy(aquaNetUser = user)
|
||||
userFedyRepo.save(userFedy)
|
||||
|
||||
notify(FedyEvent.Linked, mapOf("auId" to user.auId, "nonce" to nonce))
|
||||
return mapOf("linkedAt" to userFedy.createdAt.toEpochMilli())
|
||||
}
|
||||
|
||||
@API("/unlink")
|
||||
fun handleUnlink(@RP token: Str): Any {
|
||||
val user = jwt.auth(token)
|
||||
|
||||
val userFedy = userFedyRepo.findByAquaNetUserAuId(user.auId) ?: 412 - "User not linked"
|
||||
userFedyRepo.delete(userFedy)
|
||||
|
||||
notify(FedyEvent.Unlinked, mapOf("auId" to user.auId))
|
||||
return SUCCESS
|
||||
}
|
||||
|
||||
private fun ensureUser(auId: Long): AquaNetUser {
|
||||
val userFedy = userFedyRepo.findByAquaNetUserAuId(auId) ?: 404 - "User not linked"
|
||||
val user = userRepo.findByAuId(auId) ?: 404 - "User not found"
|
||||
return user
|
||||
}
|
||||
|
||||
data class UnlinkByRemoteReq(val auId: Long)
|
||||
@API("/unlink-by-remote")
|
||||
fun handleUnlinkByRemote(@RH(KEY_HEADER) key: Str, @RB req: UnlinkByRemoteReq): Any {
|
||||
val suppressEvents = ThreadLocal.withInitial { false }
|
||||
private fun <T> handleFedy(key: Str, block: () -> T): T {
|
||||
val old = suppressEvents.get()
|
||||
suppressEvents.set(true)
|
||||
try {
|
||||
key.checkKey()
|
||||
val user = ensureUser(req.auId)
|
||||
userFedyRepo.deleteByAquaNetUserAuId(user.auId)
|
||||
// No need to notify remote, because initiated by remote
|
||||
return SUCCESS
|
||||
return block()
|
||||
} finally { suppressEvents.set(old) }
|
||||
}
|
||||
|
||||
data class PullReq(val auId: Long, val game: Str, val exportOptions: ExportOptions)
|
||||
@API("/pull")
|
||||
fun handlePull(@RH(KEY_HEADER) key: Str, @RB req: PullReq): Any {
|
||||
key.checkKey()
|
||||
val user = ensureUser(req.auId)
|
||||
fun catched(block: () -> Any) =
|
||||
try { mapOf("result" to block()) }
|
||||
catch (e: ApiException) { mapOf("error" to mapOf("code" to e.code, "message" to e.message.toString())) }
|
||||
return when (req.game) {
|
||||
"mai2" -> catched { mai2Import.export(user, req.exportOptions) }
|
||||
data class FedyErr(val code: Int, val message: Str)
|
||||
|
||||
data class UserPullReq(val auId: Long)
|
||||
data class UserPullRes(val user: UserBasicInfo?)
|
||||
@API("/user/pull")
|
||||
fun handleUserPull(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: UserPullReq): UserPullRes = handleFedy(key) {
|
||||
UserPullRes(us.userRepo.findByAuId(req.auId)?.fedyBasicInfo())
|
||||
}
|
||||
|
||||
data class UserLookupReq(val username: Str?, val email: Str?)
|
||||
data class UserLookupRes(val user: UserBasicInfo?)
|
||||
@API("/user/lookup")
|
||||
fun handleUserLogin(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: UserLookupReq): UserLookupRes = handleFedy(key) {
|
||||
UserLookupRes(user =
|
||||
(req.username?.let { us.userRepo.findByUsernameIgnoreCase(it) } ?: req.email?.let {us.userRepo.findByEmailIgnoreCase(it) })
|
||||
?.takeIf { it.emailConfirmed || !emailProps.enable }
|
||||
?.fedyBasicInfo()
|
||||
)
|
||||
}
|
||||
|
||||
data class UserRegisterReq(val username: Str, val email: Str, val password: Str)
|
||||
data class UserRegisterRes(val error: FedyErr? = null, val user: UserBasicInfo? = null)
|
||||
@API("/user/register")
|
||||
fun handleUserRegister(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: UserRegisterReq): UserRegisterRes = handleFedy(key) {
|
||||
{
|
||||
UserRegisterRes(user = us.create(req.username, req.email, req.password, "", emailConfirmed = true).fedyBasicInfo())
|
||||
} caught { UserRegisterRes(error = it) }
|
||||
}
|
||||
|
||||
data class UserUpdateReq(val auId: Long, val fields: Map<Str, Str?>?, val gameOptions: Map<Str, Any?>?)
|
||||
data class UserUpdateRes(val error: FedyErr? = null, val user: UserBasicInfo? = null)
|
||||
@API("/user/update")
|
||||
fun handleUserUpdate(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: UserUpdateReq, @RT(PFP_PART) pfpFile: MultipartFile?): UserUpdateRes = handleFedy(key) {
|
||||
{
|
||||
val ru = us.userRepo.findByAuId(req.auId) ?: (404 - "User not found")
|
||||
val fields = req.fields?.filterValues { it != null }?.mapValues { it.value as Str } ?: emptyMap()
|
||||
fields.forEach { (k, v) ->
|
||||
if (k == "email") { ru.email = us.validateEmail(v) }
|
||||
else us.update(ru, k, v)
|
||||
}
|
||||
pfpFile?.apply {
|
||||
val mime = TIKA.detect(pfpFile.bytes).takeIf { it.startsWith("image/") } ?: (400 - "Invalid file type")
|
||||
val name = "${ru.auId}${MIMES.forName(mime)?.extension ?: ".jpg"}"
|
||||
(paths.aquaNetPortrait.path() / name).writeBytes(bytes)
|
||||
ru.profilePicture = name
|
||||
}
|
||||
req.gameOptions?.apply {
|
||||
val options = ru.gameOptions ?: AquaGameOptions().also { ru.gameOptions = it }
|
||||
forEach { (k, v) -> v?.let { GAME_OPTIONS_FIELDS[k]?.set(options, it) } }
|
||||
}
|
||||
us.userRepo.save(ru)
|
||||
if (fields.containsKey("pwHash") ?: false) { us.clearAllSessions(ru) }
|
||||
UserUpdateRes(user = ru.fedyBasicInfo())
|
||||
} caught { UserUpdateRes(error = it) }
|
||||
}
|
||||
|
||||
private fun AquaNetUser.fedyBasicInfo() = UserBasicInfo(
|
||||
auId, ghostCard.extId, regTime,
|
||||
username, displayName, email, pwHash, profileBio ?: "",
|
||||
profilePicture
|
||||
?.let { paths.aquaNetPortrait.path() / it }?.takeIf { it.isRegularFile() }
|
||||
?.let { UserProfilePicture(
|
||||
url = "/uploads/net/portrait/${profilePicture}",
|
||||
updatedAtMs = 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 DataPullResult(val data: Any?, val createdAtMs: Long, val updatedAtMs: Long, val isRebased: Bool)
|
||||
data class DataPullRes(val error: FedyErr? = null, val result: DataPullResult? = null)
|
||||
@API("/data/pull")
|
||||
fun handleDataPull(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: DataPullReq): DataPullRes = handleFedy(key) {
|
||||
val card = cardRepo.findByExtId(req.extId)
|
||||
?: (404 - "Card with extId ${req.extId} not found")
|
||||
val cardTimestamp = cardService.getCardTimestamp(card, req.game)
|
||||
if (cardTimestamp.updatedAt.toEpochMilli() == req.updatedAtMs) return@handleFedy DataPullRes(error = null, result = null) // No changes
|
||||
val isRebased = req.createdAtMs > 0 && cardTimestamp.createdAt.toEpochMilli() > req.createdAtMs
|
||||
val exportOptions = if (!isRebased) { req.exportOptions } else { req.exportOptions.copy(playlogAfter = null) }
|
||||
{
|
||||
DataPullRes(result = DataPullResult(data = when (req.game) {
|
||||
"mai2" -> mai2Import.export(card, exportOptions)
|
||||
else -> 406 - "Unsupported game"
|
||||
}
|
||||
}, createdAtMs = cardTimestamp.createdAt.toEpochMilli(), updatedAtMs = cardTimestamp.updatedAt.toEpochMilli(), isRebased = isRebased))
|
||||
} caught { DataPullRes(error = it) }
|
||||
}
|
||||
|
||||
data class PushReq(val auId: Long, val game: Str, val data: JDict, val removeOldData: Bool)
|
||||
data class DataPushReq(val extId: Long, val game: Str, val data: JDict, val removeOldData: Bool, val updatedAtMs: Long)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@API("/push")
|
||||
fun handlePush(@RH(KEY_HEADER) key: Str, @RB req: PushReq): Any {
|
||||
key.checkKey()
|
||||
val user = ensureUser(req.auId)
|
||||
val extId = user.ghostCard.extId
|
||||
@API("/data/push")
|
||||
fun handleDataPush(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: DataPushReq): Any = handleFedy(key) {
|
||||
val extId = req.extId
|
||||
fun<UserData : IUserData, UserRepo : GenericUserDataRepo<UserData>> removeOldData(repo: UserRepo) {
|
||||
val oldData = repo.findByCard_ExtId(extId)
|
||||
if (oldData.isPresent) {
|
||||
repo.findByCard_ExtId(extId)?.let { oldData ->
|
||||
log.info("Fedy: Deleting old data for $extId (${req.game})")
|
||||
repo.delete(oldData.get());
|
||||
repo.delete(oldData);
|
||||
repo.flush()
|
||||
}
|
||||
}
|
||||
val card = cardRepo.findByExtId(extId) ?: (404 - "Card not found")
|
||||
transaction.execute { when (req.game) {
|
||||
"mai2" -> {
|
||||
if (req.removeOldData) { removeOldData(mai2UserDataRepo) }
|
||||
@@ -148,30 +213,112 @@ class Fedy(
|
||||
}
|
||||
else -> 406 - "Unsupported game"
|
||||
} }
|
||||
|
||||
return SUCCESS
|
||||
cardService.updateCardTimestamp(card, req.game, now = Instant.ofEpochMilli(req.updatedAtMs), resetCreatedAt = req.removeOldData)
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
fun onUpserted(game: Str, maybeExtId: Any?) = maybeNotifyAsync(FedyEvent.Upserted, game, maybeExtId)
|
||||
fun onImported(game: Str, maybeExtId: Any?) = maybeNotifyAsync(FedyEvent.Imported, game, maybeExtId)
|
||||
data class CardResolveReq(val luid: Str, val pairedLuid: Str?, val createIfNotFound: Bool)
|
||||
data class CardResolveRes(val extId: Long, val isGhostCard: Bool, val isNewlyCreated: Bool, val isPairedLuidDiverged: Bool)
|
||||
@API("/card/resolve")
|
||||
fun handleCardResolve(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: CardResolveReq): CardResolveRes = handleFedy(key) {
|
||||
var card = cardService.tryLookup(req.luid)
|
||||
var isNewlyCreated = false
|
||||
if (card != null) {
|
||||
card = card.maybeGhost()
|
||||
if (!card.isGhost) isNewlyCreated = isCardFresh(card)
|
||||
} else if (req.createIfNotFound) {
|
||||
card = cardService.registerByAccessCode(req.luid, null)
|
||||
isNewlyCreated = true
|
||||
log.info("Fedy /card/resolve : Created new card ${card.id} (${card.luid})")
|
||||
}
|
||||
var isPairedLuidDiverged = false
|
||||
if (req.pairedLuid != null) {
|
||||
var pairedCard = cardService.tryLookup(req.pairedLuid)?.maybeGhost()
|
||||
if (pairedCard?.extId != card?.extId) {
|
||||
var isGhost = pairedCard?.isGhost == true
|
||||
var isNonFresh = pairedCard != null && !isCardFresh(pairedCard)
|
||||
if (isGhost || isNonFresh) isPairedLuidDiverged = true
|
||||
else if (card?.isGhost == true) {
|
||||
// Ensure paired card is linked, if the main card is linked
|
||||
// If the main card is not linked, there's nothing Fedy can do. It's Fedy's best effort.
|
||||
if (pairedCard == null) { pairedCard = cardService.registerByAccessCode(req.pairedLuid, card.aquaUser) }
|
||||
else { pairedCard.aquaUser = card.aquaUser; cardRepo.save(pairedCard) }
|
||||
log.info("Fedy /card/resolve : Created paired card ${pairedCard.id} (${pairedCard.luid}) for user ${card.aquaUser?.auId} (${card.aquaUser?.username})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeNotifyAsync(event: FedyEvent, game: Str, maybeExtId: Any?) = if (!props.enabled) {} else CompletableFuture.runAsync { try {
|
||||
val extId = maybeExtId?.long ?: return@runAsync
|
||||
val user = userRepo.findByGhostCardExtId(extId) ?: return@runAsync
|
||||
val userFedy = userFedyRepo.findByAquaNetUserAuId(user.auId) ?: return@runAsync
|
||||
notify(event, mapOf("auId" to user.auId, "game" to game))
|
||||
CardResolveRes(
|
||||
card?.extId ?: 0,
|
||||
card?.isGhost ?: false,
|
||||
isNewlyCreated,
|
||||
isPairedLuidDiverged)
|
||||
}
|
||||
|
||||
data class CardLinkReq(val auId: Long, val luid: Str)
|
||||
@API("/card/link")
|
||||
fun handleCardLink(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: CardLinkReq): Any = handleFedy(key) {
|
||||
val ru = us.userRepo.findByAuId(req.auId) ?: (404 - "User not found")
|
||||
var card = cardService.tryLookup(req.luid)
|
||||
if (card == null) {
|
||||
card = cardService.registerByAccessCode(req.luid, ru)
|
||||
log.info("Fedy /card/link : Linked new card ${card.id} (${card.luid}) to user ${ru.auId} (${ru.username})")
|
||||
} else {
|
||||
if (card.isGhost) 400 - "Account virtual cards cannot be unlinked"
|
||||
val cu = card.aquaUser
|
||||
if (cu != null) {
|
||||
if (cu.auId == req.auId) log.info("Fedy /card/link : Existing card ${card.id} (${card.luid}) already linked to user ${ru.auId} (${ru.username})")
|
||||
else 400 - "Card linked to another user"
|
||||
} else {
|
||||
card.aquaUser = ru
|
||||
cardRepo.save(card)
|
||||
log.info("Fedy /card/link : Linked existing card ${card.id} (${card.luid}) to user ${ru.auId} (${ru.username})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class CardUnlinkReq(val auId: Long, val luid: Str)
|
||||
@API("/card/unlink")
|
||||
fun handleCardUnlink(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: CardUnlinkReq): Any = handleFedy(key) {
|
||||
val card = cardService.tryLookup(req.luid)
|
||||
val cu = card?.aquaUser ?: return@handleFedy SUCCESS // Nothing to do
|
||||
|
||||
if (cu.auId != req.auId) 400 - "Card linked to another user"
|
||||
if (card.isGhost) 400 - "Account virtual cards cannot be unlinked"
|
||||
|
||||
card.aquaUser = null
|
||||
cardRepo.save(card)
|
||||
log.info("Fedy /card/unlink : Unlinked card ${card.id} (${card.luid}) from user ${cu.auId} (${cu.username})")
|
||||
}
|
||||
|
||||
fun onUserUpdated(u: AquaNetUser, isNew: Bool = false) = maybeNotifyAsync { FedyEvent(userUpdated = UserUpdatedEvent(u.fedyBasicInfo(), isNew)) }
|
||||
fun onCardCreated(luid: Str, extId: Long) = maybeNotifyAsync { FedyEvent(cardCreated = CardCreatedEvent(luid, extId)) }
|
||||
fun onCardLinked(luid: Str, oldExtId: Long?, ghostExtId: Long, migratedGames: List<Str>) = maybeNotifyAsync { FedyEvent(cardLinked = CardLinkedEvent(luid, oldExtId, ghostExtId, migratedGames)) }
|
||||
fun onCardUnlinked(luid: Str) = maybeNotifyAsync { FedyEvent(cardUnlinked = CardUnlinkedEvent(luid)) }
|
||||
fun onDataUpdated(extId: Long, game: Str, removeOldData: Bool) = maybeNotifyAsync {
|
||||
val card = cardRepo.findByExtId(extId) ?: return@maybeNotifyAsync null // Card not found, nothing to do
|
||||
FedyEvent(dataUpdated = DataUpdatedEvent(extId, card.isGhost, game, removeOldData))
|
||||
}
|
||||
|
||||
private fun maybeNotifyAsync(getEvent: () -> FedyEvent?) = if (!props.enabled || suppressEvents.get()) {} else CompletableFuture.runAsync {
|
||||
var event: FedyEvent? = null
|
||||
try {
|
||||
event = getEvent()
|
||||
if (event == null) return@runAsync // Nothing to do
|
||||
notify(event)
|
||||
} catch (e: Exception) {
|
||||
log.error("Error handling Fedy on maybeNotifyAsync($event, $game, $maybeExtId)", e)
|
||||
} }
|
||||
log.error("Error handling Fedy on maybeNotifyAsync($event)", e)
|
||||
}
|
||||
}.let {}
|
||||
|
||||
private fun notify(event: FedyEvent, body: Any?) {
|
||||
private fun notify(event: FedyEvent) {
|
||||
val MAX_RETRY = 3
|
||||
val body = body?.toJson() ?: "{}"
|
||||
val body = event.toJson() ?: "{}"
|
||||
var retry = 0
|
||||
var shouldRetry = true
|
||||
while (retry < MAX_RETRY) {
|
||||
while (true) {
|
||||
try {
|
||||
val response = "${props.remote.trimEnd('/')}/notify/${event.name}".request()
|
||||
val response = "${props.remote.trimEnd('/')}/notify".request()
|
||||
.header("Content-Type" to "application/json")
|
||||
.header(KEY_HEADER to props.key)
|
||||
.post(body)
|
||||
@@ -191,14 +338,33 @@ class Fedy(
|
||||
}
|
||||
}
|
||||
|
||||
// Apparently existing cards could possibly be fresh and never used in any game. Treat them as new cards.
|
||||
private fun isCardFresh(c: Card): Bool {
|
||||
fun <T : IUserData> checkForGame(repo: GenericUserDataRepo<T>, card: Card): Bool = repo.findByCard(card) != null
|
||||
return when {
|
||||
checkForGame(mai2UserDataRepo, c) -> false
|
||||
checkForGame(chu3UserDataRepo, c) -> false
|
||||
checkForGame(ongekiUserDataRepo, c) -> false
|
||||
checkForGame(waccaUserDataRepo, c) -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
private infix fun <T> (() -> T).caught(onError: (FedyErr) -> T) =
|
||||
try { this() }
|
||||
catch (e: ApiException) { onError(FedyErr(code = e.code, message = e.message.toString())) }
|
||||
|
||||
companion object
|
||||
{
|
||||
const val KEY_HEADER = "X-Fedy-Key"
|
||||
const val REQ_PART = "request"
|
||||
const val PFP_PART = "profilePicture"
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val GAME_OPTIONS_FIELDS = listOf(
|
||||
O::mai2UnlockMusic, O::mai2UnlockChara, O::mai2UnlockCharaMaxLevel, O::mai2UnlockPartners, O::mai2UnlockCollectables, O::mai2UnlockTickets
|
||||
).map { it as Var<O, Any?> }.associateBy { it.name }
|
||||
val log = logger()
|
||||
}
|
||||
}
|
||||
|
||||
fun getGameName(gameId: Str) = when (gameId) {
|
||||
"SDEZ" -> "mai2"
|
||||
else -> null // Not supported
|
||||
}
|
||||
}
|
||||
}
|
||||
typealias O = AquaGameOptions
|
||||
|
||||
@@ -19,7 +19,8 @@ class FrontierProps {
|
||||
@API("/api/v2/frontier")
|
||||
class Frontier(
|
||||
val cardService: CardService,
|
||||
val props: FrontierProps
|
||||
val props: FrontierProps,
|
||||
val fedy: Fedy
|
||||
) {
|
||||
fun Str.checkFtk() {
|
||||
if (this != props.ftk) 403 - "Invalid FTK"
|
||||
@@ -32,9 +33,12 @@ class Frontier(
|
||||
|
||||
if (accessCode.length != 20) 400 - "Invalid access code"
|
||||
// if (!accessCode.startsWith("9900")) 400 - "Frontier access code must start with 9900"
|
||||
if (async { cardService.cardRepo.findByLuid(accessCode) }.isPresent) 400 - "Card already registered"
|
||||
if (async { cardService.cardRepo.findByLuid(accessCode) } != null) 400 - "Card already registered"
|
||||
|
||||
val card = async { cardService.registerByAccessCode(accessCode) }
|
||||
|
||||
fedy.onCardCreated(accessCode, card.extId)
|
||||
|
||||
return mapOf(
|
||||
"card" to card,
|
||||
"id" to card.extId // Expose hidden ID
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package icu.samnyan.aqua.net
|
||||
|
||||
import ext.JACKSON
|
||||
import ext.invoke
|
||||
import ext.logger
|
||||
import ext.parse
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserRepo
|
||||
@@ -24,7 +23,7 @@ class Migrations(
|
||||
|
||||
@PostConstruct
|
||||
fun migrate() {
|
||||
val db = props.findByPropertyKey("migrations")() ?: PropertyEntry("migrations", "[]")
|
||||
val db = props.findByPropertyKey("migrations") ?: PropertyEntry("migrations", "[]")
|
||||
val p = JACKSON.parse<ArrayList<String>>(db.propertyValue)
|
||||
val old = p.size
|
||||
|
||||
@@ -47,7 +46,7 @@ class Migrations(
|
||||
if (c.extId > max) {
|
||||
var new = c.extId and max
|
||||
log.info("Removing signed bit: {${c.extId} -> $new} for ${c.luid}")
|
||||
while (cardRepo.findByExtId(new).isPresent) {
|
||||
while (cardRepo.findByExtId(new) != null) {
|
||||
log.error("> Conflicting card found for ${c.luid}: $new")
|
||||
new++
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ import kotlin.reflect.jvm.jvmErasure
|
||||
class SettingsApi(
|
||||
val us: AquaUserServices,
|
||||
val userRepo: AquaNetUserRepo,
|
||||
val goRepo: AquaGameOptionsRepo
|
||||
val goRepo: AquaGameOptionsRepo,
|
||||
val fedy: Fedy
|
||||
) {
|
||||
// Get all params with SettingField annotation
|
||||
val fields = AquaGameOptions::class.vars()
|
||||
@@ -41,6 +42,6 @@ class SettingsApi(
|
||||
}
|
||||
// Check field type
|
||||
field.setCast(options, value)
|
||||
goRepo.save(options)
|
||||
goRepo.save(options).also { fedy.onUserUpdated(u) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,21 +2,20 @@ package icu.samnyan.aqua.net
|
||||
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.components.*
|
||||
import icu.samnyan.aqua.net.db.*
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices.Companion.SETTING_FIELDS
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserRepo
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.db.EmailConfirmationRepo
|
||||
import icu.samnyan.aqua.net.db.ResetPasswordRepo
|
||||
import icu.samnyan.aqua.net.utils.PathProps
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.general.dao.CardRepository
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.model.CardStatus
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.io.path.writeBytes
|
||||
|
||||
@RestController
|
||||
@@ -26,15 +25,14 @@ class UserRegistrar(
|
||||
val hasher: PasswordEncoder,
|
||||
val turnstileService: TurnstileService,
|
||||
val emailService: EmailService,
|
||||
val fedy: Fedy,
|
||||
val geoIP: GeoIP,
|
||||
val jwt: JWT,
|
||||
val confirmationRepo: EmailConfirmationRepo,
|
||||
val resetPasswordRepo: ResetPasswordRepo,
|
||||
val cardRepo: CardRepository,
|
||||
val cardService: CardService,
|
||||
val validator: AquaUserServices,
|
||||
val emailProps: EmailProperties,
|
||||
val sessionRepo: SessionTokenRepo,
|
||||
final val paths: PathProps
|
||||
) {
|
||||
val portraitPath = paths.aquaNetPortrait.path()
|
||||
@@ -68,29 +66,7 @@ class UserRegistrar(
|
||||
val country = geoIP.getCountry(ip)
|
||||
|
||||
// Create user
|
||||
val u = async { AquaNetUser(
|
||||
username = validator.checkUsername(username),
|
||||
email = validator.checkEmail(email),
|
||||
pwHash = validator.checkPwHash(password),
|
||||
regTime = millis(), lastLogin = millis(), country = country,
|
||||
) }
|
||||
|
||||
// Create a ghost card
|
||||
val card = Card().apply {
|
||||
extId = cardService.randExtID(cardExtIdStart, cardExtIdEnd)
|
||||
luid = extId.toString()
|
||||
registerTime = LocalDateTime.now()
|
||||
accessTime = registerTime
|
||||
aquaUser = u
|
||||
isGhost = true
|
||||
}
|
||||
u.ghostCard = card
|
||||
|
||||
// Save the user
|
||||
async {
|
||||
userRepo.save(u)
|
||||
cardRepo.save(card)
|
||||
}
|
||||
val u = async { validator.create(username, email, password, country) }
|
||||
|
||||
// Send confirmation email
|
||||
emailService.sendConfirmation(u)
|
||||
@@ -114,8 +90,6 @@ class UserRegistrar(
|
||||
?: (400 - "User not found")
|
||||
if (!hasher.matches(password, user.pwHash)) 400 - "Invalid password"
|
||||
|
||||
if (user.ghostCard.status == CardStatus.MIGRATED_TO_MINATO) 400 - "Login not allowed: Card has been migrated to Minato."
|
||||
|
||||
// Check if email is verified
|
||||
if (!user.emailConfirmed && emailProps.enable) {
|
||||
// Check if last confirmation email was sent within a minute
|
||||
@@ -200,16 +174,13 @@ class UserRegistrar(
|
||||
if (reset.createdAt.plusSeconds(60 * 60 * 24).isBefore(Instant.now())) 400 - "Token expired"
|
||||
|
||||
// Change the password
|
||||
async { userRepo.save(reset.aquaNetUser.apply { pwHash = validator.checkPwHash(password) }) }
|
||||
val u = reset.aquaNetUser
|
||||
async { userRepo.save(u.apply { pwHash = validator.checkPwHash(password) }) }
|
||||
fedy.onUserUpdated(u)
|
||||
|
||||
// Remove the token from the list
|
||||
resetPasswordRepo.delete(reset)
|
||||
|
||||
// Clear all sessions
|
||||
sessionRepo.deleteAll(
|
||||
sessionRepo.findByAquaNetUserAuId(reset.aquaNetUser.auId)
|
||||
)
|
||||
|
||||
return SUCCESS
|
||||
}
|
||||
|
||||
@@ -227,8 +198,13 @@ class UserRegistrar(
|
||||
// Check if the token is expired
|
||||
if (confirmation.createdAt.plusSeconds(60 * 60 * 24).isBefore(Instant.now())) 400 - "Token expired"
|
||||
|
||||
// Check if the email is already confirmed
|
||||
val u = confirmation.aquaNetUser
|
||||
if (u.emailConfirmed) 400 - "Email already confirmed"
|
||||
|
||||
// Confirm the email
|
||||
async { userRepo.save(confirmation.aquaNetUser.apply { emailConfirmed = true }) }
|
||||
async { userRepo.save(u.apply { emailConfirmed = true }) }
|
||||
fedy.onUserUpdated(u, isNew = true)
|
||||
|
||||
return SUCCESS
|
||||
}
|
||||
@@ -245,22 +221,16 @@ class UserRegistrar(
|
||||
@API("/setting")
|
||||
@Doc("Validate and set a user setting field.", "Success message")
|
||||
suspend fun setting(@RP token: Str, @RP key: Str, @RP value: Str) = jwt.auth(token) { u ->
|
||||
// Check if the key is a settable field
|
||||
val field = SETTING_FIELDS.find { it.name == key } ?: (400 - "Invalid setting")
|
||||
|
||||
async {
|
||||
// Set the validated field
|
||||
field.setter.call(u, field.checker.call(validator, value))
|
||||
validator.update(u, key, value)
|
||||
|
||||
// Save the user
|
||||
userRepo.save(u)
|
||||
|
||||
// Clear all tokens if changing password
|
||||
if (key == "pwHash")
|
||||
sessionRepo.deleteAll(
|
||||
sessionRepo.findByAquaNetUserAuId(u.auId)
|
||||
)
|
||||
if (key == "pwHash") validator.clearAllSessions(u)
|
||||
}
|
||||
fedy.onUserUpdated(u)
|
||||
|
||||
SUCCESS
|
||||
}
|
||||
@@ -299,6 +269,7 @@ class UserRegistrar(
|
||||
(portraitPath / name).writeBytes(bytes)
|
||||
userRepo.save(u.apply { profilePicture = name })
|
||||
}
|
||||
fedy.onUserUpdated(u)
|
||||
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
@@ -3,11 +3,7 @@ package icu.samnyan.aqua.net.components
|
||||
import ext.Bool
|
||||
import ext.Str
|
||||
import ext.logger
|
||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||
import icu.samnyan.aqua.net.db.EmailConfirmation
|
||||
import icu.samnyan.aqua.net.db.EmailConfirmationRepo
|
||||
import icu.samnyan.aqua.net.db.ResetPassword
|
||||
import icu.samnyan.aqua.net.db.ResetPasswordRepo
|
||||
import icu.samnyan.aqua.net.db.*
|
||||
import org.simplejavamail.api.mailer.Mailer
|
||||
import org.simplejavamail.email.EmailBuilder
|
||||
import org.simplejavamail.springsupport.SimpleJavaMailSpringSupport
|
||||
|
||||
@@ -2,11 +2,7 @@ package icu.samnyan.aqua.net.components
|
||||
|
||||
import ext.Str
|
||||
import ext.minus
|
||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserRepo
|
||||
import icu.samnyan.aqua.net.db.SessionToken
|
||||
import icu.samnyan.aqua.net.db.SessionTokenRepo
|
||||
import icu.samnyan.aqua.net.db.getTokenExpiry
|
||||
import icu.samnyan.aqua.net.db.*
|
||||
import io.jsonwebtoken.JwtParser
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.security.Keys
|
||||
|
||||
@@ -2,10 +2,7 @@ package icu.samnyan.aqua.net.db
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import ext.SettingField
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.GeneratedValue
|
||||
import jakarta.persistence.GenerationType
|
||||
import jakarta.persistence.Id
|
||||
import jakarta.persistence.*
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
|
||||
@Entity
|
||||
@@ -14,21 +11,29 @@ class AquaGameOptions(
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
var id: Long = 0,
|
||||
|
||||
@SettingField("general")
|
||||
var unlockMusic: Boolean = false,
|
||||
|
||||
@SettingField("general")
|
||||
var unlockChara: Boolean = false,
|
||||
|
||||
@SettingField("general")
|
||||
var unlockCollectables: Boolean = false,
|
||||
|
||||
@SettingField("general")
|
||||
var unlockTickets: Boolean = false,
|
||||
@SettingField("mai2") @Column(name = "mai2_unlock_music")
|
||||
var mai2UnlockMusic: Boolean = false,
|
||||
@SettingField("mai2") @Column(name = "mai2_unlock_chara")
|
||||
var mai2UnlockChara: Boolean = false,
|
||||
@SettingField("mai2") @Column(name = "mai2_unlock_chara_max_level")
|
||||
var mai2UnlockCharaMaxLevel: Boolean = false,
|
||||
@SettingField("mai2") @Column(name = "mai2_unlock_partners")
|
||||
var mai2UnlockPartners: Boolean = false,
|
||||
@SettingField("mai2") @Column(name = "mai2_unlock_collectables")
|
||||
var mai2UnlockCollectables: Boolean = false,
|
||||
@SettingField("mai2") @Column(name = "mai2_unlock_tickets")
|
||||
var mai2UnlockTickets: Boolean = false,
|
||||
|
||||
@SettingField("wacca")
|
||||
var waccaUnlockMusic: Boolean = false,
|
||||
@SettingField("wacca")
|
||||
var waccaUnlockPlates: Boolean = false,
|
||||
@SettingField("wacca")
|
||||
var waccaUnlockCollectables: Boolean = false,
|
||||
@SettingField("wacca")
|
||||
var waccaUnlockTickets: Boolean = false,
|
||||
@SettingField("wacca")
|
||||
var waccaInfiniteWp: Boolean = false,
|
||||
|
||||
@SettingField("wacca")
|
||||
var waccaAlwaysVip: Boolean = false,
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.io.Serializable
|
||||
import java.time.Instant
|
||||
import java.util.UUID
|
||||
import java.util.*
|
||||
|
||||
fun getTokenExpiry() = Instant.now().plusSeconds(7 * 86400)
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package icu.samnyan.aqua.net.db
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.UserRegistrar.Companion.cardExtIdEnd
|
||||
import icu.samnyan.aqua.net.UserRegistrar.Companion.cardExtIdStart
|
||||
import icu.samnyan.aqua.net.components.JWT
|
||||
import icu.samnyan.aqua.sega.allnet.AllNetProps
|
||||
import icu.samnyan.aqua.sega.allnet.KeyChipRepo
|
||||
@@ -9,11 +11,13 @@ import icu.samnyan.aqua.sega.allnet.KeychipSession
|
||||
import icu.samnyan.aqua.sega.general.GameMusicPopularity
|
||||
import icu.samnyan.aqua.sega.general.dao.CardRepository
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import jakarta.persistence.*
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.stereotype.Service
|
||||
import java.io.Serializable
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import kotlin.reflect.KFunction
|
||||
import kotlin.reflect.KMutableProperty
|
||||
@@ -124,7 +128,9 @@ class AquaUserServices(
|
||||
val allNetProps: AllNetProps,
|
||||
val jwt: JWT,
|
||||
val em: EntityManager,
|
||||
val pop: GameMusicPopularity
|
||||
val pop: GameMusicPopularity,
|
||||
val cardService: CardService,
|
||||
val sessionRepo: SessionTokenRepo,
|
||||
) {
|
||||
companion object {
|
||||
val SETTING_FIELDS = AquaUserServices::class.functions
|
||||
@@ -136,12 +142,49 @@ class AquaUserServices(
|
||||
}
|
||||
}
|
||||
|
||||
fun create(username: Str, email: Str, password: Str, country: Str, emailConfirmed: Boolean = false): AquaNetUser {
|
||||
// Create user
|
||||
val u = AquaNetUser(
|
||||
username = checkUsername(username),
|
||||
email = validateEmail(email),
|
||||
pwHash = checkPwHash(password),
|
||||
regTime = millis(), lastLogin = millis(), country = country,
|
||||
emailConfirmed = emailConfirmed
|
||||
)
|
||||
|
||||
// Create a ghost card
|
||||
val card = Card().apply {
|
||||
extId = cardService.randExtID(cardExtIdStart, cardExtIdEnd)
|
||||
luid = extId.toString()
|
||||
registerTime = LocalDateTime.now()
|
||||
accessTime = registerTime
|
||||
aquaUser = u
|
||||
isGhost = true
|
||||
}
|
||||
u.ghostCard = card
|
||||
|
||||
// Save the user
|
||||
userRepo.save(u)
|
||||
cardRepo.save(card)
|
||||
|
||||
return u
|
||||
}
|
||||
|
||||
fun update(user: AquaNetUser, key: Str, value: Str) {
|
||||
// Check if the key is a settable field
|
||||
val field = SETTING_FIELDS.find { it.name == key } ?: (400 - "Invalid setting")
|
||||
// Set the validated field
|
||||
field.setter.call(user, field.checker.call(this, value))
|
||||
}
|
||||
|
||||
fun clearAllSessions(user: AquaNetUser) = sessionRepo.deleteAll(sessionRepo.findByAquaNetUserAuId(user.auId))
|
||||
|
||||
suspend fun <T> byName(username: Str, callback: suspend (AquaNetUser) -> T) =
|
||||
async { userRepo.findByUsernameIgnoreCase(username) }?.let { callback(it) } ?: (404 - "User not found")
|
||||
|
||||
suspend fun cardByName(username: Str) =
|
||||
if (username.startsWith("user")) username.substring(4).toLongOrNull()
|
||||
?.let { cardRepo.findById(it).getOrNull() } ?: (404 - "Card not found")
|
||||
?.let { cardRepo.findById(it)() } ?: (404 - "Card not found")
|
||||
else byName(username) { it.ghostCard }
|
||||
|
||||
suspend fun <T> cardByName(username: Str, callback: suspend (Card) -> T) = callback(cardByName(username))
|
||||
@@ -173,7 +216,7 @@ class AquaUserServices(
|
||||
400 - "User with username `$this` already exists"
|
||||
}
|
||||
|
||||
fun checkEmail(email: Str) = email.apply {
|
||||
fun validateEmail(email: Str) = email.apply {
|
||||
// Check if email is valid
|
||||
if (!isValidEmail()) 400 - "Invalid email"
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
package icu.samnyan.aqua.net.db
|
||||
|
||||
import jakarta.persistence.*
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.io.Serializable
|
||||
import java.time.Instant
|
||||
|
||||
@Entity
|
||||
@Table(name = "aqua_net_user_fedy")
|
||||
class AquaNetUserFedy(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
var id: Long = 0,
|
||||
|
||||
@Column(nullable = false)
|
||||
var createdAt: Instant = Instant.now(),
|
||||
|
||||
// Linking to the AquaNetUser
|
||||
@OneToOne
|
||||
@JoinColumn(name = "auId", referencedColumnName = "auId")
|
||||
var aquaNetUser: AquaNetUser,
|
||||
) : Serializable
|
||||
|
||||
@Repository
|
||||
interface AquaNetUserFedyRepo : JpaRepository<AquaNetUserFedy, Long> {
|
||||
fun findByAquaNetUserAuId(auId: Long): AquaNetUserFedy?
|
||||
fun deleteByAquaNetUserAuId(auId: Long): Unit
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import icu.samnyan.aqua.net.BotProps
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.model.CardStatus
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import jakarta.annotation.PostConstruct
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
@@ -27,9 +27,12 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
||||
abstract val playlogRepo: GenericPlaylogRepo<*>
|
||||
abstract val userMusicRepo: GenericUserMusicRepo<*>
|
||||
abstract val shownRanks: List<Pair<Int, String>>
|
||||
|
||||
abstract val settableFields: Map<String, (T, String) -> Unit>
|
||||
open val gettableFields: Set<String> = setOf()
|
||||
|
||||
@Autowired lateinit var cardService: CardService
|
||||
|
||||
@API("trend")
|
||||
abstract suspend fun trend(@RP username: String): List<TrendOut>
|
||||
@API("user-summary")
|
||||
@@ -56,7 +59,7 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
||||
|
||||
val reqUser = token?.let { us.jwt.auth(it) }?.let { u ->
|
||||
// Optimization: If the user is not banned, we don't need to process user information
|
||||
if (!u.ghostCard.rankingBanned && !u.cards.any { it.rankingBanned } && u.ghostCard.status == CardStatus.NORMAL) null
|
||||
if (!u.ghostCard.rankingBanned && !u.cards.any { it.rankingBanned } && u.ghostCard.status.isNormal) null
|
||||
else u
|
||||
}
|
||||
|
||||
@@ -118,19 +121,17 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
||||
}
|
||||
|
||||
@API("playlog")
|
||||
fun playlog(@RP id: Long): IGenericGamePlaylog = playlogRepo.findById(id).getOrNull() ?: (404 - "Playlog not found")
|
||||
fun playlog(@RP id: Long): IGenericGamePlaylog = playlogRepo.findById(id)() ?: (404 - "Playlog not found")
|
||||
|
||||
val userDetailFields by lazy { userDataClass.gettersMap().let { vm ->
|
||||
(settableFields.keys.toSet() + gettableFields)
|
||||
.associateWith { k -> (vm[k] ?: error("Field $k not found")) }
|
||||
} }
|
||||
|
||||
@API("user-detail")
|
||||
suspend fun userDetail(@RP username: String) = us.cardByName(username) { card ->
|
||||
val u = userDataRepo.findByCard(card) ?: (404 - "User not found")
|
||||
userDetailFields.toList().associate { (k, f) -> k to f.invoke(u) }
|
||||
}
|
||||
|
||||
@API("user-detail-set")
|
||||
suspend fun userDetailSet(@RP token: String, @RP field: String, @RP value: String): Any {
|
||||
val prop = settableFields[field] ?: (400 - "Invalid field $field")
|
||||
@@ -139,16 +140,22 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
||||
val user = async { userDataRepo.findByCard(u.ghostCard) } ?: (404 - "User not found")
|
||||
prop(user, value)
|
||||
async { userDataRepo.save(user) }
|
||||
cardService.updateCardTimestamp(u.ghostCard, name)
|
||||
SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@API("user-option")
|
||||
open suspend fun userOption(@RP token: String): Any? = 400 - "Unsupported by this game"
|
||||
@API("user-option-set")
|
||||
open suspend fun userOptionSet(@RP token: String, @RP field: String, @RP value: Int): Any = 400 - "Unsupported by this game"
|
||||
|
||||
@API("user-music-from-list")
|
||||
suspend fun userMusicFromList(@RP username: Str, @RB musicList: List<Int>) = us.cardByName(username) { card ->
|
||||
userMusicRepo.findByUser_Card_ExtIdAndMusicIdIn(card.extId, musicList)
|
||||
}
|
||||
|
||||
fun genericUserSummary(card: Card, ratingComp: Map<String, String>, rival: Boolean? = null): GenericGameSummary {
|
||||
fun genericUserSummary(card: Card, ratingComp: Map<String, String>, rival: Boolean? = null, favorites: List<Int>? = null): GenericGameSummary {
|
||||
// Summary values: total plays, player rating, server-wide ranking
|
||||
// number of each rank, max combo, number of full combo, number of all perfect
|
||||
val user = userDataRepo.findByCard(card) ?: (404 - "Game data not found")
|
||||
@@ -199,7 +206,8 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
||||
ratingComposition = ratingComp,
|
||||
recent = plays.sortedBy { it.userPlayDate.toString() }.takeLast(100).reversed(),
|
||||
lastPlayedHost = user.lastClientId?.let { us.userRepo.findByKeychip(it)?.username },
|
||||
rival = rival
|
||||
rival = rival,
|
||||
favorites = favorites
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ import java.time.LocalDate
|
||||
const val LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
"abcdefghijklmnopqrstuvwxyz" +
|
||||
"0123456789"
|
||||
const val SYMBOLS = "・:;?!~/+-×÷=♂♀∀#&*@☆○◎◇□△▽♪†‡ΣαβγθφψωДё$()._␣"
|
||||
const val SYMBOLS = "・:;?!~/+-×÷=♂♀∀#&*@☆○◎◇□△▽♪†‡ΣαβγθφψωДё$()._ "
|
||||
const val KANA = "あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよらりるれろわをん" +
|
||||
"アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワヲン"
|
||||
const val SEGA_USERNAME_CAHRS = LETTERS + SYMBOLS + KANA
|
||||
const val SEGA_USERNAME_CHARS = LETTERS + SYMBOLS + KANA
|
||||
const val WACCA_USERNAME_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
|
||||
"abcdefghijklmnopqrstuvwxyz" +
|
||||
"0123456789" +
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
package icu.samnyan.aqua.net.games
|
||||
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.Fedy
|
||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.Fedy
|
||||
import icu.samnyan.aqua.net.utils.AquaNetProps
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.repository.NoRepositoryBean
|
||||
import org.springframework.transaction.PlatformTransactionManager
|
||||
import org.springframework.transaction.support.TransactionTemplate
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.writeText
|
||||
import kotlin.reflect.KClass
|
||||
import org.springframework.context.annotation.Lazy
|
||||
|
||||
data class ExportOptions(
|
||||
val playlogAfter: String? = null
|
||||
@@ -42,13 +43,14 @@ interface IExportClass<UserModel: IUserData> {
|
||||
@NoRepositoryBean
|
||||
interface IUserRepo<UserModel, ThisModel>: JpaRepository<ThisModel, Long> {
|
||||
fun findByUser(user: UserModel): List<ThisModel>
|
||||
fun findSingleByUser(user: UserModel): Optional<ThisModel>
|
||||
fun findSingleByUser(user: UserModel): ThisModel?
|
||||
}
|
||||
|
||||
/**
|
||||
* Import controller for a game
|
||||
*
|
||||
* @param game: 4-letter Game ID
|
||||
* @param gameName: mai2/chu3/ongeki
|
||||
* @param exportFields: Mapping of type names to variables in the export model
|
||||
* (e.g. "Mai2UserCharacter" -> Mai2DataExport::userCharacterList)
|
||||
* @param exportRepos: Mapping of variables to repositories that can be used to find the data
|
||||
@@ -56,6 +58,7 @@ interface IUserRepo<UserModel, ThisModel>: JpaRepository<ThisModel, Long> {
|
||||
*/
|
||||
abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel: IUserData>(
|
||||
val game: String,
|
||||
val gameName: String,
|
||||
val exportClass: KClass<ExportModel>,
|
||||
val exportFields: Map<String, Var<ExportModel, Any>>,
|
||||
val exportRepos: Map<Var<ExportModel, Any>, IUserRepo<UserModel, *>>,
|
||||
@@ -70,7 +73,7 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
|
||||
@Autowired lateinit var netProps: AquaNetProps
|
||||
@Autowired lateinit var transManager: PlatformTransactionManager
|
||||
val trans by lazy { TransactionTemplate(transManager) }
|
||||
@Autowired @Lazy lateinit var fedy: Fedy
|
||||
@Autowired lateinit var cardService: CardService
|
||||
|
||||
init {
|
||||
artemisRenames.values.forEach {
|
||||
@@ -81,14 +84,14 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
|
||||
val listRepos = exportRepos.filter { it.key returns List::class }
|
||||
val singleRepos = exportRepos.filter { !(it.key returns List::class) }
|
||||
|
||||
fun export(u: AquaNetUser): ExportModel = export(u, ExportOptions())
|
||||
fun export(u: AquaNetUser): ExportModel = export(u.ghostCard, ExportOptions())
|
||||
|
||||
fun export(u: AquaNetUser, options: ExportOptions) = createEmpty().apply {
|
||||
fun export(c: Card, options: ExportOptions) = createEmpty().apply {
|
||||
gameId = game
|
||||
userData = userDataRepo.findByCard(u.ghostCard) ?: (404 - "User not found")
|
||||
userData = userDataRepo.findByCard(c) ?: (404 - "User not found")
|
||||
exportRepos.forEach { (f, u) ->
|
||||
if (f returns List::class) f.set(this, u.findByUser(userData))
|
||||
else u.findSingleByUser(userData)()?.let { f.set(this, it) }
|
||||
else u.findSingleByUser(userData)?.let { f.set(this, it) }
|
||||
}
|
||||
customExporters.forEach { (f, exporter) ->
|
||||
exporter(userData, options)?.let { f.set(this, it) }
|
||||
@@ -147,7 +150,7 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
|
||||
}
|
||||
}
|
||||
|
||||
Fedy.getGameName(game)?.let { fedy.onImported(it, u.ghostCard.extId) }
|
||||
cardService.updateCardTimestamp(u.ghostCard, gameName, resetCreatedAt = true)
|
||||
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
@@ -47,7 +47,8 @@ data class GenericGameSummary(
|
||||
|
||||
val recent: List<IGenericGamePlaylog>,
|
||||
|
||||
val rival: Boolean?
|
||||
val rival: Boolean?,
|
||||
val favorites: List<Int>?
|
||||
)
|
||||
|
||||
data class GenericRankingPlayer(
|
||||
@@ -124,7 +125,7 @@ open class BaseEntity(
|
||||
@NoRepositoryBean
|
||||
interface GenericUserDataRepo<T : IUserData> : JpaRepository<T, Long> {
|
||||
fun findByCard(card: Card): T?
|
||||
fun findByCard_ExtId(extId: Long): Optional<T>
|
||||
fun findByCard_ExtId(extId: Long): T?
|
||||
|
||||
@Query("select e from #{#entityName} e where e.card.rankingBanned = false")
|
||||
fun findAllNonBanned(): List<T>
|
||||
|
||||
@@ -17,7 +17,7 @@ import kotlin.reflect.full.declaredMembers
|
||||
class Chu3Import(
|
||||
val repos: Chu3Repos,
|
||||
) : ImportController<Chu3DataExport, Chu3UserData>(
|
||||
"SDHD", Chu3DataExport::class,
|
||||
"SDHD", "chu3", Chu3DataExport::class,
|
||||
exportFields = Chu3DataExport::class.vars().associateBy {
|
||||
it.name.replace("List", "").lowercase()
|
||||
},
|
||||
|
||||
@@ -3,10 +3,18 @@ package icu.samnyan.aqua.net.games.chu3
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.games.*
|
||||
import icu.samnyan.aqua.net.utils.*
|
||||
import icu.samnyan.aqua.sega.chusan.model.*
|
||||
import icu.samnyan.aqua.net.utils.chu3Scores
|
||||
import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
|
||||
import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo
|
||||
import icu.samnyan.aqua.sega.chusan.model.Chu3UserMusicDetailRepo
|
||||
import icu.samnyan.aqua.sega.chusan.model.Chu3UserPlaylogRepo
|
||||
import icu.samnyan.aqua.sega.chusan.model.userdata.Chu3UserData
|
||||
import icu.samnyan.aqua.sega.chusan.model.userdata.UserGameOption
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import kotlin.reflect.full.memberProperties
|
||||
|
||||
@RestController
|
||||
@API("api/v2/game/chu3")
|
||||
@@ -25,7 +33,7 @@ class Chusan(
|
||||
// Only show > AAA rank
|
||||
override val shownRanks = chu3Scores.filter { it.first >= 95 * 10000 }
|
||||
override val settableFields: Map<String, (Chu3UserData, String) -> Unit> by lazy { mapOf(
|
||||
"userName" to usernameCheck(SEGA_USERNAME_CAHRS),
|
||||
"userName" to usernameCheck(SEGA_USERNAME_CHARS),
|
||||
"nameplateId" to { u, v -> u.nameplateId = v.int },
|
||||
"frameId" to { u, v -> u.frameId = v.int },
|
||||
"trophyId" to { u, v -> u.trophyId = v.int },
|
||||
@@ -33,6 +41,7 @@ class Chusan(
|
||||
"trophyIdSub2" to { u, v -> u.trophyIdSub2 = v.int },
|
||||
"mapIconId" to { u, v -> u.mapIconId = v.int },
|
||||
"voiceId" to { u, v -> u.voiceId = v.int },
|
||||
"characterId" to { u, v -> u.characterId = v.int },
|
||||
"avatarWear" to { u, v -> u.avatarWear = v.int },
|
||||
"avatarHead" to { u, v -> u.avatarHead = v.int },
|
||||
"avatarFace" to { u, v -> u.avatarFace = v.int },
|
||||
@@ -44,7 +53,7 @@ class Chusan(
|
||||
"lastRomVersion" to { u, v -> u.lastRomVersion = v },
|
||||
"lastDataVersion" to { u, v -> u.lastDataVersion = v },
|
||||
) }
|
||||
override val gettableFields: Set<String> = setOf("level", "playerRating", "characterId")
|
||||
override val gettableFields: Set<String> = setOf("level", "playerRating")
|
||||
|
||||
override suspend fun userSummary(@RP username: Str, @RP token: String?) = us.cardByName(username) { card ->
|
||||
// Summary values: total plays, player rating, server-wide ranking
|
||||
@@ -60,7 +69,9 @@ class Chusan(
|
||||
"new" to (extra["rating_new_list"] ?: ""),
|
||||
)
|
||||
|
||||
genericUserSummary(card, ratingComposition)
|
||||
val misc = rp.userMisc.findByUser_Card_ExtId(card.extId).firstOrNull()
|
||||
|
||||
genericUserSummary(card, ratingComposition, null, misc?.favMusic)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -92,6 +103,23 @@ 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)
|
||||
val property = UserGameOption::class.memberProperties.filterIsInstance<KMutableProperty1<Any, Any?>>().find{ it.name == field }
|
||||
|
||||
if (property != null && gameOptions != null) {
|
||||
property.setter.call(gameOptions, value)
|
||||
rp.userGameOption.save(gameOptions)
|
||||
200 - "Success"
|
||||
} else
|
||||
400 - "Invalid parameters"
|
||||
}
|
||||
|
||||
// UserBox related APIs
|
||||
@API("user-box")
|
||||
fun userBox(@RP token: String) = us.jwt.auth(token) {
|
||||
|
||||
@@ -14,12 +14,13 @@ import icu.samnyan.aqua.sega.maimai2.model.userdata.*
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import kotlin.reflect.full.declaredMembers
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@RestController
|
||||
@API("api/v2/game/mai2")
|
||||
class Mai2Import(
|
||||
val repos: Mai2Repos,
|
||||
) : ImportController<Maimai2DataExport, Mai2UserDetail>(
|
||||
"SDEZ", Maimai2DataExport::class,
|
||||
"SDEZ", "mai2", Maimai2DataExport::class,
|
||||
exportFields = Maimai2DataExport::class.vars().associateBy {
|
||||
it.name.replace("List", "").lowercase()
|
||||
},
|
||||
@@ -61,7 +62,7 @@ class Mai2Import(
|
||||
}
|
||||
},
|
||||
Maimai2DataExport::userFavoriteMusicList to { user: Mai2UserDetail, _: ExportOptions ->
|
||||
repos.userGeneralData.findByUserAndPropertyKey(user, "favorite_music").orElse(null)
|
||||
repos.userGeneralData.findByUserAndPropertyKey(user, "favorite_music")
|
||||
?.propertyValue
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.split(",")
|
||||
@@ -78,7 +79,7 @@ class Mai2Import(
|
||||
if (favoriteMusicList.isNotEmpty()) {
|
||||
val key = "favorite_music"
|
||||
// This field always imports as incremental, since the userGeneralData field (for backwards compatibility) is processed before this
|
||||
val data = repos.userGeneralData.findByUserAndPropertyKey(user, key).orElse(null)
|
||||
val data = repos.userGeneralData.findByUserAndPropertyKey(user, key)
|
||||
?: Mai2UserGeneralData().apply { this.user = user; propertyKey = key }
|
||||
repos.userGeneralData.save(data.apply {
|
||||
propertyValue = favoriteMusicList.sortedBy { it.orderId }.map { it.id }.joinToString(",")
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
package icu.samnyan.aqua.net.games.mai2
|
||||
|
||||
import ext.*
|
||||
import ext.API
|
||||
import ext.RB
|
||||
import ext.RP
|
||||
import ext.minus
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserMusicDetail
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
@@ -13,32 +17,28 @@ import org.springframework.web.bind.annotation.RestController
|
||||
class Mai2MusicDetailImport(
|
||||
val us: AquaUserServices,
|
||||
val repos: Mai2Repos,
|
||||
val cardService: CardService,
|
||||
) {
|
||||
@PostMapping("import-music-detail")
|
||||
suspend fun importMusicDetail(@RP token: String, @RB data: List<Mai2UserMusicDetail>) = us.jwt.auth(token) { u ->
|
||||
us.cardByName(u.username) { card ->
|
||||
val user = repos.userData.findByCardExtId(card.extId).orElse(null) ?: (404 - "User not found")
|
||||
val user = repos.userData.findByCardExtId(card.extId) ?: (404 - "User not found")
|
||||
data.forEach { newMusic ->
|
||||
val musicRec = repos.userMusicDetail.findByUserAndMusicIdAndLevel(user, newMusic.musicId, newMusic.level)
|
||||
if (musicRec.isPresent) {
|
||||
val music = musicRec.get()
|
||||
newMusic.user = user
|
||||
repos.userMusicDetail.findByUserAndMusicIdAndLevel(user, newMusic.musicId, newMusic.level)?.let { m ->
|
||||
newMusic.apply {
|
||||
id = music.id
|
||||
this.user = user
|
||||
achievement = achievement.coerceAtLeast(music.achievement)
|
||||
scoreRank = scoreRank.coerceAtLeast(music.scoreRank)
|
||||
comboStatus = comboStatus.coerceAtLeast(music.comboStatus)
|
||||
syncStatus = syncStatus.coerceAtLeast(music.syncStatus)
|
||||
deluxscoreMax = deluxscoreMax.coerceAtLeast(music.deluxscoreMax)
|
||||
playCount = playCount.coerceAtLeast(music.playCount)
|
||||
}
|
||||
} else {
|
||||
newMusic.apply {
|
||||
this.user = user
|
||||
id = m.id
|
||||
achievement = achievement.coerceAtLeast(m.achievement)
|
||||
scoreRank = scoreRank.coerceAtLeast(m.scoreRank)
|
||||
comboStatus = comboStatus.coerceAtLeast(m.comboStatus)
|
||||
syncStatus = syncStatus.coerceAtLeast(m.syncStatus)
|
||||
deluxscoreMax = deluxscoreMax.coerceAtLeast(m.deluxscoreMax)
|
||||
playCount = playCount.coerceAtLeast(m.playCount)
|
||||
}
|
||||
}
|
||||
}
|
||||
repos.userMusicDetail.saveAll(data)
|
||||
cardService.updateCardTimestamp(card, "mai2")
|
||||
SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,23 @@ package icu.samnyan.aqua.net.games.mai2
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.games.*
|
||||
import icu.samnyan.aqua.net.utils.*
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.net.utils.mai2Scores
|
||||
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPhotoHandler
|
||||
import icu.samnyan.aqua.sega.maimai2.model.*
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.*
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserMusicDetailRepo
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserPlaylogRepo
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserDetail
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserGeneralData
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserLoginBonus
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserOption
|
||||
import org.springframework.http.MediaType
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.util.*
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.full.memberProperties
|
||||
|
||||
@RestController
|
||||
@API("api/v2/game/mai2")
|
||||
@@ -29,7 +38,7 @@ class Maimai2(
|
||||
// Only show > S rank
|
||||
override val shownRanks = mai2Scores.filter { it.first >= 97 * 10000 }
|
||||
override val settableFields: Map<String, (Mai2UserDetail, String) -> Unit> by lazy { mapOf(
|
||||
"userName" to usernameCheck(SEGA_USERNAME_CAHRS),
|
||||
"userName" to usernameCheck(SEGA_USERNAME_CHARS),
|
||||
"iconId" to { u, v -> u.iconId = v.int() },
|
||||
"plateId" to { u, v -> u.plateId = v.int() },
|
||||
"titleId" to { u, v -> u.titleId = v.int() },
|
||||
@@ -58,15 +67,15 @@ class Maimai2(
|
||||
us.jwt.auth(t) { u ->
|
||||
if (u.username == username) return@auth null
|
||||
us.cardByName(u.username) { myCard ->
|
||||
val user = repos.userData.findByCardExtId(card.extId).orElse(null) ?: (404 - "User not found")
|
||||
val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival")
|
||||
.map { it.propertyValue.split(',') }.orElse(emptyList()).filter { it.isNotEmpty() }.map { it.long() }
|
||||
val user = repos.userData.findByCardExtId(card.extId) ?: (404 - "User not found")
|
||||
val myRival = (repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival")?.propertyValue?.split(',') ?: emptyList())
|
||||
.filter { it.isNotEmpty() }.map { it.long() }
|
||||
myRival.contains(user.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
genericUserSummary(card, ratingComposition, isMyRival)
|
||||
genericUserSummary(card, ratingComposition, isMyRival, extra["favorite_music"]?.split(",")?.mapNotNull{it -> it.toIntOrNull()})
|
||||
}
|
||||
|
||||
@API("user-rating")
|
||||
@@ -110,6 +119,7 @@ class Maimai2(
|
||||
val user = userDataRepo.findByCard(card) ?: (404 - "User not found")
|
||||
user.userName = newNameFull
|
||||
userDataRepo.save(user)
|
||||
cardService.updateCardTimestamp(card, "mai2")
|
||||
}
|
||||
mapOf("newName" to newNameFull)
|
||||
}
|
||||
@@ -132,17 +142,36 @@ class Maimai2(
|
||||
if (loginBonus.none { it.bonusId == bonusId }) {
|
||||
// create one
|
||||
val newBonus = Mai2UserLoginBonus().apply {
|
||||
user = repos.userData.findByCardExtId(card.extId).orElse(null) ?: (404 - "User not found")
|
||||
user = repos.userData.findByCardExtId(card.extId) ?: (404 - "User not found")
|
||||
this.bonusId = bonusId
|
||||
isCurrent = true
|
||||
}
|
||||
loginBonus.add(newBonus)
|
||||
}
|
||||
repos.userLoginBonus.saveAll(loginBonus)
|
||||
cardService.updateCardTimestamp(card, "mai2")
|
||||
}
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
@API("user-option")
|
||||
override suspend fun userOption(@RP token: String) = us.jwt.auth(token) { u ->
|
||||
repos.userOption.findByUser_Card_ExtId(u.ghostCard.extId).getOrNull(0)
|
||||
}
|
||||
@API("user-option-set")
|
||||
override suspend fun userOptionSet(@RP token: String, @RP field: String, @RP value: Int): Any = us.jwt.auth(token) { u ->
|
||||
val gameOptions = repos.userOption.findSingleByUser_Card_ExtId(u.ghostCard.extId)
|
||||
val property = Mai2UserOption::class.memberProperties.filterIsInstance<KMutableProperty1<Any, Any?>>().find{ it.name == field }
|
||||
|
||||
if (property != null && gameOptions != null) {
|
||||
property.setter.call(gameOptions, value)
|
||||
repos.userOption.save(gameOptions)
|
||||
200 - "Success"
|
||||
} else
|
||||
400 - "Invalid parameters"
|
||||
|
||||
}
|
||||
|
||||
@API("owned-items")
|
||||
suspend fun ownedItems(@RP token: String) = us.jwt.auth(token) { u ->
|
||||
us.cardByName(u.username) { card ->
|
||||
@@ -154,10 +183,10 @@ class Maimai2(
|
||||
suspend fun setRival(@RP token: String, @RP rivalUserName: String, @RP isAdd: Boolean) = us.jwt.auth(token) { u ->
|
||||
us.cardByName(u.username) { myCard ->
|
||||
val rivalCard = us.cardByName(rivalUserName) { it }
|
||||
val rivalUser = repos.userData.findByCardExtId(rivalCard.extId).orElse(null) ?: (404 - "User not found")
|
||||
val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival").orElse(null)
|
||||
val rivalUser = repos.userData.findByCardExtId(rivalCard.extId) ?: (404 - "User not found")
|
||||
val myRival = repos.userGeneralData.findByUser_Card_ExtIdAndPropertyKey(myCard.extId, "favorite_rival")
|
||||
?: Mai2UserGeneralData().apply {
|
||||
user = repos.userData.findByCardExtId(myCard.extId).orElse(null) ?: (404 - "User not found")
|
||||
user = repos.userData.findByCardExtId(myCard.extId) ?: (404 - "User not found")
|
||||
propertyKey = "favorite_rival"
|
||||
}
|
||||
val myRivalList = myRival.propertyValue.split(',').filter { it.isNotEmpty() }.mut
|
||||
@@ -172,6 +201,7 @@ class Maimai2(
|
||||
|
||||
myRival.propertyValue = myRivalList.joinToString(",")
|
||||
repos.userGeneralData.save(myRival)
|
||||
cardService.updateCardTimestamp(myCard, "mai2")
|
||||
}
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
@@ -1,15 +1,22 @@
|
||||
package icu.samnyan.aqua.net.games.ongeki
|
||||
|
||||
import ext.API
|
||||
import ext.RP
|
||||
import ext.minus
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.games.*
|
||||
import icu.samnyan.aqua.net.utils.*
|
||||
import icu.samnyan.aqua.net.utils.ongekiScores
|
||||
import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo
|
||||
import icu.samnyan.aqua.sega.ongeki.OgkUserGeneralDataRepo
|
||||
import icu.samnyan.aqua.sega.ongeki.OgkUserMusicDetailRepo
|
||||
import icu.samnyan.aqua.sega.ongeki.OgkUserOptionRepo
|
||||
import icu.samnyan.aqua.sega.ongeki.OgkUserPlaylogRepo
|
||||
import icu.samnyan.aqua.sega.ongeki.model.UserData
|
||||
import icu.samnyan.aqua.sega.ongeki.model.UserOption
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import kotlin.reflect.KMutableProperty1
|
||||
import kotlin.reflect.full.memberProperties
|
||||
|
||||
@RestController
|
||||
@API("api/v2/game/ongeki")
|
||||
@@ -18,7 +25,8 @@ class Ongeki(
|
||||
override val playlogRepo: OgkUserPlaylogRepo,
|
||||
override val userDataRepo: OgkUserDataRepo,
|
||||
override val userMusicRepo: OgkUserMusicDetailRepo,
|
||||
val userGeneralDataRepository: OgkUserGeneralDataRepo
|
||||
val userGeneralDataRepository: OgkUserGeneralDataRepo,
|
||||
val userOptionRepo: OgkUserOptionRepo
|
||||
): GameApiController<UserData>("ongeki", UserData::class) {
|
||||
override suspend fun trend(username: String) = us.cardByName(username) { card ->
|
||||
findTrend(playlogRepo.findByUser_Card_ExtId(card.extId)
|
||||
@@ -27,7 +35,7 @@ class Ongeki(
|
||||
|
||||
override val shownRanks = ongekiScores.filter { it.first >= 950000 }
|
||||
override val settableFields: Map<String, (UserData, String) -> Unit> by lazy { mapOf(
|
||||
"userName" to usernameCheck(SEGA_USERNAME_CAHRS),
|
||||
"userName" to usernameCheck(SEGA_USERNAME_CHARS),
|
||||
|
||||
"lastRomVersion" to { u, v -> u.lastRomVersion = v },
|
||||
"lastDataVersion" to { u, v -> u.lastDataVersion = v },
|
||||
@@ -45,4 +53,21 @@ class Ongeki(
|
||||
|
||||
genericUserSummary(card, ratingComposition)
|
||||
}
|
||||
|
||||
@API("user-option")
|
||||
override suspend fun userOption(@RP token: String) = us.jwt.auth(token) { u ->
|
||||
userOptionRepo.findByUser_Card_ExtId(u.ghostCard.extId).getOrNull(0)
|
||||
}
|
||||
@API("user-option-set")
|
||||
override suspend fun userOptionSet(@RP token: String, @RP field: String, @RP value: Int): Any = us.jwt.auth(token) { u ->
|
||||
val gameOptions = userOptionRepo.findSingleByUser_Card_ExtId(u.ghostCard.extId)
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package icu.samnyan.aqua.net.games.wacca
|
||||
|
||||
import ext.*
|
||||
import ext.API
|
||||
import ext.RP
|
||||
import ext.isoDate
|
||||
import ext.utc
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.games.*
|
||||
import icu.samnyan.aqua.net.utils.waccaScores
|
||||
@@ -31,7 +34,10 @@ class Wacca(
|
||||
|
||||
override suspend fun userSummary(@RP username: String, @RP token: String?) = us.cardByName(username) { card ->
|
||||
// TODO: Rating composition
|
||||
genericUserSummary(card, mapOf())
|
||||
|
||||
val data = userDataRepo.findByCard_ExtId(card.extId)
|
||||
|
||||
genericUserSummary(card, mapOf(), null, data?.favoriteSongs)
|
||||
}
|
||||
|
||||
override val shownRanks: List<Pair<Int, String>> = waccaScores.filter { it.first > 85 * 10000 }
|
||||
|
||||
@@ -3,10 +3,10 @@ package icu.samnyan.aqua.net.transfer
|
||||
import ext.header
|
||||
import ext.post
|
||||
import ext.request
|
||||
import java.net.URI
|
||||
import icu.samnyan.aqua.sega.aimedb.AimeDbClient
|
||||
import icu.samnyan.aqua.sega.allnet.AllNetBillingDecoder
|
||||
import icu.samnyan.aqua.sega.allnet.AllNetBillingDecoder.decodeAllNetResp
|
||||
import java.net.URI
|
||||
|
||||
val keychipPattern = Regex("([A-Z\\d]{4}-[A-Z\\d]{11}|[A-Z\\d]{11})")
|
||||
|
||||
|
||||
@@ -11,9 +11,9 @@ import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserItem
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserMusicDetail
|
||||
import icu.samnyan.aqua.sega.ongeki.model.OngekiUpsertUserAll
|
||||
import icu.samnyan.aqua.sega.ongeki.model.UserItem
|
||||
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
|
||||
import icu.samnyan.aqua.sega.util.jackson.IMapper
|
||||
import icu.samnyan.aqua.sega.util.jackson.StringMapper
|
||||
import icu.samnyan.aqua.sega.util.BasicMapper
|
||||
import icu.samnyan.aqua.sega.util.IMapper
|
||||
import icu.samnyan.aqua.sega.util.StringMapper
|
||||
|
||||
|
||||
abstract class DataBroker(
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package icu.samnyan.aqua.sega.aimedb
|
||||
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.BotProps
|
||||
import ext.logger
|
||||
import ext.toHex
|
||||
import icu.samnyan.aqua.net.Fedy
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.sega.allnet.AllNetProps
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.model.CardStatus
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.ByteBufUtil
|
||||
@@ -26,6 +26,7 @@ class AimeDB(
|
||||
val cardService: CardService,
|
||||
val us: AquaUserServices,
|
||||
val allNetProps: AllNetProps,
|
||||
val fedy: Fedy,
|
||||
): ChannelInboundHandlerAdapter() {
|
||||
val logger = logger()
|
||||
|
||||
@@ -68,9 +69,9 @@ class AimeDB(
|
||||
*/
|
||||
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
|
||||
if (msg !is Map<*, *>) return
|
||||
try {
|
||||
val type = msg["type"] as Int
|
||||
val data = msg["data"] as ByteBuf
|
||||
try {
|
||||
val base = data.decodeHeader()
|
||||
val handler = handlers[type] ?: return logger.error("AimeDB: Unknown request type 0x${type.toString(16)}")
|
||||
|
||||
@@ -89,6 +90,7 @@ class AimeDB(
|
||||
|
||||
handler.fn(data)?.let { ctx.write(it) }
|
||||
} finally {
|
||||
data.release()
|
||||
ctx.flush()
|
||||
ctx.close()
|
||||
}
|
||||
@@ -124,7 +126,7 @@ class AimeDB(
|
||||
}
|
||||
}
|
||||
|
||||
fun getCard(accessCode: String) = us.cardRepo.findByLuid(accessCode)()?.maybeGhost()?.let { card ->
|
||||
fun getCard(accessCode: String) = us.cardRepo.findByLuid(accessCode)?.maybeGhost()?.let { card ->
|
||||
// Update card access time and return the extId
|
||||
us.cardRepo.save(card.apply { accessTime = LocalDateTime.now() }).extId
|
||||
} ?: -1
|
||||
@@ -195,11 +197,13 @@ class AimeDB(
|
||||
var status = 0
|
||||
var aimeId = 0L
|
||||
|
||||
if (us.cardRepo.findByLuid(luid).isEmpty) {
|
||||
if (us.cardRepo.findByLuid(luid) == null) {
|
||||
val card: Card = cardService.registerByAccessCode(luid)
|
||||
|
||||
status = 1
|
||||
aimeId = card.extId
|
||||
|
||||
fedy.onCardCreated(luid, card.extId)
|
||||
}
|
||||
else logger.warn("> Duplicated Aime Card Register detected, access code: $luid")
|
||||
|
||||
|
||||
@@ -1,12 +1,30 @@
|
||||
package icu.samnyan.aqua.sega.aimedb
|
||||
|
||||
import icu.samnyan.aqua.sega.util.ByteBufUtil
|
||||
import io.netty.buffer.ByteBuf
|
||||
import io.netty.buffer.Unpooled.copiedBuffer
|
||||
import java.nio.charset.StandardCharsets
|
||||
import javax.crypto.Cipher
|
||||
import javax.crypto.spec.SecretKeySpec
|
||||
|
||||
fun ByteBuf.toBytes(): ByteArray {
|
||||
val readerPos = readerIndex()
|
||||
resetReaderIndex()
|
||||
val result = ByteArray(readableBytes())
|
||||
readBytes(result)
|
||||
readerIndex(readerPos)
|
||||
return result
|
||||
}
|
||||
|
||||
fun ByteBuf.toAllBytes(): ByteArray {
|
||||
val readerPos = readerIndex()
|
||||
resetReaderIndex()
|
||||
writerIndex(capacity())
|
||||
val result = ByteArray(capacity())
|
||||
readBytes(result)
|
||||
readerIndex(readerPos)
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* @author samnyan (privateamusement@protonmail.com)
|
||||
*/
|
||||
@@ -15,7 +33,6 @@ object AimeDbEncryption {
|
||||
val enc = Cipher.getInstance("AES/ECB/NoPadding").apply { init(Cipher.ENCRYPT_MODE, KEY) }
|
||||
val dec = Cipher.getInstance("AES/ECB/NoPadding").apply { init(Cipher.DECRYPT_MODE, KEY) }
|
||||
|
||||
fun decrypt(src: ByteBuf) = copiedBuffer(dec.doFinal(ByteBufUtil.toBytes(src)))
|
||||
|
||||
fun encrypt(src: ByteBuf) = copiedBuffer(enc.doFinal(ByteBufUtil.toAllBytes(src)))
|
||||
fun decrypt(src: ByteBuf) = copiedBuffer(dec.doFinal(src.toBytes()))
|
||||
fun encrypt(src: ByteBuf) = copiedBuffer(enc.doFinal(src.toAllBytes()))
|
||||
}
|
||||
|
||||
@@ -9,8 +9,6 @@ import io.netty.channel.socket.SocketChannel
|
||||
import io.netty.channel.socket.nio.NioServerSocketChannel
|
||||
import io.netty.handler.logging.LogLevel
|
||||
import io.netty.handler.logging.LoggingHandler
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.stereotype.Component
|
||||
|
||||
@@ -3,7 +3,6 @@ package icu.samnyan.aqua.sega.allnet
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserRepo
|
||||
import icu.samnyan.aqua.sega.allnet.AllNetBillingDecoder.decodeAllNet
|
||||
import icu.samnyan.aqua.sega.util.AquaConst
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import jakarta.servlet.http.HttpServletResponse
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
@@ -14,7 +13,6 @@ import java.io.InputStream
|
||||
import java.nio.charset.StandardCharsets
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "allnet.server")
|
||||
@@ -84,7 +82,7 @@ class AllNet(
|
||||
|
||||
logger.info("AllNet /DownloadOrder : $reqMap")
|
||||
|
||||
val serial = reqMap["serial"] ?: AquaConst.DEFAULT_KEYCHIP_ID
|
||||
val serial = reqMap["serial"] ?: "A69E01A8888"
|
||||
val resp = mapOf(
|
||||
"stat" to "1",
|
||||
"serial" to serial
|
||||
|
||||
@@ -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\"}"
|
||||
}
|
||||
@@ -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>>() {})
|
||||
}
|
||||
}
|
||||
@@ -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\"}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<>() {
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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\"}";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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> {
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user