37 Commits

Author SHA1 Message Date
Azalea
aeafa6a396 [M] Rename to avoid migration conflict 2025-12-14 05:27:17 +09:00
Paiton Bertschy
448426a96d chore: remove a300 events (dummy events) 2025-12-14 05:27:17 +09:00
thewiilover
dfa6176689 chore: ad chusan xverse a071 to a300 event ids 2025-12-14 05:27:17 +09:00
Menci
d996fba291 [+] Fedy for AquaGameOptions 2025-12-13 10:21:42 +09:00
Menci
0cb2a95ff3 [F] Fix inheritance in KClass<T>.vars() 2025-12-13 10:21:42 +09:00
Menci
be5220fd51 [O] Use Iterable<T>.mapApply() 2025-12-13 10:21:42 +09:00
Menci
f23c0d6fe1 [RF] Re-organize game options 2025-12-13 10:21:42 +09:00
Menci
5aca650602 [F] CardTimestamp relationship definition 2025-12-10 14:03:51 +09:00
Menci
7c72348016 [+] Card Timestamp 2025-12-10 14:03:51 +09:00
radiumerst
5eee6505f9 Fix: KALEIDXSCOPE crashing on final phase
Add gates 7-10 to prevent crashing when entered

Set Gates 1-9 to LIFE999 BASIC
Set Gate 10 to LIFE999/999 BASIC
2025-12-10 14:03:27 +09:00
Azalea
43b7ea65a5 [-] Replace login blocking with an @ AquaDX prefix 2025-11-23 23:43:23 +08:00
Paiton Bertschy
85149dcd03 Update src/main/resources/db/80/V1000_61__ongeki_refresh_a017_to_a032.sql
Co-authored-by: 凌莞~(=^▽^=) <opensource@c5y.moe>
2025-11-19 13:33:23 +08:00
Paiton Bertschy
a767c8949c Update src/main/resources/db/80/V1000_61__ongeki_refresh_a017_to_a032.sql
Co-authored-by: 凌莞~(=^▽^=) <opensource@c5y.moe>
2025-11-19 13:33:23 +08:00
Paiton Bertschy
bbeb476a62 Update src/main/resources/db/80/V1000_61__ongeki_refresh_a017_to_a032.sql
Co-authored-by: 凌莞~(=^▽^=) <opensource@c5y.moe>
2025-11-19 13:33:23 +08:00
Paiton Bertschy
c444350cef Remove duplicate entries from SQL data
Removed duplicate entries for '7mai' and 'HiTECH NINJA' in the SQL file.
2025-11-19 13:33:23 +08:00
thewiilover
13a318d519 chroe: adds all the new content ids for ongeki 2025-11-19 13:33:23 +08:00
thewiilover
e744d96c96 chore: add ongeki ReFresh event ids from a017-a032 to new migration 2025-11-19 13:33:23 +08:00
Clansty
491044d37a [-] remove munet migration notice 2025-11-14 22:11:21 +08:00
Azalea
9d30cf1e7d [+] Pagination 2025-11-14 18:52:46 +08:00
凌莞~(=^▽^=)
d2608472d8 fix: total_point null (#185) 2025-10-25 03:46:05 +08:00
Menci
34aae0c87a [F] Player name validation (#186) 2025-10-21 03:46:43 +09:00
Clansty
69bd35a579 [O] Change tip 2025-10-11 04:05:56 +08:00
Menci
3e6c0b4159 feat: user management APIs (#184) 2025-10-07 13:21:01 -07:00
Menci
a33ec8b11c feat: crop pfp to at most 1024px (#183) 2025-10-07 13:20:49 -07:00
Azalea
dd03ca38a1 [F] Fix memory leak 2025-10-07 03:40:49 +08:00
Menci
1cac5e451a refactor: user registrar (#182) 2025-10-06 12:32:52 -07:00
Azalea
010d4592e4 hot fix 2025-10-07 00:30:48 +08:00
Menci
b0d0f8ef7d feat: some data management APIs (#176) 2025-10-06 09:27:39 -07:00
Raymond
967d311ee4 chusan x-v a001 (#180)
Co-authored-by: asterisk727 <59166650+asterisk727@users.noreply.github.com>
2025-10-04 10:59:02 -07:00
crxmsxn
d5b777d720 fix: mobile styling for favorites (#181) 2025-10-04 10:57:31 -07:00
Raymond
2ab2666ad0 Feat: Favorites (for all supported games) (#174) 2025-09-29 22:56:26 -07:00
alexay7
4971f2be78 [+] Add support for geki cm and fix chusan cm implementation (#175) 2025-09-29 22:47:31 -07:00
Menci
b0a49d6626 [+] Add APIs (#177) 2025-09-29 21:54:58 -07:00
凌莞~(=^▽^=)
d830854eaa fix: X-Verse Username (#179) 2025-09-29 22:35:56 -04:00
凌莞~(=^▽^=)
68820d5a86 chore: Bump versions 2025-09-13 21:49:46 +08:00
Raymond
8b079bc40b fix: nvm looks like shit 2025-09-12 11:34:25 -04:00
Raymond
b0dd9b845f chore: bump versions 2025-09-12 11:33:23 -04:00
70 changed files with 2838 additions and 542 deletions

View File

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

View File

@@ -1,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}

View 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>

View File

@@ -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";
@@ -13,7 +12,6 @@
<blockquote>
{ts("settings.gameNotice")}
</blockquote>
<GameSettingFields game="general"/>
<div class="field">
<div class="bool">
<input id="rounding" type="checkbox" bind:checked={rounding.value}/>

View File

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

View File

@@ -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 = {
@@ -148,18 +149,30 @@ export const EN_REF_SETTINGS = {
'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',

View File

@@ -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 = {
@@ -160,18 +161,30 @@ const zhSettings: typeof EN_REF_SETTINGS = {
'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': '我是桐谷遥',

View File

@@ -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}>

View File

@@ -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>

View File

@@ -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>

View File

@@ -80,11 +80,12 @@
// Don't know why this isn't just a part of the cropper module. Have to do this myself.. What a shame
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'
@@ -282,7 +283,7 @@
object-fit: cover
aspect-ratio: 1
.cropper-container
position: relative

View File

@@ -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

View File

@@ -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
@@ -162,7 +158,7 @@
return submitting = false
}
// Send request to server
// Send request to server
await USER.changePassword({ token, password })
.then(() => {
state = 'verify'
@@ -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>
@@ -264,7 +257,7 @@
{#if error}
<span class="error">{error}</span>
{/if}
<div class="login-form" transition:slide>
<div class="login-form" transition:slide>
<input type="password" placeholder={t('new-password')} bind:value={password}>
<button on:click={changePassword}>
{#if submitting}

View File

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

View File

@@ -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() }
@@ -263,3 +269,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) }

View File

@@ -21,15 +21,15 @@ val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, obj
else -> 400 - "Invalid boolean value ${parser.text}"
}
})
val JSON_DATETIME = SimpleModule().addDeserializer(java.time.LocalDateTime::class.java, object : JsonDeserializer<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)

View File

@@ -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(
@@ -180,7 +206,8 @@ class CardGameService(
val diva: icu.samnyan.aqua.sega.diva.dao.userdata.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 {

View File

@@ -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 icu.samnyan.aqua.net.components.EmailProperties
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
import org.springframework.web.bind.annotation.RestController
import java.security.MessageDigest
import icu.samnyan.aqua.net.db.AquaNetUserRepo
import icu.samnyan.aqua.net.db.AquaNetUserFedyRepo
import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.net.components.JWT
import icu.samnyan.aqua.net.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.db.AquaUserServices
import icu.samnyan.aqua.net.games.mai2.Mai2Import
import icu.samnyan.aqua.net.games.ExportOptions
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPlaylogHandler as Mai2UploadUserPlaylogHandler
import icu.samnyan.aqua.sega.maimai2.handler.UpsertUserAllHandler as Mai2UpsertUserAllHandler
import icu.samnyan.aqua.net.utils.ApiException
import 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.utils.PathProps
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.ongeki.OgkUserDataRepo
import icu.samnyan.aqua.sega.wacca.model.db.WcUserRepo
import org.springframework.context.ApplicationContext
import org.springframework.web.multipart.MultipartFile
import java.time.Instant
import java.util.concurrent.CompletableFuture
import kotlin.io.path.getLastModifiedTime
import kotlin.io.path.isRegularFile
import kotlin.io.path.writeBytes
@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,73 +93,108 @@ 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))
val suppressEvents = ThreadLocal.withInitial { false }
private fun <T> handleFedy(key: Str, block: () -> T): T {
val old = suppressEvents.get()
suppressEvents.set(true)
try {
key.checkKey()
return block()
} finally { suppressEvents.set(old) }
}
@API("/link")
fun handleLink(@RP token: Str, @RP nonce: Str): Any {
val user = jwt.auth(token)
data class FedyErr(val code: Int, val message: Str)
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())
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())
}
@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
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()
)
}
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 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 UnlinkByRemoteReq(val auId: Long)
@API("/unlink-by-remote")
fun handleUnlinkByRemote(@RH(KEY_HEADER) key: Str, @RB req: UnlinkByRemoteReq): Any {
key.checkKey()
val user = ensureUser(req.auId)
userFedyRepo.deleteByAquaNetUserAuId(user.auId)
// No need to notify remote, because initiated by remote
return SUCCESS
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) }
}
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) }
else -> 406 - "Unsupported game"
}
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).orElse(null)
?: (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) {
@@ -138,6 +203,7 @@ class Fedy(
repo.flush()
}
}
val card = cardRepo.findByExtId(extId).orElse(null) ?: (404 - "Card not found")
transaction.execute { when (req.game) {
"mai2" -> {
if (req.removeOldData) { removeOldData(mai2UserDataRepo) }
@@ -148,30 +214,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))
} catch (e: Exception) {
log.error("Error handling Fedy on maybeNotifyAsync($event, $game, $maybeExtId)", e)
} }
CardResolveRes(
card?.extId ?: 0,
card?.isGhost ?: false,
isNewlyCreated,
isPairedLuidDiverged)
}
private fun notify(event: FedyEvent, body: Any?) {
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).orElse(null) ?: 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)", e)
}
}.let {}
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 +339,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

View File

@@ -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"
@@ -35,6 +36,9 @@ class Frontier(
if (async { cardService.cardRepo.findByLuid(accessCode) }.isPresent) 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

View File

@@ -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) }
}
}

View File

@@ -3,20 +3,15 @@ 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.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 +21,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 +62,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 +86,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 +170,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 +194,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 +217,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 +265,7 @@ class UserRegistrar(
(portraitPath / name).writeBytes(bytes)
userRepo.save(u.apply { profilePicture = name })
}
fedy.onUserUpdated(u)
SUCCESS
}

View File

@@ -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,

View File

@@ -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,6 +142,43 @@ 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")
@@ -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"

View File

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

View File

@@ -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
@@ -30,6 +30,8 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
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 +58,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
}
@@ -139,6 +141,7 @@ 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
}
}
@@ -148,7 +151,7 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
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 +202,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
)
}

View File

@@ -8,10 +8,10 @@ import java.time.LocalDate
const val LETTERS = "" +
"" +
""
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" +

View File

@@ -6,6 +6,8 @@ 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.data.jpa.repository.JpaRepository
import org.springframework.data.repository.NoRepositoryBean
@@ -16,7 +18,6 @@ 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
@@ -49,6 +50,7 @@ interface IUserRepo<UserModel, ThisModel>: JpaRepository<ThisModel, Long> {
* 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,11 +84,11 @@ 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) }
@@ -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
}

View File

@@ -47,7 +47,8 @@ data class GenericGameSummary(
val recent: List<IGenericGamePlaylog>,
val rival: Boolean?
val rival: Boolean?,
val favorites: List<Int>?
)
data class GenericRankingPlayer(

View File

@@ -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()
},

View File

@@ -25,7 +25,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 },
@@ -60,7 +60,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)
}
/**

View File

@@ -19,7 +19,7 @@ import kotlin.reflect.full.declaredMembers
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()
},

View File

@@ -3,6 +3,7 @@ package icu.samnyan.aqua.net.games.mai2
import ext.*
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,6 +14,7 @@ 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 ->
@@ -39,6 +41,7 @@ class Mai2MusicDetailImport(
}
}
repos.userMusicDetail.saveAll(data)
cardService.updateCardTimestamp(card, "mai2")
SUCCESS
}
}

View File

@@ -29,7 +29,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() },
@@ -66,7 +66,7 @@ class Maimai2(
}
}
genericUserSummary(card, ratingComposition, isMyRival)
genericUserSummary(card, ratingComposition, isMyRival, extra["favorite_music"]?.split(",")?.mapNotNull{it -> it.toIntOrNull()})
}
@API("user-rating")
@@ -110,6 +110,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)
}
@@ -139,6 +140,7 @@ class Maimai2(
loginBonus.add(newBonus)
}
repos.userLoginBonus.saveAll(loginBonus)
cardService.updateCardTimestamp(card, "mai2")
}
SUCCESS
}
@@ -172,6 +174,7 @@ class Maimai2(
myRival.propertyValue = myRivalList.joinToString(",")
repos.userGeneralData.save(myRival)
cardService.updateCardTimestamp(myCard, "mai2")
}
SUCCESS
}

View File

@@ -27,7 +27,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 },

View File

@@ -31,7 +31,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, if (data.isPresent) data.get().favoriteSongs else null)
}
override val shownRanks: List<Pair<Int, String>> = waccaScores.filter { it.first > 85 * 10000 }

View File

@@ -1,11 +1,10 @@
package icu.samnyan.aqua.sega.aimedb
import ext.*
import icu.samnyan.aqua.net.BotProps
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 +25,7 @@ class AimeDB(
val cardService: CardService,
val us: AquaUserServices,
val allNetProps: AllNetProps,
val fedy: Fedy,
): ChannelInboundHandlerAdapter() {
val logger = logger()
@@ -68,9 +68,9 @@ class AimeDB(
*/
override fun channelRead(ctx: ChannelHandlerContext, msg: Any) {
if (msg !is Map<*, *>) return
val type = msg["type"] as Int
val data = msg["data"] as ByteBuf
try {
val type = msg["type"] as Int
val data = msg["data"] as ByteBuf
val base = data.decodeHeader()
val handler = handlers[type] ?: return logger.error("AimeDB: Unknown request type 0x${type.toString(16)}")
@@ -89,6 +89,7 @@ class AimeDB(
handler.fn(data)?.let { ctx.write(it) }
} finally {
data.release()
ctx.flush()
ctx.close()
}
@@ -200,6 +201,8 @@ class AimeDB(
status = 1
aimeId = card.extId
fedy.onCardCreated(luid, card.extId)
}
else logger.warn("> Duplicated Aime Card Register detected, access code: $luid")

View File

@@ -10,6 +10,7 @@ import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
import icu.samnyan.aqua.sega.general.GameMusicPopularity
import icu.samnyan.aqua.sega.general.MeowApi
import icu.samnyan.aqua.sega.general.RequestContext
import icu.samnyan.aqua.sega.general.service.CardService
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
import icu.samnyan.aqua.sega.util.jackson.StringMapper
import icu.samnyan.aqua.spring.Metrics
@@ -29,13 +30,14 @@ class ChusanController(
val cmMapper: BasicMapper,
val db: Chu3Repos,
val us: AquaUserServices,
val cardService: CardService,
val versionHelper: ChusanVersionHelper,
val props: ChusanProps,
val pop: GameMusicPopularity,
val chusan: Chusan
): MeowApi({ api, resp ->
if (resp is String) resp
else (if ("CM" in api) cmMapper else mapper).write(resp)
else (if ("CM" in api || api == "GetUserItemApi" ) cmMapper else mapper).write(resp)
}) {
val log = LoggerFactory.getLogger(ChusanController::class.java)

View File

@@ -236,9 +236,7 @@ fun ChusanController.chusanInit() {
) + userDict
if (user.card?.status == CardStatus.MIGRATED_TO_MINATO) {
res["userName"] = "JiaQQqun / CardMigrated"
res["rating"] = 0
res["playerLevel"] = 0
res["userName"] = "${res["userName"]}"
}
res
@@ -394,13 +392,6 @@ fun ChusanController.chusanInit() {
// }
// process()
val user = db.userData.findByCard_ExtId(uid)()
if (user?.card?.status == CardStatus.MIGRATED_TO_MINATO) {
"""{"returnCode":"0"}"""
}
else {
"""{"returnCode":"1"}"""
}
"""{"returnCode":"1"}"""
}
}

View File

@@ -54,23 +54,48 @@ fun ChusanController.cmApiInit() {
db.userGacha.save(it)
}
//Set the hasCompleted to true
val printState = db.userCardPrintState.findByUserAndGachaIdAndHasCompleted(u, gachaId, false)
if (printState.isEmpty()) return@api null
printState.forEach { it.hasCompleted = true }
db.userCardPrintState.saveAll(printState)
// Append the order ID to the response with the key "orderId"
val fullPrintState = printState.map {
mapOf(
"orderId" to it.id,
"hasCompleted" to it.hasCompleted,
"limitDate" to it.limitDate.toString(),
"placeId" to it.placeId,
"cardId" to it.cardId,
"gachaId" to it.gachaId,
"userId" to uid
)
}
u.card?.let { cardService.updateCardTimestamp(it, "chu3") }
mapOf(
"returnCode" to 1,
"apiName" to "CMUpsertUserGachaApi",
"userCardPrintStateList" to db.userCardPrintState.findByUserAndGachaIdAndHasCompleted(u, gachaId, false)
"userCardPrintStateList" to fullPrintState
)
}
"CMUpsertUserPrintCancel" {
val orderIdList: List<Long> = cmMapper.convert<List<Long>>(parsing { data["orderIdList"]!! })
db.userCardPrintState.saveAll(orderIdList.mapNotNull {
val states = db.userCardPrintState.saveAll(orderIdList.mapNotNull {
// TODO: The original code by Eori writes findById but I don't think that is correct...
db.userCardPrintState.findById(it)()?.apply {
hasCompleted = true
}
})
states.firstOrNull()?.user?.card?.let { cardService.updateCardTimestamp(it, "chu3") }
mapOf("returnCode" to 1, "apiName" to "CMUpsertUserPrintCancelApi")
}
@@ -93,6 +118,8 @@ fun ChusanController.cmApiInit() {
db.userCardPrintState.save(this)
}
u.card?.let { cardService.updateCardTimestamp(it, "chu3") }
mapOf("returnCode" to 1, "apiName" to "CMUpsertUserPrintSubtractApi")
}
}
}

View File

@@ -4,6 +4,7 @@ import ext.*
import icu.samnyan.aqua.sega.chusan.ChusanController
import icu.samnyan.aqua.sega.chusan.model.request.Chu3UserAll
import icu.samnyan.aqua.sega.chusan.model.userdata.*
import icu.samnyan.aqua.sega.general.model.CardStatus
import icu.samnyan.aqua.sega.general.model.response.UserRecentRating
@Suppress("UNCHECKED_CAST")
@@ -13,6 +14,7 @@ fun ChusanController.upsertApiInit() {
charge.user = db.userData.findByCard_ExtId(uid)() ?: (400 - "User not found")
charge.id = db.userCharge.findByUser_Card_ExtIdAndChargeId(uid, charge.chargeId)?.id ?: 0
db.userCharge.save(charge)
charge.user.card?.let { cardService.updateCardTimestamp(it, "chu3") }
"""{"returnCode":"1"}"""
}
@@ -25,10 +27,23 @@ fun ChusanController.upsertApiInit() {
val u = (userData?.get(0) ?: return@api null).apply {
id = oldUser?.id ?: 0
card = oldUser?.card ?: us.cardRepo.findByExtId(uid).expect("Card not found")
userName = userName.fromChusanUsername()
val version = data["version"] as? String ?: "0.00"
val versionNumber = version.toDoubleOrNull() ?: 0.0
userName = if (versionNumber >= 2.40) {
userName
} else {
userName.fromChusanUsername()
}
userNameEx = ""
}.also { db.userData.saveAndFlush(it) }
// If the user was previously migrated to Minato, saving would mark them "migrated and then cleared".
if (u.card?.status == CardStatus.MIGRATED_TO_MINATO) {
u.card?.status = CardStatus.NORMAL_MIGRATED_TO_MINATO_AND_THEN_CLEARED
us.cardRepo.save(u.card!!)
}
// Only save if it is a valid region and the user has played at least a song
req.userPlaylogList?.firstOrNull()?.regionId?.let { rid ->
val region = db.userRegions.findByUserAndRegionId(u, rid)?.apply {
@@ -92,10 +107,15 @@ fun ChusanController.upsertApiInit() {
score = it.score
}
selectUserName = selectUserName.fromChusanUsername()
opponentUserName1 = opponentUserName1.fromChusanUsername()
opponentUserName2 = opponentUserName2.fromChusanUsername()
opponentUserName3 = opponentUserName3.fromChusanUsername()
val version = data["version"] as? String ?: "0.00"
val versionNumber = version.toDoubleOrNull() ?: 0.0
if (versionNumber < 2.40) {
// 2.40以下版本需要转换编码
selectUserName = selectUserName.fromChusanUsername()
opponentUserName1 = opponentUserName1.fromChusanUsername()
opponentUserName2 = opponentUserName2.fromChusanUsername()
opponentUserName3 = opponentUserName3.fromChusanUsername()
}
}) }
// List data
@@ -173,8 +193,10 @@ fun ChusanController.upsertApiInit() {
}.also { db.userCMissionProgress.save(it) }
}
}
u.card?.let { cardService.updateCardTimestamp(it, "chu3") }
}
"""{"returnCode":1}"""
}
}
}

View File

@@ -3,6 +3,10 @@ package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonInclude
import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.core.JsonParser
import com.fasterxml.jackson.databind.DeserializationContext
import com.fasterxml.jackson.databind.JsonDeserializer
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
import com.fasterxml.jackson.databind.annotation.JsonSerialize
import icu.samnyan.aqua.net.games.BaseEntity
import icu.samnyan.aqua.net.games.IUserData
@@ -10,8 +14,22 @@ import icu.samnyan.aqua.sega.chusan.model.request.UserEmoney
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.util.jackson.AccessCodeSerializer
import jakarta.persistence.*
import kotlinx.io.IOException
import lombok.NoArgsConstructor
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
class FlexibleDateTimeDeserializer : JsonDeserializer<LocalDateTime?>() {
@Throws(IOException::class)
public override fun deserialize(p: JsonParser, ctxt: DeserializationContext?): LocalDateTime {
return LocalDateTime.parse(p.getText(), FORMATTER)
}
companion object {
// Card Maker needs the date ending with ".0" and chunithm sends the dates without it so we need a flexible parser
private val FORMATTER: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss[.S]")
}
}
@Entity(name = "ChusanUserData")
@Table(name = "chusan_user_data")
@@ -50,12 +68,14 @@ class Chu3UserData : BaseEntity(), IUserData {
var totalExpertHighScore: Long = 0
var totalMasterHighScore: Long = 0
var totalUltimaHighScore: Long = 0
@JsonDeserialize(using = FlexibleDateTimeDeserializer::class)
var eventWatchedDate: LocalDateTime = LocalDateTime.now()
var friendCount = 0
var firstGameId: String = ""
var firstRomVersion: String = ""
var firstDataVersion: String = ""
@JsonDeserialize(using = FlexibleDateTimeDeserializer::class)
override var firstPlayDate: LocalDateTime = LocalDateTime.now()
var lastGameId: String = ""
@@ -65,6 +85,7 @@ class Chu3UserData : BaseEntity(), IUserData {
@JsonIgnore
var lastLoginDate: LocalDateTime = LocalDateTime.now()
@JsonDeserialize(using = FlexibleDateTimeDeserializer::class)
override var lastPlayDate: LocalDateTime = LocalDateTime.now()
var lastPlaceId = 0
var lastPlaceName: String = ""

View File

@@ -1,5 +1,6 @@
package icu.samnyan.aqua.sega.chusan.model.userdata
import com.fasterxml.jackson.annotation.JsonIgnore
import jakarta.persistence.Entity
import jakarta.persistence.Table
import java.time.LocalDateTime
@@ -8,6 +9,8 @@ import java.time.LocalDateTime
@Table(name = "chusan_user_print_state")
class UserCardPrintState(
var hasCompleted: Boolean = false,
@JsonIgnore
var limitDate: LocalDateTime = LocalDateTime.now(),
var placeId: Int = 0,
var cardId: Int = 0,

View File

@@ -9,7 +9,7 @@ enum class CardStatus {
NORMAL,
// Reserved for future use
NORMAL_RESERVED_1,
NORMAL_MIGRATED_TO_MINATO_AND_THEN_CLEARED,
NORMAL_RESERVED_2,
NORMAL_RESERVED_3,
NORMAL_RESERVED_4,
@@ -22,7 +22,12 @@ enum class CardStatus {
// Deleted statuses
OVERWRITTEN,
DELETED,
MIGRATED_TO_MINATO,
MIGRATED_TO_MINATO;
/**
* Returns true if card status is in any NORMAL state
*/
val isNormal get() = this.ordinal in NORMAL.ordinal..NORMAL_RESERVED_9.ordinal
}
/**

View File

@@ -0,0 +1,32 @@
package icu.samnyan.aqua.sega.general.model
import ext.Str
import jakarta.persistence.*
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.stereotype.Repository
import java.time.Instant
@Entity(name = "SegaCardTimestamp")
@Table(name = "sega_card_timestamp")
class CardTimestamp(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long = 0,
@Column(nullable = false)
var createdAt: Instant = Instant.now(),
@Column(nullable = false)
var updatedAt: Instant = Instant.now(),
var game: Str,
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "card_id")
var card: Card? = null,
)
@Repository
interface CardTimestampRepo : JpaRepository<CardTimestamp, Long> {
fun findByCardIdAndGame(cardId: Long, game: Str): CardTimestamp?
}

View File

@@ -1,10 +1,16 @@
package icu.samnyan.aqua.sega.general.service
import ext.Bool
import ext.Str
import ext.minus
import icu.samnyan.aqua.net.Fedy
import icu.samnyan.aqua.net.db.AquaNetUser
import icu.samnyan.aqua.sega.general.dao.CardRepository
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.model.CardTimestamp
import icu.samnyan.aqua.sega.general.model.CardTimestampRepo
import org.springframework.stereotype.Service
import java.time.Instant
import java.time.LocalDateTime
import java.util.*
import java.util.concurrent.ThreadLocalRandom
@@ -14,7 +20,7 @@ import kotlin.jvm.optionals.getOrNull
* @author samnyan (privateamusement@protonmail.com)
*/
@Service
class CardService(val cardRepo: CardRepository)
class CardService(val cardRepo: CardRepository, val cardTimestampRepo: CardTimestampRepo, val fedy: Fedy)
{
/**
* Find a card by External ID
@@ -106,4 +112,13 @@ class CardService(val cardRepo: CardRepository)
}
return eid
}
fun getCardTimestamp(card: Card, game: Str, now: Instant = Instant.now()) =
cardTimestampRepo.findByCardIdAndGame(card.id, game) ?: CardTimestamp(game = game, card = card, createdAt = now, updatedAt = now);
fun updateCardTimestamp(card: Card, game: Str, now: Instant = Instant.now(), resetCreatedAt: Bool = false) {
cardTimestampRepo.save(getCardTimestamp(card, game, now).apply { updatedAt = now }
.apply { if (resetCreatedAt) createdAt = now });
fedy.onDataUpdated(card.extId, game, resetCreatedAt)
}
}

View File

@@ -9,6 +9,7 @@ import icu.samnyan.aqua.sega.maimai2.model.UserRivalMusicDetail
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserKaleidx
import icu.samnyan.aqua.sega.maimai2.model.userdata.UserRegions
import java.time.LocalDate
import kotlin.random.Random
fun Maimai2ServletController.initApis() {
val log = logger()
@@ -114,10 +115,7 @@ fun Maimai2ServletController.initApis() {
)
if (d.card?.status == CardStatus.MIGRATED_TO_MINATO) {
res["userName"] = "JiaQQqun / CardMigrated"
res["dispRate"] = 1
res["playerRating"] = 66564
res["totalAwake"] = 7114
res["userName"] = "${res["userName"]}"
}
res
@@ -133,10 +131,6 @@ fun Maimai2ServletController.initApis() {
"Bearer" to "meow", "bearer" to "meow"
)
if (d?.card?.status == CardStatus.MIGRATED_TO_MINATO) {
res["returnCode"] = 0
}
// Get regionId from request
val region = data["regionId"] as? Int
@@ -149,6 +143,7 @@ fun Maimai2ServletController.initApis() {
regionId = region
}
db.userRegions.save(region)
// d.card?.let { cardService.updateCardTimestamp(it, "mai2") } // TODO: why save regions on login?
}
res
@@ -230,12 +225,16 @@ fun Maimai2ServletController.initApis() {
// Kaleidoscope, added on 1.50
// [{gateId, phaseId}]
"GetGameKaleidxScope" { mapOf("gameKaleidxScopeList" to ls(
mapOf("gateId" to 1, "phaseId" to findPhase(LocalDate.of(2025, 1, 18))),
mapOf("gateId" to 2, "phaseId" to 2),
mapOf("gateId" to 3, "phaseId" to 2),
mapOf("gateId" to 4, "phaseId" to findPhase(LocalDate.of(2025, 2, 25))),
mapOf("gateId" to 5, "phaseId" to 2),
mapOf("gateId" to 6, "phaseId" to 2),
mapOf("gateId" to 1, "phaseId" to 6),
mapOf("gateId" to 2, "phaseId" to 6),
mapOf("gateId" to 3, "phaseId" to 6),
mapOf("gateId" to 4, "phaseId" to 6),
mapOf("gateId" to 5, "phaseId" to 6),
mapOf("gateId" to 6, "phaseId" to 6),
mapOf("gateId" to 7, "phaseId" to 6),
mapOf("gateId" to 8, "phaseId" to 6),
mapOf("gateId" to 9, "phaseId" to 6),
mapOf("gateId" to 10, "phaseId" to 13),
)) }
// Request: {userId}
// Response: {userId, userKaleidxScopeList}
@@ -312,14 +311,6 @@ fun Maimai2ServletController.initApis() {
)
}
"GetServerAnnouncement" static { mapOf(
"title" to "",
"announcement" to "",
"showOnIdle" to false,
"showOnUserLogin" to false,
"imageUrl" to "",
) }
"GetGameWeeklyData" static { mapOf(
"gameWeeklyData" to mapOf(
"missionCategory" to 0,
@@ -362,4 +353,71 @@ fun Maimai2ServletController.initApis() {
"userRecommendSelectionMusicIdList" to (net.recommendedMusic[user.id] ?: empty)
)
}
"GetGameFesta" { mapOf(
"eventId" to 0,
"isRallyPeriod" to false,
"isCircleJoinNotAllowed" to false,
"jackingFestaSideId" to Random.nextInt(0, 3),
"festaSideDataList" to empty,
) }
"GetPlaceCircleData" static { mapOf(
"returnCode" to 0,
"circleId" to 0,
"aggrDate" to ""
) }
"GetUserCircleData" static { mapOf(
"circleId" to 0,
"circleName" to "一緒に歌おう!",
"isPlace" to false,
"circleClass" to 0,
"lastLoginDate" to "",
"circlePointRankingList" to empty
) }
"GetUserCirclePointData" { mapOf(
"userId" to uid,
"aggrDate" to "",
"userCirclePointDataList" to empty
) }
"GetUserCirclePointRanking" static { mapOf(
"circleId" to 0,
"circleName" to "一緒に歌おう!",
"aggrDate" to "",
"lastMonthCircleRank" to 0,
"lastMonthPoint" to 0
) }
"GetUserFesta" static { mapOf(
"userFestaData" to mapOf(
"eventId" to 0,
"circleId" to 0,
"festaSideId" to 0,
"circleTotalFestaPoint" to 0,
"currentTotalFestaPoint" to 0,
"circleRankInFestaSide" to 0,
"circleRecordDate" to "",
"isDailyBonus" to false,
"participationRewardGet" to false,
"receivedRewardBorder" to 0
),
"userResultFestaData" to mapOf(
"eventId" to 0,
"circleId" to 0,
"circleName" to "一緒に歌おう!",
"festaSideId" to 0,
"circleRankInFestaSide" to 0,
"receivedRewardBorder" to 0,
"circleTotalFestaPoint" to 0,
"resultRewardGet" to false,
)
) }
"UpsertUserPlaceCircleRegist" static { mapOf(
"returnCode" to 0,
"apiName" to "UpsertUserPlaceCircleRegistApi"
) }
}

View File

@@ -40,9 +40,6 @@ class Maimai2ServletController(
val db: Mai2Repos,
val net: Maimai2,
): MeowApi(serialize = { _, resp -> if (resp is String) resp else resp.toJson() }) {
@Autowired @Lazy lateinit var fedy: Fedy
companion object {
private val log = logger()
private val empty = listOf<Any>()
@@ -55,7 +52,7 @@ class Maimai2ServletController(
val endpointList = setOf("GetGameRankingApi","GetUserCharacterApi","GetUserItemApi","GetUserPortraitApi",
"GetUserRatingApi","UploadUserPhotoApi","UploadUserPlaylogApi","UploadUserPortraitApi","UpsertUserAllApi",
"CMGetUserCardApi","CMGetUserCardPrintErrorApi","CMGetUserDataApi","CMGetUserItemApi","CMUpsertUserPrintApi",
"GetUserFavoriteItemApi","GetServerAnnouncementApi")
"GetUserFavoriteItemApi")
val noopEndpoint = setOf("GetUserScoreRankingApi", "UpsertClientBookkeepingApi",
"UpsertClientSettingApi", "UpsertClientTestmodeApi", "UpsertClientUploadApi", "Ping", "RemoveTokenApi",
@@ -95,7 +92,6 @@ class Maimai2ServletController(
val ctx = RequestContext(req, data.mut)
serialize(api, handlers[api]!!(ctx) ?: noop).also {
log.info("$token : $api > ${it.truncate(500)}")
if (api == "UpsertUserAllApi") { fedy.onUpserted("mai2", data["userId"]) }
}
}
} catch (e: Exception) {

View File

@@ -1,11 +1,14 @@
package icu.samnyan.aqua.sega.maimai2.handler
import ext.int
import ext.logger
import ext.mapApply
import icu.samnyan.aqua.net.games.mai2.Maimai2
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.general.dao.CardRepository
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2ItemKind
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserCharacter
import org.springframework.stereotype.Component
import kotlin.jvm.optionals.getOrNull
@@ -15,34 +18,25 @@ class GetUserCharacterHandler(
val maimai2: Maimai2,
val cardRepo: CardRepository,
) : BaseHandler {
val itemUnlock = maimai2.itemMapping[Mai2ItemKind.chara.name]?.map { mapOf(
"characterId" to it.key,
"level" to 9999,
"awakening" to 1,
"useCount" to 0
) }
val charaIds = maimai2.itemMapping[Mai2ItemKind.chara.name]?.map { it.key.int() } ?: emptyList()
init {
if (itemUnlock.isNullOrEmpty()) logger.warn("Mai2 item info is empty")
if (charaIds.isEmpty()) logger.warn("Mai2 item info is empty")
}
override fun handle(request: Map<String, Any>): Any {
val userId = (request["userId"] as Number).toLong()
// Aqua Net game unlock feature
cardRepo.findByExtId(userId).getOrNull()?.aquaUser?.gameOptions?.let { opt ->
if (!opt.unlockChara or itemUnlock.isNullOrEmpty()) return@let
logger.info("Response: ${itemUnlock!!.size} Characters - All unlock")
return mapOf(
"userId" to userId,
"userCharacterList" to itemUnlock
)
}
val gameOptions = cardRepo.findByExtId(userId).getOrNull()?.aquaUser?.gameOptions
val userCharacterList = repos.userCharacter.findByUser_Card_ExtId(userId)
.let { if (gameOptions?.mai2UnlockChara != true) it else (
charaIds.associateWith { Mai2UserCharacter().apply { characterId = it; level = 1 } } +
it.associateBy { it.characterId }
).values }
.let { if (gameOptions?.mai2UnlockCharaMaxLevel != true) it else it.mapApply { level = 9999 } }
return mapOf(
"userId" to userId,
"userCharacterList" to repos.userCharacter.findByUser_Card_ExtId(userId)
"userCharacterList" to userCharacterList
)
}

View File

@@ -50,10 +50,11 @@ class GetUserItemHandler(
// Aqua Net game unlock feature
cardRepo.findByExtId(userId).getOrNull()?.aquaUser?.gameOptions?.let { opt ->
val items = when {
(kind in 5..8) && opt.unlockMusic -> musicUnlock.getValue(kind)
(kind in 1..3 || kind == 11) && opt.unlockCollectables -> itemUnlock[kind]
(kind == 12) && opt.unlockTickets -> itemUnlock[kind]
(kind in 9..10) && opt.unlockChara -> itemUnlock[kind]
(kind in 5..8) && opt.mai2UnlockMusic -> musicUnlock.getValue(kind)
(kind in 1..3 || kind == 11) && opt.mai2UnlockCollectables -> itemUnlock[kind]
(kind == 12) && opt.mai2UnlockTickets -> itemUnlock[kind]
(kind == 9) && opt.mai2UnlockChara -> itemUnlock[kind]
(kind == 10) && opt.mai2UnlockPartners -> itemUnlock[kind]
else -> emptyList()
}

View File

@@ -6,6 +6,7 @@ import ext.millis
import ext.parsing
import icu.samnyan.aqua.sega.allnet.TokenChecker
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.general.service.CardService
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserPlaylogRepo
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPlaylog
@@ -22,7 +23,8 @@ import kotlin.jvm.optionals.getOrNull
class UploadUserPlaylogHandler(
private val userDataRepository: Mai2UserDataRepo,
private val playlogRepo: Mai2UserPlaylogRepo,
private val mapper: BasicMapper
private val mapper: BasicMapper,
private val cardService: CardService
) : BaseHandler {
data class BacklogEntry(val time: Long, val playlog: Mai2UserPlaylog)
companion object {
@@ -60,7 +62,10 @@ class UploadUserPlaylogHandler(
// Save if the user is registered
val u = userDataRepository.findByCardExtId(uid).getOrNull()
if (u != null) playlogRepo.save(playlog.apply { user = u })
if (u != null) {
playlogRepo.save(playlog.apply { user = u })
// u.card?.let { cardService.updateCardTimestamp(it, "mai2") } // No need: always followed by an UpsertUserAll
}
// If the user hasn't registered (first play), save the playlog to a backlog
else {

View File

@@ -5,11 +5,14 @@ import ext.invoke
import ext.mapApply
import ext.unique
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.general.model.CardStatus
import icu.samnyan.aqua.sega.general.service.CardService
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPlaylogHandler.Companion.playBacklog
import icu.samnyan.aqua.sega.maimai2.model.*
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
import icu.samnyan.aqua.sega.maimai2.model.request.Mai2UpsertUserAll
import icu.samnyan.aqua.sega.maimai2.model.userdata.*
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.Mai2UserRate
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
import lombok.AllArgsConstructor
import org.slf4j.LoggerFactory
@@ -25,7 +28,6 @@ class UpsertUserAllHandler(
val cardService: CardService,
val repos: Mai2Repos
) : BaseHandler {
fun String.isValidUsername() = isNotBlank() && length <= 8
@Throws(JsonProcessingException::class)
@@ -59,6 +61,12 @@ class UpsertUserAllHandler(
}
})
// If the user was previously migrated to Minato, saving would mark them "migrated and then cleared".
if (u.card?.status == CardStatus.MIGRATED_TO_MINATO) {
u.card?.status = CardStatus.NORMAL_MIGRATED_TO_MINATO_AND_THEN_CLEARED
cardService.cardRepo.save(u.card!!)
}
// Check playlog backlog
if (playBacklog.containsKey(userId)) playBacklog.remove(userId)?.forEach {
repos.userPlaylog.save(it.playlog.apply { user = u })
@@ -163,6 +171,8 @@ class UpsertUserAllHandler(
})
}
u.card?.let { cardService.updateCardTimestamp(it, "mai2") }
return SUCCESS
}

View File

@@ -5,6 +5,7 @@ import ext.logger
import ext.long
import ext.parsing
import icu.samnyan.aqua.sega.general.BaseHandler
import icu.samnyan.aqua.sega.general.service.CardService
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPrintDetail
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
@@ -18,6 +19,7 @@ import java.util.concurrent.ThreadLocalRandom
class UpsertUserPrintHandler(
val mapper: BasicMapper,
val db: Mai2Repos,
val cardService: CardService,
@param:Value("\${game.cardmaker.card.expiration:15}") val expirationTime: Long,
) : BaseHandler {
val log = logger()
@@ -43,6 +45,8 @@ class UpsertUserPrintHandler(
}
db.userPrintDetail.save(userPrint)
userData.card?.let { cardService.updateCardTimestamp(it, "mai2") }
return mapOf(
"returnCode" to 1,
"orderId" to 0,

View File

@@ -9,6 +9,7 @@ fun OngekiController.ongekiInit() {
fun <T> List<T>.staticLst(key: String) = mapOf("length" to size, key to this)
initUser()
cmApiInit()
initUpsertAll()
// Has type, but type is always 1

View File

@@ -0,0 +1,351 @@
package icu.samnyan.aqua.sega.ongeki
import ext.asDateTime
import ext.empty
import ext.int
import ext.invoke
import ext.long
import ext.mapApply
import ext.minus
import ext.parsing
import icu.samnyan.aqua.sega.ongeki.model.*
import java.time.LocalDateTime
fun OngekiController.cmApiInit() {
"CMGetUserData" {
val user = db.data.findByCard_ExtId(uid)() ?: (400 - "User not found")
mapOf("userId" to uid, "userData" to user)
}
"PrinterLogin" { mapOf("returnCode" to 1) }
"PrinterLogout" { mapOf("returnCode" to 1) }
"CMGetUserCard".unpaged("userCardList"){
db.card.findByUser_Card_ExtId(uid).map { it.apply { printCount = 99 } }
}
"CMGetUserCharacter".unpaged("userCharacterList") { db.character.findByUser_Card_ExtId(uid)}
"CMGetUserItem" {
val kind = (parsing { data["nextIndex"]!!.long } / 10000000000L).toInt()
var dat = db.item.findByUser_Card_ExtIdAndItemKind(uid, kind)
// Check if user have infinite kaika
if (kind == OgkItemType.KaikaItem.ordinal) {
val u = db.data.findByCard_ExtId(uid)()
u?.card?.aquaUser?.gameOptions?.let {
if (it.ongekiInfiniteKaika) {
dat = listOf(UserItem().apply {
user = u
itemKind = OgkItemType.KaikaItem.ordinal
itemId = 1
stock = 999
})
}
}
}
mapOf("userId" to uid, "length" to dat.size, "nextIndex" to -1, "itemKind" to kind, "userItemList" to dat)
}
"GetGameGacha" {
gdb.gacha.findAll().let {
mapOf("length" to it.size, "gameGachaList" to it, "registIdList" to emptyList<Any>())
}
}
"GetGameGachaCardById" {
val gachaId = parsing { data["gachaId"]!!.long }
gdb.gachaCard.findAllByGachaId(gachaId).let {
mapOf("gachaId" to gachaId, "length" to it.size, "isPickup" to false, "gameGachaCardList" to it,
"emissionList" to empty, "afterCalcList" to empty
)
}
}
"GetUserGacha" {
db.gacha.findByUser_Card_ExtId(uid).let{
mapOf("userId" to uid, "length" to it.size, "nextIndex" to 0, "userGachaList" to it)
}
}
"RollGacha" {
val (gachaId, tmpTimes) = parsing { data["gachaId"]!!.long to data["times"]!!.int }
var times = tmpTimes
val user = db.data.findByCard_ExtId(uid)() ?: (400 - "User not found")
val foundGacha = gdb.gacha.findById(gachaId)() ?: (404 - "Gacha not found")
val foundUserGacha = db.gacha.findByUserAndGachaId(user, gachaId)
// Official gacha percentages: R 76% SR 21% SSR 3%
val probabilities = listOf(76, 21, 3)
var rarityResults: List<Int> = emptyList()
// If this is the first x5 pull or any x11 pull, an SR is guaranteed
if(foundUserGacha != null && ((times == 5 && foundUserGacha.fiveGachaCnt==0) || times==11)){
rarityResults = listOf(3)
times -= 1
}
rarityResults = rarityResults + List(times) {
val rand = (1..100).random()
when {
rand <= probabilities[0] -> 2
rand <= probabilities[0] + probabilities[1] -> 3
else -> 4
}
}
val pulledCards = if(foundGacha.kind==0 || foundGacha.kind==3) {
// If it is the free or the permanent gacha, include cards from the gacha itself
gdb.gachaCard.findAllByGachaId(gachaId).groupBy { it.rarity }
} else {
// If it is a normal gacha, include all cards from the permanent gacha
gdb.gachaCard.findAllByGachaIdAndPermanent(gachaId).groupBy { it.rarity }
}
val finalPulls = rarityResults.map { rarity -> pulledCards[rarity]?.random()}
mapOf("length" to finalPulls.size, "gameGachaCardList" to finalPulls)
}
"CMUpsertUserGacha" api@ {
val all: OngekiCMUpsertUserGacha = mapper.convert(data["cmUpsertUserGacha"]!!)
val gacha = parsing { data["gachaId"]!!.long }
val pullCount = parsing { data["gachaCnt"]!!.int }
val earnedSelectPoints = parsing { data["selectPoint"]!!.int }
// User data
val oldUser = db.data.findByCard_ExtId(uid)()
val u: UserData = all.userData?.get(0)?.apply {
id = oldUser?.id ?: 0
card = oldUser?.card ?: us.cardRepo.findByExtId(uid)() ?: (404 - "Card not found")
// Set eventWatchedDate with lastPlayDate, because client doesn't update it
cmEventWatchedDate = oldUser?.lastPlayDate ?: ""
db.data.save(this)
} ?: oldUser ?: return@api null
db.gacha.findByUserAndGachaId(u, gacha)?.apply {
totalGachaCnt += pullCount
// If the user pulled a select gacha increase the stats
if (earnedSelectPoints > 0) {
selectPoint += earnedSelectPoints
ceilingGachaCnt += pullCount
}
// Stats related with the group of pulls the user makes
if (pullCount == 5) {
fiveGachaCnt += 1
}else if (pullCount == 11) {
elevenGachaCnt+=1
}
// Update the daily gacha
val parsedDailyGachaDate = dailyGachaDate.asDateTime()!!
if (
parsedDailyGachaDate.dayOfMonth != LocalDateTime.now().dayOfMonth ||
parsedDailyGachaDate.monthValue != LocalDateTime.now().monthValue ||
parsedDailyGachaDate.year != LocalDateTime.now().year
) {
dailyGachaCnt += pullCount
dailyGachaDate = LocalDateTime.now().toString()
}else{
dailyGachaCnt += pullCount
}
}?: UserGacha().apply {
user = u
gachaId = gacha
totalGachaCnt = pullCount
selectPoint = earnedSelectPoints
ceilingGachaCnt = if (earnedSelectPoints > 0) 1 else 0
fiveGachaCnt = if (pullCount == 5) 1 else 0
elevenGachaCnt = if (pullCount == 11) 1 else 0
dailyGachaCnt = pullCount
db.gacha.save(this)
}
all.run {
// Set users
listOfNotNull(
userCharacterList, userCardList, userItemList
).flatten().forEach { it.user = u }
// UserCharacterList
userCharacterList?.let { list ->
db.character.saveAll(list.distinctBy { it.characterId }.mapApply {
id = db.character.findByUserAndCharacterId(u, characterId)()?.id ?: 0
})
}
// UserCardList
userCardList?.let { list ->
db.card.saveAll(list.distinctBy { it.cardId }.mapApply {
id = db.card.findByUserAndCardId(u, cardId)()?.id ?: 0
})
}
// UserItemList
userItemList?.let { list ->
db.item.saveAll(list.distinctBy { it.itemKind to it.itemId }.mapApply {
id = db.item.findByUserAndItemKindAndItemId(u, itemKind, itemId)()?.id ?: 0
})
}
}
u.card?.let { cardService.updateCardTimestamp(it, "ongeki") }
null
}
"CMUpsertUserAll" api@{
val all: OngekiCMUpsertAll = mapper.convert(data["cmUpsertUserAll"]!!)
// User data
val oldUser = db.data.findByCard_ExtId(uid)()
val u: UserData = all.userData?.get(0)?.apply {
id = oldUser?.id ?: 0
card = oldUser?.card ?: us.cardRepo.findByExtId(uid)() ?: (404 - "Card not found")
// Set eventWatchedDate with lastPlayDate, because client doesn't update it
cmEventWatchedDate = oldUser?.lastPlayDate ?: ""
db.data.save(this)
} ?: oldUser ?: return@api null
all.run {
// Set users
listOfNotNull(
userActivityList, userCharacterList, userCardList, userItemList
).flatten().forEach { it.user = u }
// UserActivityList
userActivityList?.let { list ->
db.activity.saveAll(list.distinctBy { it.activityId to it.kind }.mapApply {
id = db.activity.findByUserAndKindAndActivityId(u, kind, activityId)()?.id ?: 0
})
}
// UserCharacterList
userCharacterList?.let { list ->
db.character.saveAll(list.distinctBy { it.characterId }.mapApply {
id = db.character.findByUserAndCharacterId(u, characterId)()?.id ?: 0
})
}
// UserCardList
userCardList?.let { list ->
db.card.saveAll(list.distinctBy { it.cardId }.mapApply {
id = db.card.findByUserAndCardId(u, cardId)()?.id ?: 0
})
}
// UserItemList
userItemList?.let { list ->
db.item.saveAll(list.distinctBy { it.itemKind to it.itemId }.mapApply {
id = db.item.findByUserAndItemKindAndItemId(u, itemKind, itemId)()?.id ?: 0
})
}
}
u.card?.let { cardService.updateCardTimestamp(it, "ongeki") }
null
}
"CMUpsertUserSelectGacha" api@ {
val all: OngekiCMUpsertUserSelectGacha = mapper.convert(data["cmUpsertUserSelectGacha"]!!)
val selectLog:List<OngekiCMSelectGachaLog> = mapper.convert(data["selectGachaLogList"]!!)
// User data
val oldUser = db.data.findByCard_ExtId(uid)()
val u: UserData = all.userData?.get(0)?.apply {
id = oldUser?.id ?: 0
card = oldUser?.card ?: us.cardRepo.findByExtId(uid)() ?: (404 - "Card not found")
// Set eventWatchedDate with lastPlayDate, because client doesn't update it
cmEventWatchedDate = oldUser?.lastPlayDate ?: ""
db.data.save(this)
} ?: oldUser ?: return@api null
if(selectLog.isNotEmpty()) {
val selectionInfo = selectLog.first()
db.gacha.findByUserAndGachaId(u, selectionInfo.gachaId)?.apply {
// Total reset of selectPoints
selectPoint = 0
// Set the flag for the selection gacha so that the user cant use it anymore
useSelectPoint = 1
} ?: UserGacha().apply {
// It should be impossible to reach this
user = u
gachaId = selectionInfo.gachaId
db.gacha.save(this)
}
}
all.run {
// Set users
listOfNotNull(
userCharacterList, userCardList
).flatten().forEach { it.user = u }
if (all.isNewCharacterList?.contains("1") ?: false) {
// UserCharacterList
userCharacterList?.let { list ->
db.character.saveAll(list.distinctBy { it.characterId }.mapApply {
id = db.character.findByUserAndCharacterId(u, characterId)()?.id ?: 0
})
}
}
if (all.isNewCardList?.contains("1") ?: false) {
// UserCardList
userCardList?.let { list ->
db.card.saveAll(list.distinctBy { it.cardId }.mapApply {
id = db.card.findByUserAndCardId(u, cardId)()?.id ?: 0
})
}
}
}
u.card?.let { cardService.updateCardTimestamp(it, "ongeki") }
null
}
"CMUpsertUserPrintPlaylog" api@ {
// User payment logs, useless
null
}
"CMUpsertUserPrint" api@ {
// User print information, useless
null
}
"CMUpsertUserPrintlog" api@ {
// User print logs, useless
null
}
"CMGetUserGachaSupply" {
// Mock function, not sure of functionality
mapOf("supplyId" to 0, "length" to 0, "supplyCardList" to emptyList<Any>())
}
"GetGameTheater" {
// Mock function, not sure of functionality
mapOf("length" to 0, "gameTheaterList" to emptyList<Any>(), "registIdList" to emptyList<Any>())
}
}

View File

@@ -7,6 +7,7 @@ import icu.samnyan.aqua.sega.allnet.TokenChecker
import icu.samnyan.aqua.sega.general.GameMusicPopularity
import icu.samnyan.aqua.sega.general.MeowApi
import icu.samnyan.aqua.sega.general.RequestContext
import icu.samnyan.aqua.sega.general.service.CardService
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
import icu.samnyan.aqua.spring.Metrics
import jakarta.servlet.http.HttpServletRequest
@@ -22,6 +23,7 @@ class OngekiController(
val gdb: OngekiGameRepos,
val us: AquaUserServices,
val pop: GameMusicPopularity,
val cardService: CardService,
): MeowApi({ _, resp -> if (resp is String) resp else mapper.write(resp) }) {
val log = logger()

View File

@@ -151,6 +151,19 @@ interface OgkUserRegionsRepo: OngekiUserLinked<UserRegions> {
fun findByUserAndRegionId(user: UserData, regionId: Int): UserRegions?
}
interface OgkGameGachaCardRepo : JpaRepository<GameGachaCard, Long> {
fun findAllByGachaId(gachaId: Long): List<GameGachaCard>
@Query("SELECT g FROM OngekiGameGachaCard g WHERE g.gachaId = :gachaId OR g.gachaId = 1112")
fun findAllByGachaIdAndPermanent(gachaId: Long): List<GameGachaCard>
}
interface OgkGameGachaRepo : JpaRepository<GameGacha, Long>
interface OgkUserGachaRepo : OngekiUserLinked<UserGacha> {
fun findByUserAndGachaId(user: UserData, gachaId: Long): UserGacha?
}
// Re:Fresh
interface OgkUserEventMapRepo : OngekiUserLinked<UserEventMap>
interface OgkUserSkinRepo : OngekiUserLinked<UserSkin>
@@ -195,6 +208,7 @@ class OngekiUserRepos(
val eventMap: OgkUserEventMapRepo,
val skin: OgkUserSkinRepo,
val regions: OgkUserRegionsRepo,
val gacha: OgkUserGachaRepo,
)
@Component
@@ -207,6 +221,8 @@ class OngekiGameRepos(
val present: OgkGamePresentRepo,
val reward: OgkGameRewardRepo,
val skill: OgkGameSkillRepo,
val gachaCard: OgkGameGachaCardRepo,
val gacha:OgkGameGachaRepo
)
@Component

View File

@@ -4,6 +4,7 @@ import ext.int
import ext.invoke
import ext.mapApply
import ext.minus
import icu.samnyan.aqua.sega.general.model.CardStatus
import icu.samnyan.aqua.sega.ongeki.model.OngekiUpsertUserAll
import icu.samnyan.aqua.sega.ongeki.model.UserData
import icu.samnyan.aqua.sega.ongeki.model.UserGeneralData
@@ -49,6 +50,12 @@ fun OngekiController.initUpsertAll() {
db.regions.save(region)
}
// If the user was previously migrated to Minato, saving would mark them "migrated and then cleared".
if (u.card?.status == CardStatus.MIGRATED_TO_MINATO) {
u.card?.status = CardStatus.NORMAL_MIGRATED_TO_MINATO_AND_THEN_CLEARED
us.cardRepo.save(u.card!!)
}
all.run {
// Set users
listOfNotNull(
@@ -202,6 +209,8 @@ fun OngekiController.initUpsertAll() {
id = db.kop.findByUserAndKopIdAndAreaId(u, kopId, areaId)()?.id ?: 0 }) }
}
u.card?.let { cardService.updateCardTimestamp(it, "ongeki") }
null
}
}

View File

@@ -162,26 +162,13 @@ fun OngekiController.initUser() {
)
if (u.card?.status == CardStatus.MIGRATED_TO_MINATO) {
res["userName"] = "JiaQQqun / CardMigrated"
res["level"] = 0
res["exp"] = 0
res["playerRating"] = 0
res["newPlayerRating"] = 0
res["userName"] = "${res["userName"]}"
}
res
}
"GameLogin" {
val user = db.data.findByCard_ExtId(uid)()
if (user?.card?.status == CardStatus.MIGRATED_TO_MINATO) {
"""{"returnCode":"0"}"""
}
else {
"""{"returnCode":"1"}"""
}
}
"GameLogin" { """{"returnCode":"1"}""" }
"GetUserRecentRating".unpaged {
db.generalData.findByUser_Card_ExtIdAndPropertyKey(uid, "recent_rating_list")()?.let { recent ->

View File

@@ -1,6 +1,7 @@
package icu.samnyan.aqua.sega.ongeki.model
import com.fasterxml.jackson.annotation.JsonIgnore
import com.fasterxml.jackson.annotation.JsonProperty
import jakarta.persistence.*
@Entity(name = "OngekiGameCard")
@@ -112,3 +113,41 @@ class GameSkill {
var category: String = ""
var info: String = ""
}
@Entity(name = "OngekiGameGachaCard")
@Table(name = "ongeki_game_gacha_card")
class GameGachaCard {
@Id
var cardId: Long = 0
var gachaId: Long = 0
var rarity: Int = 0
var weight: Int = 0
@JsonProperty("isPickup")
var isPickup: Boolean = false
@JsonProperty("isSelect")
var isSelect: Boolean = false
}
@Entity(name = "OngekiGameGacha")
@Table(name = "ongeki_game_gacha")
class GameGacha {
@Id
var gachaId: Long = 0
var gachaName: String = ""
var type: Int = 0
var kind: Int = 0
var maxSelectPoint: Int = 0
var ceilingCnt: Int = 0
var changeRateCnt1: Int = 0
var changeRateCnt2: Int = 0
var startDate: String = ""
var endDate: String = ""
var noticeStartDate: String = ""
var noticeEndDate: String = ""
var convertEndDate: String = ""
@JsonProperty("isCeiling")
var isCeiling: Boolean = false
}

View File

@@ -0,0 +1,44 @@
package icu.samnyan.aqua.sega.ongeki.model
class OngekiCMUpsertUserGacha {
var userData: List<UserData>? = null
var userCharacterList: List<UserCharacter>? = null
var userCardList: List<UserCard>? = null
var gameGachaCardList: List<GameGachaCard>? = null
var userItemList: List<UserItem>? = null
// These are strings with as many 1s as new elements are present
var isNewCharacterList: String? = null
var isNewCardList: String? = null
var isNewItemList: String? = null
}
class OngekiCMUpsertAll {
var userData: List<UserData>? = null
var userActivityList: List<UserActivity>? = null
var userCharacterList: List<UserCharacter>? = null
var userCardList: List<UserCard>? = null
var userItemList: List<UserItem>? = null
// These are strings with as many 1s as new elements are given
var isNewCharacterList: String? = null
var isNewCardList: String? = null
var isNewItemList: String? = null
}
class OngekiCMUpsertUserSelectGacha {
var userData: List<UserData>? = null
var userCharacterList: List<UserCharacter>? = null
var userCardList: List<UserCard>? = null
// These are strings with as many 1s as new elements are given
var isNewCharacterList: String? = null
var isNewCardList: String? = null
}
class OngekiCMSelectGachaLog {
var gachaId: Long = 0
var useSelectPoint: Int = 0
var convertType: Int = 0
var convertItem: Int = 0
}

View File

@@ -8,6 +8,7 @@ import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.util.jackson.AccessCodeSerializer
import jakarta.persistence.*
import java.time.LocalDate
import java.time.LocalDateTime
@MappedSuperclass
class OngekiUserEntity : BaseEntity(), IUserEntity<UserData> {
@@ -517,10 +518,27 @@ class UserSkin : OngekiUserEntity() {
@Entity(name = "OngekiUserRegions")
@Table(
name = "ongeki_user_regions",
uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "regionId"])]
uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "region_id"])]
)
class UserRegions : OngekiUserEntity() {
var regionId = 0
var playCount = 1
var created: String = LocalDate.now().toString()
}
@Entity(name="OngekiUserGacha")
@Table(
name = "ongeki_user_gacha",
uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "gacha_id"])]
)
class UserGacha : OngekiUserEntity() {
var gachaId:Long = 0
var totalGachaCnt = 0
var ceilingGachaCnt = 0
var selectPoint = 0
var useSelectPoint = 0
var dailyGachaCnt = 0
var fiveGachaCnt = 0
var elevenGachaCnt = 0
var dailyGachaDate: String = LocalDateTime.now().toString()
}

View File

@@ -205,28 +205,28 @@ fun WaccaServer.init() {
val go = u.card?.aquaUser?.gameOptions ?: AquaGameOptions()
// All unlock
if (go.unlockMusic && wacca.musicMapping.isNotEmpty()) {
if (go.waccaUnlockMusic && wacca.musicMapping.isNotEmpty()) {
items[MUSIC_UNLOCK()] = wacca.musicMapping.map { (id, v) -> MUSIC_UNLOCK(u, id, p1 = v.notes.size.long() - 1) }
}
if (go.unlockTickets) {
if (go.waccaUnlockTickets) {
var i = 0
items[TICKET()] = enabledTickets.flatMap { (1..5).map { TICKET(u, it).apply { id = (i++).toLong() } } }
}
if (go.unlockChara) {
if (go.waccaUnlockPlates) {
wacca.itemMapping["plates"]?.let { items[USER_PLATE()] = it.map { (k, _) -> USER_PLATE(u, k.int()) } }
}
if (go.unlockCollectables) {
if (go.waccaUnlockCollectables) {
// TODO: Add titles
mapOf("icon" to ICON, "plates" to USER_PLATE, "trophy" to TROPHY).map { (name, type) ->
mapOf("icon" to ICON, "trophy" to TROPHY).map { (name, type) ->
wacca.itemMapping[name]?.let { items[type()] = it.map { (k, _) -> type(u, k.int()) } }
}
}
val status = u.lStatus().toMutableList()
if (u.card?.status == CardStatus.MIGRATED_TO_MINATO) {
status[1] = "JiaQQqun / CardMigrated"
}
// if (u.card?.status == CardStatus.MIGRATED_TO_MINATO) {
// status[1] = "${status[1]}@AquaDX"
// }
u.run { ls(
"0 status" - status,

View File

@@ -0,0 +1,840 @@
CREATE TABLE ongeki_game_gacha
(
gacha_id BIGINT AUTO_INCREMENT NOT NULL,
gacha_name VARCHAR(255) NOT NULL,
type INT NOT NULL,
kind INT NOT NULL,
is_ceiling BOOLEAN NOT NULL,
max_select_point INT NOT NULL,
ceiling_cnt INT NOT NULL,
change_rate_cnt1 INT NOT NULL,
change_rate_cnt2 INT NOT NULL,
start_date DATETIME NOT NULL,
end_date DATETIME NOT NULL,
notice_start_date DATETIME NOT NULL,
notice_end_date DATETIME NOT NULL,
convert_end_date DATETIME NOT NULL,
CONSTRAINT pk_ongk_gacha PRIMARY KEY (gacha_id)
);
CREATE TABLE ongeki_game_gacha_card
(
card_id BIGINT AUTO_INCREMENT NOT NULL,
gacha_id BIGINT NOT NULL,
rarity INT NOT NULL,
weight INT NOT NULL,
is_pickup BOOLEAN NOT NULL,
is_select BOOLEAN NOT NULL,
CONSTRAINT pk_ongk_gacha_card PRIMARY KEY (card_id, gacha_id)
);
ALTER TABLE ongeki_game_gacha_card
ADD CONSTRAINT FK_ONGEKI_GAME_GACHA_CARD_ON_GACHA FOREIGN KEY (gacha_id) REFERENCES ongeki_game_gacha (gacha_id );
CREATE TABLE ongeki_user_gacha
(
id BIGINT AUTO_INCREMENT NOT NULL,
user_id BIGINT NOT NULL,
gacha_id BIGINT NOT NULL,
total_gacha_cnt INT NOT NULL,
ceiling_gacha_cnt INT NOT NULL,
select_point INT NOT NULL,
use_select_point INT NOT NULL,
daily_gacha_cnt INT NOT NULL,
five_gacha_cnt INT NOT NULL,
eleven_gacha_cnt INT NOT NULL,
daily_gacha_date DATETIME NOT NULL,
CONSTRAINT pk_ongk_user_gacha PRIMARY KEY (id)
);
ALTER TABLE ongeki_user_gacha
ADD CONSTRAINT FK_ONGEKI_USER_GACHA_ON_USER FOREIGN KEY (user_id) REFERENCES ongeki_user_data (id);
ALTER TABLE ongeki_user_gacha
ADD CONSTRAINT FK_ONGEKI_USER_GACHA_ON_GACHA FOREIGN KEY (gacha_id) REFERENCES ongeki_game_gacha (gacha_id);
ALTER TABLE ongeki_user_gacha
ADD CONSTRAINT unq_ongk_user_gacha UNIQUE (user_id, gacha_id);
INSERT INTO ongeki_game_gacha (gacha_id, gacha_name, `type`, kind, is_ceiling, max_select_point, ceiling_cnt, change_rate_cnt1, change_rate_cnt2, start_date, end_date, notice_start_date, notice_end_date, convert_end_date)
VALUES
(1011, '無料ガチャ', 0, 3, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1012, '無料ガチャSR確定', 0, 3, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1112, 'レギュラーガチャ', 0, 0, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1123, 'AQUA属性オンリーガチャ', 0, 1, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1128, 'bitter chocolate kiss ガチャ', 0, 4, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1135, 'オンゲキ bright 大感謝祭ガチャ', 0, 1, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1137, 'テアトル展開!プロジェクト奏坂ドールズリミテッドガチャ', 0, 1, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1138, 'WONDER SHOOTER EASTER ガチャ', 0, 4, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1139, 'ラスボス登場!?悪の侵略者ガチャ', 0, 4, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1140, 'カラフルアンブレラガチャ', 0, 4, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1141, 'It''s Showtimeワンダフルサーカスガチャ', 0, 4, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1143, 'マーチングポケッツ ピックアップガチャ', 0, 4, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1144, 'bitter flavor ピックアップガチャ', 0, 4, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1145, '7EVENDAYS⇔HOLIDAYS ピックアップガチャ', 0, 1, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1146, '⊿TRiEDGE ピックアップガチャ', 0, 1, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1147, 'R.B.P. ピックアップガチャ', 0, 1, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1148, '皇城 セツナ ピックアップガチャ', 0, 1, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1149, 'ASTERISM ピックアップガチャ', 0, 1, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1151, 'スラッシュスキル ピックアップガチャ', 0, 1, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1152, '高瀬 梨緒オンリーガチャ', 0, 1, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1154, 'トラストスキル ピックアップガチャ', 0, 1, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1156, 'bright memory振り返りガチャ', 0, 2, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1159, 'オンゲキ&オンゲキ PLUS ピックアップガチャ', 0, 2, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1160, 'SUMMER SUMMER PLUS ピックアップガチャ', 0, 2, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1161, 'R.E.D. & R.E.D. PLUS ピックアップガチャ', 0, 2, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1162, 'bright & bright MEMORY ピックアップガチャ', 0, 2, 0, 0, 0, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000'),
(1167, '6周年記念!! 6rd Anniversaryセレクトガチャ', 0, 2, 1, 11, 11, 0, 0, '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2020-01-01 00:00:00.000', '2999-01-01 00:00:00.000', '2999-01-01 00:00:00.000');
INSERT INTO ongeki_game_gacha_card (card_id, gacha_id,rarity,weight,is_pickup,is_select)
VALUES
(100003, 1112, 2, 100, 0, 0),
(100003, 1149, 2, 100, 0, 0),
(100004, 1112, 2, 100, 0, 0),
(100004, 1149, 2, 100, 0, 0),
(100006, 1112, 3, 100, 0, 0),
(100006, 1159, 3, 100, 0, 0),
(100007, 1112, 3, 100, 0, 0),
(100007, 1159, 3, 100, 0, 0),
(100008, 1112, 4, 100, 0, 0),
(100008, 1159, 4, 100, 0, 0),
(100008, 1167, 4, 100, 0, 1),
(100012, 1112, 2, 100, 0, 0),
(100012, 1149, 2, 100, 0, 0),
(100013, 1112, 2, 100, 0, 0),
(100013, 1149, 2, 100, 0, 0),
(100015, 1112, 3, 100, 0, 0),
(100015, 1159, 3, 100, 0, 0),
(100016, 1112, 3, 100, 0, 0),
(100016, 1159, 3, 100, 0, 0),
(100017, 1112, 4, 100, 0, 0),
(100017, 1159, 4, 100, 0, 0),
(100017, 1167, 4, 100, 0, 1),
(100021, 1112, 2, 100, 0, 0),
(100021, 1149, 2, 100, 0, 0),
(100022, 1112, 2, 100, 0, 0),
(100022, 1149, 2, 100, 0, 0),
(100024, 1112, 3, 100, 0, 0),
(100024, 1159, 3, 100, 0, 0),
(100025, 1112, 3, 100, 0, 0),
(100025, 1159, 3, 100, 0, 0),
(100026, 1112, 4, 100, 0, 0),
(100026, 1159, 4, 100, 0, 0),
(100026, 1167, 4, 100, 0, 1),
(100030, 1112, 2, 100, 0, 0),
(100030, 1146, 2, 100, 0, 0),
(100030, 1152, 2, 100, 1, 0),
(100033, 1152, 3, 100, 1, 0),
(100033, 1159, 3, 100, 0, 0),
(100034, 1112, 4, 100, 0, 0),
(100034, 1152, 4, 100, 1, 0),
(100034, 1159, 4, 100, 0, 0),
(100034, 1167, 4, 100, 0, 1),
(100037, 1112, 2, 100, 0, 0),
(100037, 1146, 2, 100, 0, 0),
(100040, 1112, 3, 100, 0, 0),
(100040, 1159, 3, 100, 0, 0),
(100041, 1112, 4, 100, 0, 0),
(100041, 1159, 4, 100, 0, 0),
(100041, 1167, 4, 100, 0, 1),
(100044, 1112, 2, 100, 0, 0),
(100044, 1146, 2, 100, 0, 0),
(100047, 1112, 3, 100, 0, 0),
(100047, 1159, 3, 100, 0, 0),
(100048, 1112, 4, 100, 0, 0),
(100048, 1159, 4, 100, 0, 0),
(100048, 1167, 4, 100, 0, 1),
(100051, 1112, 2, 100, 0, 0),
(100051, 1144, 2, 100, 0, 0),
(100053, 1112, 3, 100, 0, 0),
(100053, 1144, 3, 100, 0, 0),
(100053, 1159, 3, 100, 0, 0),
(100054, 1112, 4, 100, 0, 0),
(100054, 1144, 4, 100, 0, 0),
(100054, 1159, 4, 100, 0, 0),
(100054, 1167, 4, 100, 0, 1),
(100057, 1112, 2, 100, 0, 0),
(100057, 1144, 2, 100, 0, 0),
(100059, 1112, 3, 100, 0, 0),
(100059, 1144, 3, 100, 0, 0),
(100059, 1159, 3, 100, 0, 0),
(100060, 1112, 4, 100, 0, 0),
(100060, 1144, 4, 100, 0, 0),
(100060, 1159, 4, 100, 0, 0),
(100060, 1167, 4, 100, 0, 1),
(100063, 1112, 2, 100, 0, 0),
(100063, 1147, 2, 100, 0, 0),
(100065, 1112, 3, 100, 0, 0),
(100065, 1147, 3, 100, 0, 0),
(100065, 1159, 3, 100, 0, 0),
(100066, 1112, 4, 100, 0, 0),
(100066, 1147, 4, 100, 0, 0),
(100066, 1159, 4, 100, 0, 0),
(100066, 1167, 4, 100, 0, 1),
(100069, 1112, 2, 100, 0, 0),
(100069, 1145, 2, 100, 0, 0),
(100071, 1112, 3, 100, 0, 0),
(100071, 1145, 3, 100, 0, 0),
(100071, 1159, 3, 100, 0, 0),
(100072, 1112, 4, 100, 0, 0),
(100072, 1145, 4, 100, 0, 0),
(100072, 1159, 4, 100, 0, 0),
(100072, 1167, 4, 100, 0, 1),
(100075, 1112, 2, 100, 0, 0),
(100075, 1145, 2, 100, 0, 0),
(100077, 1112, 3, 100, 0, 0),
(100077, 1145, 3, 100, 0, 0),
(100077, 1159, 3, 100, 0, 0),
(100078, 1112, 4, 100, 0, 0),
(100078, 1145, 4, 100, 0, 0),
(100078, 1159, 4, 100, 0, 0),
(100078, 1167, 4, 100, 0, 1),
(100081, 1112, 2, 100, 0, 0),
(100081, 1147, 2, 100, 0, 0),
(100083, 1112, 3, 100, 0, 0),
(100083, 1147, 3, 100, 0, 0),
(100083, 1159, 3, 100, 0, 0),
(100084, 1112, 4, 100, 0, 0),
(100084, 1147, 4, 100, 0, 0),
(100084, 1159, 4, 100, 0, 0),
(100084, 1167, 4, 100, 0, 1),
(100087, 1112, 2, 100, 0, 0),
(100087, 1147, 2, 100, 0, 0),
(100089, 1112, 3, 100, 0, 0),
(100089, 1147, 3, 100, 0, 0),
(100089, 1159, 3, 100, 0, 0),
(100090, 1112, 4, 100, 0, 0),
(100090, 1147, 4, 100, 0, 0),
(100090, 1159, 4, 100, 0, 0),
(100090, 1167, 4, 100, 0, 1),
(100173, 1112, 2, 100, 0, 0),
(100173, 1149, 2, 100, 0, 0),
(100174, 1112, 2, 100, 0, 0),
(100174, 1149, 2, 100, 0, 0),
(100175, 1112, 2, 100, 0, 0),
(100175, 1149, 2, 100, 0, 0),
(100176, 1112, 2, 100, 0, 0),
(100176, 1146, 2, 100, 0, 0),
(100176, 1152, 2, 100, 1, 0),
(100177, 1112, 2, 100, 0, 0),
(100177, 1146, 2, 100, 0, 0),
(100178, 1112, 2, 100, 0, 0),
(100178, 1146, 2, 100, 0, 0),
(100179, 1112, 2, 100, 0, 0),
(100179, 1144, 2, 100, 0, 0),
(100180, 1112, 2, 100, 0, 0),
(100180, 1144, 2, 100, 0, 0),
(100181, 1112, 2, 100, 0, 0),
(100181, 1147, 2, 100, 0, 0),
(100182, 1112, 2, 100, 0, 0),
(100182, 1145, 2, 100, 0, 0),
(100183, 1112, 2, 100, 0, 0),
(100183, 1145, 2, 100, 0, 0),
(100184, 1112, 2, 100, 0, 0),
(100184, 1147, 2, 100, 0, 0),
(100185, 1112, 2, 100, 0, 0),
(100185, 1147, 2, 100, 0, 0),
(100209, 1011, 2, 100, 0, 0),
(100223, 1112, 2, 100, 0, 0),
(100223, 1149, 2, 100, 0, 0),
(100224, 1112, 2, 100, 0, 0),
(100224, 1149, 2, 100, 0, 0),
(100225, 1112, 2, 100, 0, 0),
(100225, 1149, 2, 100, 0, 0),
(100226, 1112, 2, 100, 0, 0),
(100226, 1146, 2, 100, 0, 0),
(100226, 1152, 2, 100, 1, 0),
(100227, 1112, 2, 100, 0, 0),
(100227, 1146, 2, 100, 0, 0),
(100228, 1112, 2, 100, 0, 0),
(100228, 1146, 2, 100, 0, 0),
(100229, 1112, 2, 100, 0, 0),
(100229, 1144, 2, 100, 0, 0),
(100230, 1112, 2, 100, 0, 0),
(100230, 1144, 2, 100, 0, 0),
(100231, 1112, 2, 100, 0, 0),
(100231, 1147, 2, 100, 0, 0),
(100232, 1112, 2, 100, 0, 0),
(100232, 1145, 2, 100, 0, 0),
(100233, 1112, 2, 100, 0, 0),
(100233, 1145, 2, 100, 0, 0),
(100234, 1112, 2, 100, 0, 0),
(100234, 1147, 2, 100, 0, 0),
(100235, 1112, 2, 100, 0, 0),
(100235, 1147, 2, 100, 0, 0),
(100236, 1011, 3, 100, 0, 0),
(100236, 1012, 3, 100, 0, 0),
(100237, 1011, 3, 100, 0, 0),
(100237, 1012, 3, 100, 0, 0),
(100238, 1011, 3, 100, 0, 0),
(100238, 1012, 3, 100, 0, 0),
(100239, 1011, 3, 100, 0, 0),
(100239, 1012, 3, 100, 0, 0),
(100240, 1011, 3, 100, 0, 0),
(100240, 1012, 3, 100, 0, 0),
(100241, 1011, 3, 100, 0, 0),
(100241, 1012, 3, 100, 0, 0),
(100242, 1011, 3, 100, 0, 0),
(100242, 1012, 3, 100, 0, 0),
(100243, 1011, 3, 100, 0, 0),
(100243, 1012, 3, 100, 0, 0),
(100244, 1011, 3, 100, 0, 0),
(100244, 1012, 3, 100, 0, 0),
(100245, 1011, 3, 100, 0, 0),
(100245, 1012, 3, 100, 0, 0),
(100246, 1011, 3, 100, 0, 0),
(100246, 1012, 3, 100, 0, 0),
(100247, 1011, 3, 100, 0, 0),
(100247, 1012, 3, 100, 0, 0),
(100248, 1011, 3, 100, 0, 0),
(100248, 1012, 3, 100, 0, 0),
(100249, 1112, 3, 100, 0, 0),
(100249, 1159, 3, 100, 0, 0),
(100250, 1112, 3, 100, 0, 0),
(100250, 1159, 3, 100, 0, 0),
(100251, 1112, 3, 100, 0, 0),
(100251, 1159, 3, 100, 0, 0),
(100252, 1011, 3, 100, 0, 0),
(100252, 1012, 3, 100, 0, 0),
(100253, 1011, 3, 100, 0, 0),
(100253, 1012, 3, 100, 0, 0),
(100254, 1011, 3, 100, 0, 0),
(100254, 1012, 3, 100, 0, 0),
(100255, 1011, 3, 100, 0, 0),
(100255, 1012, 3, 100, 0, 0),
(100256, 1011, 3, 100, 0, 0),
(100256, 1012, 3, 100, 0, 0),
(100257, 1011, 3, 100, 0, 0),
(100257, 1012, 3, 100, 0, 0),
(100258, 1011, 3, 100, 0, 0),
(100258, 1012, 3, 100, 0, 0),
(100259, 1011, 3, 100, 0, 0),
(100259, 1012, 3, 100, 0, 0),
(100260, 1011, 3, 100, 0, 0),
(100260, 1012, 3, 100, 0, 0),
(100261, 1011, 3, 100, 0, 0),
(100261, 1012, 3, 100, 0, 0),
(100262, 1112, 3, 100, 0, 0),
(100262, 1159, 3, 100, 0, 0),
(100263, 1112, 3, 100, 0, 0),
(100263, 1159, 3, 100, 0, 0),
(100264, 1112, 3, 100, 0, 0),
(100264, 1159, 3, 100, 0, 0),
(100265, 1112, 3, 100, 0, 0),
(100265, 1152, 3, 100, 1, 0),
(100265, 1159, 3, 100, 0, 0),
(100266, 1112, 3, 100, 0, 0),
(100266, 1159, 3, 100, 0, 0),
(100267, 1112, 3, 100, 0, 0),
(100267, 1159, 3, 100, 0, 0),
(100268, 1112, 3, 100, 0, 0),
(100268, 1144, 3, 100, 0, 0),
(100268, 1159, 3, 100, 0, 0),
(100269, 1112, 3, 100, 0, 0),
(100269, 1144, 3, 100, 0, 0),
(100269, 1159, 3, 100, 0, 0),
(100270, 1112, 3, 100, 0, 0),
(100270, 1147, 3, 100, 0, 0),
(100270, 1159, 3, 100, 0, 0),
(100271, 1112, 3, 100, 0, 0),
(100271, 1145, 3, 100, 0, 0),
(100271, 1159, 3, 100, 0, 0),
(100272, 1112, 3, 100, 0, 0),
(100272, 1145, 3, 100, 0, 0),
(100272, 1159, 3, 100, 0, 0),
(100273, 1112, 3, 100, 0, 0),
(100273, 1147, 3, 100, 0, 0),
(100273, 1159, 3, 100, 0, 0),
(100274, 1112, 3, 100, 0, 0),
(100274, 1147, 3, 100, 0, 0),
(100274, 1159, 3, 100, 0, 0),
(100275, 1112, 4, 100, 0, 0),
(100275, 1159, 4, 100, 0, 0),
(100275, 1167, 4, 100, 0, 1),
(100276, 1112, 4, 100, 0, 0),
(100276, 1159, 4, 100, 0, 0),
(100276, 1167, 4, 100, 0, 1),
(100277, 1112, 4, 100, 0, 0),
(100277, 1159, 4, 100, 0, 0),
(100277, 1167, 4, 100, 0, 1),
(100278, 1112, 4, 100, 0, 0),
(100278, 1152, 4, 100, 1, 0),
(100278, 1159, 4, 100, 0, 0),
(100278, 1167, 4, 100, 0, 1),
(100280, 1112, 4, 100, 0, 0),
(100280, 1159, 4, 100, 0, 0),
(100280, 1167, 4, 100, 0, 1),
(100282, 1112, 4, 100, 0, 0),
(100282, 1144, 4, 100, 0, 0),
(100282, 1159, 4, 100, 0, 0),
(100282, 1167, 4, 100, 0, 1),
(100284, 1112, 4, 100, 0, 0),
(100284, 1145, 4, 100, 0, 0),
(100284, 1159, 4, 100, 0, 0),
(100284, 1167, 4, 100, 0, 1),
(100285, 1112, 4, 100, 0, 0),
(100285, 1145, 4, 100, 0, 0),
(100285, 1159, 4, 100, 0, 0),
(100285, 1167, 4, 100, 0, 1),
(100286, 1112, 4, 100, 0, 0),
(100286, 1147, 4, 100, 0, 0),
(100286, 1159, 4, 100, 0, 0),
(100286, 1167, 4, 100, 0, 1),
(100336, 1011, 3, 100, 0, 0),
(100336, 1012, 3, 100, 0, 0),
(100337, 1011, 3, 100, 0, 0),
(100337, 1012, 3, 100, 0, 0),
(100338, 1011, 3, 100, 0, 0),
(100338, 1012, 3, 100, 0, 0),
(100339, 1112, 2, 100, 0, 0),
(100339, 1149, 2, 100, 0, 0),
(100340, 1112, 2, 100, 0, 0),
(100340, 1149, 2, 100, 0, 0),
(100341, 1112, 2, 100, 0, 0),
(100341, 1149, 2, 100, 0, 0),
(100342, 1112, 2, 100, 0, 0),
(100342, 1146, 2, 100, 0, 0),
(100342, 1152, 2, 100, 1, 0),
(100343, 1112, 2, 100, 0, 0),
(100343, 1146, 2, 100, 0, 0),
(100344, 1112, 2, 100, 0, 0),
(100344, 1146, 2, 100, 0, 0),
(100345, 1112, 2, 100, 0, 0),
(100345, 1144, 2, 100, 0, 0),
(100346, 1112, 2, 100, 0, 0),
(100346, 1144, 2, 100, 0, 0),
(100347, 1112, 2, 100, 0, 0),
(100347, 1147, 2, 100, 0, 0),
(100348, 1112, 2, 100, 0, 0),
(100348, 1145, 2, 100, 0, 0),
(100349, 1112, 2, 100, 0, 0),
(100349, 1145, 2, 100, 0, 0),
(100350, 1112, 2, 100, 0, 0),
(100350, 1147, 2, 100, 0, 0),
(100351, 1112, 2, 100, 0, 0),
(100351, 1147, 2, 100, 0, 0),
(100407, 1112, 4, 100, 0, 0),
(100407, 1143, 4, 100, 0, 0),
(100407, 1160, 4, 100, 0, 0),
(100407, 1167, 4, 100, 0, 1),
(100411, 1112, 2, 100, 0, 0),
(100411, 1146, 2, 100, 0, 0),
(100411, 1152, 2, 100, 1, 0),
(100412, 1112, 2, 100, 0, 0),
(100412, 1146, 2, 100, 0, 0),
(100413, 1112, 2, 100, 0, 0),
(100413, 1146, 2, 100, 0, 0),
(100414, 1112, 2, 100, 0, 0),
(100415, 1112, 2, 100, 0, 0),
(100416, 1112, 2, 100, 0, 0),
(100417, 1112, 2, 100, 0, 0),
(100418, 1112, 3, 100, 0, 0),
(100418, 1149, 3, 100, 0, 0),
(100418, 1160, 3, 100, 0, 0),
(100419, 1112, 3, 100, 0, 0),
(100419, 1160, 3, 100, 0, 0),
(100420, 1112, 3, 100, 0, 0),
(100420, 1144, 3, 100, 0, 0),
(100421, 1147, 3, 100, 0, 0),
(100421, 1160, 3, 100, 0, 0),
(100422, 1112, 3, 100, 0, 0),
(100422, 1144, 3, 100, 0, 0),
(100422, 1160, 3, 100, 0, 0),
(100423, 1112, 3, 100, 0, 0),
(100423, 1160, 3, 100, 0, 0),
(100424, 1112, 3, 100, 0, 0),
(100424, 1143, 3, 100, 0, 0),
(100424, 1160, 3, 100, 0, 0),
(100425, 1112, 3, 100, 0, 0),
(100425, 1160, 3, 100, 0, 0),
(100426, 1112, 3, 100, 0, 0),
(100426, 1147, 3, 100, 0, 0),
(100426, 1160, 3, 100, 0, 0),
(100427, 1112, 3, 100, 0, 0),
(100427, 1160, 3, 100, 0, 0),
(100428, 1112, 3, 100, 0, 0),
(100428, 1160, 3, 100, 0, 0),
(100429, 1112, 3, 100, 0, 0),
(100429, 1160, 3, 100, 0, 0),
(100430, 1112, 3, 100, 0, 0),
(100430, 1160, 3, 100, 0, 0),
(100431, 1112, 4, 100, 0, 0),
(100431, 1143, 4, 100, 0, 0),
(100431, 1160, 4, 100, 0, 0),
(100431, 1167, 4, 100, 0, 1),
(100432, 1112, 4, 100, 0, 0),
(100432, 1143, 4, 100, 0, 0),
(100432, 1160, 4, 100, 0, 0),
(100432, 1167, 4, 100, 0, 1),
(100433, 1112, 4, 100, 0, 0),
(100433, 1160, 4, 100, 0, 0),
(100433, 1167, 4, 100, 0, 1),
(100434, 1112, 4, 100, 0, 0),
(100434, 1144, 4, 100, 0, 0),
(100434, 1160, 4, 100, 0, 0),
(100434, 1167, 4, 100, 0, 1),
(100435, 1112, 4, 100, 0, 0),
(100435, 1123, 4, 100, 0, 0),
(100435, 1147, 4, 100, 0, 0),
(100435, 1154, 4, 100, 0, 0),
(100435, 1160, 4, 100, 0, 0),
(100435, 1167, 4, 100, 0, 1),
(100436, 1112, 4, 100, 0, 0),
(100436, 1160, 4, 100, 0, 0),
(100436, 1167, 4, 100, 0, 1),
(100437, 1112, 4, 100, 0, 0),
(100437, 1160, 4, 100, 0, 0),
(100437, 1167, 4, 100, 0, 1),
(100438, 1112, 4, 100, 0, 0),
(100438, 1147, 4, 100, 0, 0),
(100438, 1160, 4, 100, 0, 0),
(100438, 1167, 4, 100, 0, 1),
(100439, 1112, 4, 100, 0, 0),
(100439, 1146, 4, 100, 0, 0),
(100439, 1152, 4, 100, 1, 0),
(100439, 1160, 4, 100, 0, 0),
(100439, 1167, 4, 100, 0, 1),
(100440, 1011, 2, 100, 0, 0),
(100441, 1011, 2, 100, 0, 0),
(100445, 1112, 2, 100, 0, 0),
(100445, 1143, 2, 100, 0, 0),
(100447, 1112, 2, 100, 0, 0),
(100447, 1143, 2, 100, 0, 0),
(100449, 1112, 2, 100, 0, 0),
(100449, 1143, 2, 100, 0, 0),
(100454, 1011, 4, 100, 0, 0),
(100606, 1112, 3, 100, 0, 0),
(100607, 1112, 3, 100, 0, 0),
(100608, 1112, 3, 100, 0, 0),
(100608, 1143, 3, 100, 0, 0),
(100608, 1160, 3, 100, 0, 0),
(100609, 1112, 3, 100, 0, 0),
(100609, 1143, 3, 100, 0, 0),
(100609, 1160, 3, 100, 0, 0),
(100610, 1112, 3, 100, 0, 0),
(100610, 1143, 3, 100, 0, 0),
(100610, 1160, 3, 100, 0, 0),
(100634, 1011, 3, 100, 0, 0),
(100634, 1012, 3, 100, 0, 0),
(100636, 1011, 3, 100, 0, 0),
(100636, 1012, 3, 100, 0, 0),
(100637, 1011, 3, 100, 0, 0),
(100637, 1012, 3, 100, 0, 0),
(100638, 1011, 3, 100, 0, 0),
(100638, 1012, 3, 100, 0, 0),
(100639, 1011, 3, 100, 0, 0),
(100639, 1012, 3, 100, 0, 0),
(100640, 1011, 3, 100, 0, 0),
(100640, 1012, 3, 100, 0, 0),
(100641, 1011, 3, 100, 0, 0),
(100641, 1012, 3, 100, 0, 0),
(100642, 1011, 3, 100, 0, 0),
(100642, 1012, 3, 100, 0, 0),
(100643, 1011, 3, 100, 0, 0),
(100643, 1012, 3, 100, 0, 0),
(100644, 1011, 3, 100, 0, 0),
(100644, 1012, 3, 100, 0, 0),
(100645, 1011, 3, 100, 0, 0),
(100645, 1012, 3, 100, 0, 0),
(100646, 1011, 3, 100, 0, 0),
(100646, 1012, 3, 100, 0, 0),
(100647, 1112, 2, 100, 0, 0),
(100648, 1112, 2, 100, 0, 0),
(100649, 1112, 2, 100, 0, 0),
(100650, 1011, 2, 100, 0, 0),
(100651, 1011, 2, 100, 0, 0),
(100652, 1011, 2, 100, 0, 0),
(100653, 1112, 2, 100, 0, 0),
(100653, 1143, 2, 100, 0, 0),
(100654, 1112, 2, 100, 0, 0),
(100654, 1143, 2, 100, 0, 0),
(100655, 1112, 2, 100, 0, 0),
(100655, 1143, 2, 100, 0, 0),
(100662, 1112, 2, 100, 0, 0),
(100665, 1011, 3, 100, 0, 0),
(100665, 1012, 3, 100, 0, 0),
(100666, 1011, 3, 100, 0, 0),
(100666, 1012, 3, 100, 0, 0),
(100667, 1011, 3, 100, 0, 0),
(100667, 1012, 3, 100, 0, 0),
(100692, 1112, 2, 100, 0, 0),
(100692, 1149, 2, 100, 0, 0),
(100693, 1112, 2, 100, 0, 0),
(100693, 1149, 2, 100, 0, 0),
(100694, 1112, 2, 100, 0, 0),
(100694, 1149, 2, 100, 0, 0),
(100701, 1112, 2, 100, 0, 0),
(100701, 1146, 2, 100, 0, 0),
(100701, 1152, 2, 100, 1, 0),
(100702, 1011, 2, 100, 0, 0),
(100703, 1011, 2, 100, 0, 0),
(100711, 1112, 3, 100, 0, 0),
(100711, 1161, 3, 100, 0, 0),
(100712, 1112, 3, 100, 0, 0),
(100712, 1161, 3, 100, 0, 0),
(100725, 1112, 4, 100, 0, 0),
(100725, 1146, 4, 100, 0, 0),
(100725, 1161, 4, 100, 0, 0),
(100725, 1167, 4, 100, 0, 1),
(100726, 1112, 4, 100, 0, 0),
(100726, 1161, 4, 100, 0, 0),
(100726, 1167, 4, 100, 0, 1),
(100755, 1112, 2, 100, 0, 0),
(100756, 1112, 2, 100, 0, 0),
(100757, 1112, 3, 100, 0, 0),
(100757, 1160, 3, 100, 0, 0),
(100758, 1112, 3, 100, 0, 0),
(100758, 1160, 3, 100, 0, 0),
(100759, 1112, 3, 100, 0, 0),
(100759, 1160, 3, 100, 0, 0),
(100760, 1112, 4, 100, 0, 0),
(100760, 1149, 4, 100, 0, 0),
(100760, 1160, 4, 100, 0, 0),
(100760, 1167, 4, 100, 0, 1),
(100761, 1112, 4, 100, 0, 0),
(100761, 1160, 4, 100, 0, 0),
(100761, 1167, 4, 100, 0, 1),
(100762, 1112, 3, 100, 0, 0),
(100762, 1160, 3, 100, 0, 0),
(100763, 1112, 3, 100, 0, 0),
(100763, 1160, 3, 100, 0, 0),
(100764, 1112, 3, 100, 0, 0),
(100764, 1160, 3, 100, 0, 0),
(100765, 1112, 3, 100, 0, 0),
(100765, 1160, 3, 100, 0, 0),
(100766, 1112, 3, 100, 0, 0),
(100766, 1146, 3, 100, 0, 0),
(100766, 1160, 3, 100, 0, 0),
(100767, 1112, 4, 100, 0, 0),
(100767, 1160, 4, 100, 0, 0),
(100767, 1167, 4, 100, 0, 1),
(100768, 1112, 4, 100, 0, 0),
(100768, 1160, 4, 100, 0, 0),
(100768, 1167, 4, 100, 0, 1),
(100771, 1112, 2, 100, 0, 0),
(100772, 1011, 3, 100, 0, 0),
(100772, 1012, 3, 100, 0, 0),
(100777, 1112, 3, 100, 0, 0),
(100777, 1146, 3, 100, 0, 0),
(100777, 1152, 3, 100, 1, 0),
(100777, 1160, 3, 100, 0, 0),
(100778, 1112, 3, 100, 0, 0),
(100778, 1160, 3, 100, 0, 0),
(100779, 1112, 4, 100, 0, 0),
(100779, 1154, 4, 100, 0, 0),
(100779, 1160, 4, 100, 0, 0),
(100779, 1167, 4, 100, 0, 1),
(100780, 1112, 4, 100, 0, 0),
(100780, 1149, 4, 100, 0, 0),
(100780, 1160, 4, 100, 0, 0),
(100780, 1167, 4, 100, 0, 1),
(100783, 1112, 3, 100, 0, 0),
(100783, 1160, 3, 100, 0, 0),
(100784, 1112, 4, 100, 0, 0),
(100784, 1160, 4, 100, 0, 0),
(100784, 1167, 4, 100, 0, 1),
(100785, 1112, 3, 100, 0, 0),
(100785, 1149, 3, 100, 0, 0),
(100785, 1151, 3, 100, 0, 0),
(100785, 1161, 3, 100, 0, 0),
(100786, 1112, 3, 100, 0, 0),
(100786, 1149, 3, 100, 0, 0),
(100786, 1151, 3, 100, 0, 0),
(100786, 1161, 3, 100, 0, 0),
(100787, 1112, 4, 100, 0, 0),
(100787, 1151, 4, 100, 0, 0),
(100787, 1161, 4, 100, 0, 0),
(100787, 1167, 4, 100, 0, 1),
(100805, 1112, 3, 100, 0, 0),
(100805, 1160, 3, 100, 0, 0),
(100842, 1112, 3, 100, 0, 0),
(100980, 1011, 4, 100, 0, 0),
(100982, 1112, 4, 100, 0, 0),
(100982, 1161, 4, 100, 0, 0),
(100982, 1167, 4, 100, 0, 1),
(100983, 1112, 4, 100, 0, 0),
(100983, 1161, 4, 100, 0, 0),
(100983, 1167, 4, 100, 0, 1),
(100984, 1137, 4, 100, 1, 0),
(100984, 1146, 4, 100, 0, 0),
(100984, 1167, 4, 100, 0, 1),
(100985, 1112, 4, 100, 0, 0),
(100985, 1161, 4, 100, 0, 0),
(100985, 1167, 4, 100, 0, 1),
(100986, 1137, 4, 100, 1, 0),
(100986, 1167, 4, 100, 0, 1),
(100987, 1112, 4, 100, 0, 0),
(100987, 1149, 4, 100, 0, 0),
(100987, 1154, 4, 100, 0, 0),
(100987, 1161, 4, 100, 0, 0),
(100987, 1167, 4, 100, 0, 1),
(100988, 1112, 4, 100, 0, 0),
(100988, 1154, 4, 100, 0, 0),
(100988, 1161, 4, 100, 0, 0),
(100988, 1167, 4, 100, 0, 1),
(100989, 1112, 4, 100, 0, 0),
(100989, 1123, 4, 100, 0, 0),
(100989, 1146, 4, 100, 0, 0),
(100989, 1152, 4, 100, 1, 0),
(100989, 1161, 4, 100, 0, 0),
(100989, 1167, 4, 100, 0, 1),
(100996, 1112, 3, 100, 0, 0),
(100996, 1161, 3, 100, 0, 0),
(100997, 1137, 3, 100, 1, 0),
(100998, 1137, 3, 100, 1, 0),
(100999, 1112, 3, 100, 0, 0),
(100999, 1161, 3, 100, 0, 0),
(101000, 1112, 3, 100, 0, 0),
(101000, 1161, 3, 100, 0, 0),
(101001, 1137, 3, 100, 1, 0),
(101002, 1137, 3, 100, 1, 0),
(101002, 1146, 3, 100, 0, 0),
(101003, 1112, 3, 100, 0, 0),
(101003, 1161, 3, 100, 0, 0),
(101004, 1112, 3, 100, 0, 0),
(101004, 1161, 3, 100, 0, 0),
(101005, 1112, 3, 100, 0, 0),
(101005, 1149, 3, 100, 0, 0),
(101005, 1161, 3, 100, 0, 0),
(101020, 1112, 2, 100, 0, 0),
(101020, 1149, 2, 100, 0, 0),
(101021, 1112, 2, 100, 0, 0),
(101021, 1146, 2, 100, 0, 0),
(101021, 1152, 2, 100, 1, 0),
(101022, 1112, 2, 100, 0, 0),
(101023, 1112, 2, 100, 0, 0),
(101023, 1146, 2, 100, 0, 0),
(101024, 1112, 2, 100, 0, 0),
(101024, 1146, 2, 100, 0, 0),
(101025, 1112, 2, 100, 0, 0),
(101025, 1149, 2, 100, 0, 0),
(101026, 1112, 2, 100, 0, 0),
(101293, 1112, 4, 100, 0, 0),
(101293, 1123, 4, 100, 0, 0),
(101293, 1137, 4, 100, 1, 0),
(101293, 1154, 4, 100, 0, 0),
(101293, 1161, 4, 100, 0, 0),
(101293, 1167, 4, 100, 0, 1),
(101294, 1112, 4, 100, 0, 0),
(101294, 1161, 4, 100, 0, 0),
(101294, 1167, 4, 100, 0, 1),
(101295, 1112, 4, 100, 0, 0),
(101295, 1123, 4, 100, 0, 0),
(101295, 1149, 4, 100, 0, 0),
(101295, 1161, 4, 100, 0, 0),
(101295, 1167, 4, 100, 0, 1),
(101296, 1112, 4, 100, 0, 0),
(101296, 1149, 4, 100, 0, 0),
(101296, 1151, 4, 100, 0, 0),
(101296, 1154, 4, 100, 0, 0),
(101296, 1161, 4, 100, 0, 0),
(101296, 1167, 4, 100, 0, 1),
(101297, 1137, 4, 100, 1, 0),
(101297, 1167, 4, 100, 0, 1),
(101298, 1112, 3, 100, 0, 0),
(101298, 1146, 3, 100, 0, 0),
(101298, 1161, 3, 100, 0, 0),
(101299, 1112, 3, 100, 0, 0),
(101299, 1161, 3, 100, 0, 0),
(101300, 1112, 3, 100, 0, 0),
(101300, 1151, 3, 100, 0, 0),
(101300, 1161, 3, 100, 0, 0),
(101301, 1137, 3, 100, 1, 0),
(101301, 1146, 3, 100, 0, 0),
(101301, 1152, 3, 100, 1, 0),
(101315, 1146, 2, 100, 0, 0),
(101315, 1151, 2, 100, 0, 0),
(101316, 1151, 2, 100, 0, 0),
(101320, 1112, 4, 100, 0, 0),
(101320, 1146, 4, 100, 0, 0),
(101320, 1161, 4, 100, 0, 0),
(101320, 1167, 4, 100, 0, 1),
(101321, 1112, 3, 100, 0, 0),
(101321, 1161, 3, 100, 0, 0),
(101553, 1011, 4, 100, 0, 0),
(101566, 1112, 3, 100, 0, 0),
(101566, 1135, 3, 100, 1, 0),
(101566, 1148, 3, 100, 0, 0),
(101566, 1162, 3, 100, 0, 0),
(101567, 1112, 4, 100, 0, 0),
(101567, 1135, 4, 100, 1, 0),
(101567, 1148, 4, 100, 0, 0),
(101567, 1151, 4, 100, 0, 0),
(101567, 1162, 4, 100, 0, 0),
(101567, 1167, 4, 100, 0, 1),
(101589, 1011, 2, 100, 0, 0),
(101592, 1112, 4, 100, 0, 0),
(101592, 1135, 4, 100, 1, 0),
(101592, 1149, 4, 100, 0, 0),
(101592, 1154, 4, 100, 0, 0),
(101592, 1162, 4, 100, 0, 0),
(101592, 1167, 4, 100, 0, 1),
(101593, 1137, 4, 100, 1, 0),
(101593, 1167, 4, 100, 0, 1),
(101594, 1112, 4, 100, 0, 0),
(101594, 1123, 4, 100, 0, 0),
(101594, 1135, 4, 100, 1, 0),
(101594, 1154, 4, 100, 0, 0),
(101594, 1162, 4, 100, 0, 0),
(101594, 1167, 4, 100, 0, 1),
(101595, 1112, 4, 100, 0, 0),
(101595, 1123, 4, 100, 0, 0),
(101595, 1128, 4, 100, 1, 0),
(101595, 1135, 4, 100, 1, 0),
(101595, 1167, 4, 100, 0, 1),
(101596, 1112, 4, 100, 0, 0),
(101596, 1138, 4, 100, 1, 0),
(101596, 1146, 4, 100, 0, 0),
(101596, 1154, 4, 100, 0, 0),
(101596, 1156, 4, 100, 1, 0),
(101596, 1162, 4, 100, 0, 0),
(101596, 1167, 4, 100, 0, 1),
(101597, 1112, 4, 100, 0, 0),
(101597, 1139, 4, 100, 1, 0),
(101597, 1156, 4, 100, 1, 0),
(101597, 1162, 4, 100, 0, 0),
(101597, 1167, 4, 100, 0, 1),
(101598, 1137, 4, 100, 1, 0),
(101598, 1148, 4, 100, 0, 0),
(101599, 1112, 4, 100, 0, 0),
(101599, 1140, 4, 100, 1, 0),
(101599, 1156, 4, 100, 1, 0),
(101599, 1162, 4, 100, 0, 0),
(101599, 1167, 4, 100, 0, 1),
(101600, 1112, 4, 100, 0, 0),
(101600, 1141, 4, 100, 1, 0),
(101600, 1156, 4, 100, 1, 0),
(101600, 1162, 4, 100, 0, 0),
(101600, 1167, 4, 100, 0, 1),
(101601, 1137, 3, 100, 1, 0),
(101602, 1112, 3, 100, 0, 0),
(101602, 1135, 3, 100, 1, 0),
(101602, 1149, 3, 100, 0, 0),
(101602, 1162, 3, 100, 0, 0),
(101603, 1112, 3, 100, 0, 0),
(101603, 1128, 3, 100, 1, 0),
(101603, 1135, 3, 100, 1, 0),
(101603, 1146, 3, 100, 0, 0),
(101603, 1162, 3, 100, 0, 0),
(101604, 1112, 3, 100, 0, 0),
(101604, 1138, 3, 100, 1, 0),
(101604, 1149, 3, 100, 0, 0),
(101604, 1156, 3, 100, 1, 0),
(101604, 1162, 3, 100, 0, 0),
(101605, 1112, 3, 100, 0, 0),
(101605, 1139, 3, 100, 1, 0),
(101605, 1156, 3, 100, 1, 0),
(101605, 1162, 3, 100, 0, 0),
(101606, 1137, 3, 100, 1, 0),
(101606, 1167, 3, 100, 0, 1),
(101607, 1112, 3, 100, 0, 0),
(101607, 1140, 3, 100, 1, 0),
(101607, 1156, 3, 100, 1, 0),
(101607, 1162, 3, 100, 0, 0),
(101608, 1112, 3, 100, 0, 0),
(101608, 1141, 3, 100, 1, 0),
(101608, 1156, 3, 100, 1, 0),
(101608, 1162, 3, 100, 0, 0),
(101619, 1112, 2, 100, 0, 0),
(101619, 1135, 2, 100, 1, 0),
(101619, 1148, 2, 100, 0, 0),
(101620, 1011, 2, 100, 0, 0),
(101627, 1011, 4, 100, 0, 0);

View File

@@ -0,0 +1,101 @@
INSERT INTO chusan_game_event (id, type, end_date, start_date, enable)
VALUES
(2507, 13, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(2508, 13, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(2509, 13, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(2510, 13, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(2511, 13, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(2512, 13, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17000, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17001, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17002, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17003, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17004, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17005, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17006, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17007, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17008, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17009, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17010, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17011, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17012, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17013, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17014, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17015, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17016, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17017, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17018, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17019, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17020, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17021, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17022, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17023, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17024, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17025, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17026, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17027, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17028, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17029, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17030, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17031, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17032, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17033, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17034, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17035, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17036, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17040, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17041, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17042, 17, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17043, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17044, 14, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17045, 10, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17046, 12, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17047, 7, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17048, 7, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17049, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17050, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17051, 5, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17052, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17053, 11, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17054, 4, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17055, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17056, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17057, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17058, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17059, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17060, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17061, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17062, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17063, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17064, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17065, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17066, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17067, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17068, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17069, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17070, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17071, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17072, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17073, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17074, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17075, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17076, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17077, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17078, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17079, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17080, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17081, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17082, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17083, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17084, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17085, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17086, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17087, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17088, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17089, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true)
ON DUPLICATE KEY UPDATE
type = VALUES(type),
end_date = VALUES(end_date),
start_date = VALUES(start_date),
enable = VALUES(enable);

View File

@@ -0,0 +1 @@
DROP TABLE aqua_net_user_fedy;

View File

@@ -0,0 +1,11 @@
# Fix NULL values in maimai2_user_detail point and total_point fields
# These fields were added without NOT NULL constraint in V1000_36
# First, set NULL values to 0
UPDATE maimai2_user_detail SET point = 0 WHERE point IS NULL;
UPDATE maimai2_user_detail SET total_point = 0 WHERE total_point IS NULL;
# Then add NOT NULL constraint
ALTER TABLE maimai2_user_detail MODIFY point INT NOT NULL DEFAULT 0;
ALTER TABLE maimai2_user_detail MODIFY total_point INT NOT NULL DEFAULT 0;

View File

@@ -0,0 +1,289 @@
INSERT IGNORE INTO `ongeki_game_music` (id, artist_name, boss_card_id, boss_level, genre, level0, level1, level2, level3, level4, name, sort_name) VALUES
(337, 'LOVE☆MAXガールズ「ゴシックは魔法乙女」', '100816', 15, 'POPSANIME', '4', '6', '9', 12, 0, 'わたしたち魔法乙女です☆', 'ワタシタチマホウオトメテス'),
(591, 'TJ.hangneil', '101557', 80, 'オンゲキ', '6', '10', '13', 15, 0, 'Apollo', 'APOLLO'),
(945, '柊かえCV:立花芽恵夢「Re:ステージ!プリズムステップ」', '100915', 40, 'POPSANIME', '4', '8', '12', 14, 0, 'ガジェットはプリンセス', 'カシエツトハフリンセス'),
(946, 'テトラルキア「Re:ステージ!プリズムステップ」', '100915', 15, 'POPSANIME', '2', '7', '11', 14, 0, 'ユニゾンモノローグ', 'ユニソンモノロク'),
(1000, 'あべにゅうぷろじぇくと feat.エンジェルオールスターズ(めぐる・すみれ・遥・葵・クルミ・テスラ・ナイン)', '102185', 7, 'POPSANIME', '3', '7', '10', 12, 0, 'えんじぇる♡ピタゴリ☆ほーみたい!', 'エンシエルヒタコリホミタイ'),
(1001, '周防パトラ', '102217', 10, 'POPSANIME', '4', '8', '11', 13, 0, '偉大なる悪魔は実は大天使パトラちゃん様なのだ!', 'イタイナルアクマハシツハタイテンシハトラチヤンサマナノタ'),
(1002, '周防パトラ', '102217', 8, 'POPSANIME', '3', '6', '9', 12, 0, 'ハートサーモグラフィー', 'ハトサモクラフイ'),
(1061, 'ARForest', '100765', 35, 'オンゲキ', '5', '9', '11', 14, 0, 'Flow Into You', 'FLOWINTOYOU'),
(1074, 'Feryquitous高瀬 梨緒(CV久保 ユリカ)', '101872', 24, 'オンゲキ', '3', '8', '12', 13, 0, 'Ai Me', 'AIME'),
(1075, 'KENSUKE結城 莉玖(CV朝日奈 丸佳)', '101873', 25, 'オンゲキ', '4', '8', '12', 13, 0, 'がおっと!!最強☆ぱーりーないと', 'カオツトサイキヨウハリナイト'),
(1076, '曲:テラ(少女理論観測所)/歌:藍原 椿(CV橋本 ちなみ)', '101874', 26, 'オンゲキ', '3', '7', '11', 13, 0, '耐冬花麗', 'ツハキウルワシ'),
(1077, '提供', '100070', 15, 'オンゲキ', '4', '8', '12', 14, 0, 'Never Ending Adventure', 'NEVERENDINGADVENTURE'),
(1078, 'brz1128', '100762', 40, 'オンゲキ', '4', '9', '12', 14, 0, 'ALLNIGHT_DANCER', 'ALLNIGHTDANCER'),
(1079, '7mai', '101571', 63, 'オンゲキ', '6', '9', '12', 14, 0, 'Nýx', 'NYX'),
(1080, 'Aoi', '100785', 80, 'オンゲキ', '7', '10', '13', 15, 0, 'Sargasso', 'SARGASSO'),
(1083, 'Artifact vs. Dualcast', '100001', 43, 'チュウマイ', '4', '9', '12', 14, 0, 'Energizing Flame', 'ENERGIZINGFLAME'),
(1084, 'owltree feat.nietree', '100064', 44, 'チュウマイ', '4', '9', '12', 14, 0, 'QuiQ', 'QUIQ'),
(1085, '歌:長瀬有花/曲:いよわ', '102586', 5, 'POPSANIME', '2', '6', '10', 13, 0, 'オレンジスケール', 'オレンシスケル'),
(1086, '歌:長瀬有花/曲:いよわ', '102586', 15, 'POPSANIME', '4', '8', '11', 13, 0, 'ほんの感想', 'ホンノカンソウ'),
(1087, 'ジョー・力一', '101600', 11, 'POPSANIME', '2', '7', '10', 13, 0, 'レイテストショーマン', 'レイテストシヨマン'),
(1088, '名取さな', '101842', 37, 'POPSANIME', '2', '7', '10', 13, 0, 'いっかい書いてさようなら', 'イツカイカイテサヨウナラ'),
(1089, 'K-forest「Phigros」', '100209', 15, 'VARIETY', '3', '9', '12', 14, 0, 'Break Over', 'BREAKOVER'),
(1090, 'Halv「Phigros」', '100029', 25, 'VARIETY', '3', '9', '12', 14, 0, 'Concvssion', 'CONCVSSION'),
(1091, 'Sakuzyo「Phigros」', '100074', 65, 'VARIETY', '6', '10', '13', 15, 0, 'Distorted Fate', 'DISTORTEDFATE'),
(1092, 'Blacklolita', '102300', 60, 'オンゲキ', '3', '9', '13', 14, 0, '[HALO]', 'HALO'),
(1093, '矢鴇つかさ feat. kalon.', '100443', 26, 'チュウマイ', '2', '8', '11', 13, 0, 'SPILL OVER COLORS', 'SPILLOVERCOLORS'),
(1094, '原口沙輔 feat.重音テト', '102503', 10, 'niconico', '2', '8', '10', 13, 0, '人マニア', 'ヒトマニア'),
(1095, 'マサラダ', '102503', 15, 'niconico', '3', '7', '10', 13, 0, 'ライアーダンサー', 'ライアタンサ'),
(1096, '柊マグネタイト', '100420', 28, 'niconico', '2', '7', '10', 13, 0, 'リアライズ', 'リアライス'),
(1097, 'かめりあ', '100048', 71, 'チュウマイ', '6', '9', '13', 15, 0, 'Λzure Vixen', 'AZUREVIXEN'),
(1098, '曲:脇眞富/歌:オンゲキシューターズ', '102711', 7, 'オンゲキ', '3', '7', '10', 13, 0, 'パピプペ Popping Talk', 'ハヒフヘPOPPINGTALK'),
(1099, 'パソコン音楽クラブ', '100248', 33, 'チュウマイ', '3', '9', '13', 14, 0, 'Ignition', 'IGNITION'),
(1100, 'kamome sano', '100022', 39, 'チュウマイ', '5', '8', '12', 14, 0, 'crazy (about you)', 'CRAZYABOUTYOU'),
(1101, '天川はの', '100211', 5, 'POPSANIME', '2', '6', '10', 12, 0, 'Honeycomb', 'HONEYCOMB'),
(1102, 'DÉ DÉ MOUSE & WaMi', '100061', 9, 'VARIETY', '2', '7', '11', 13, 0, 'As You Feel', 'ASYOUFEEL'),
(1103, '歌:櫻川めぐ/曲:堀江晶太', '102571', 1, 'POPSANIME', '2', '5', '10', 13, 0, '一冊のアロー', 'イツサツノアロ'),
(1104, '歌:松下/曲:堀江晶太', '100427', 10, 'POPSANIME', '2', '6', '10', 13, 0, 'Hey Darling!', 'HEYDARLING'),
(1105, 'Mitsukiyo「ブルーアーカイブ -Blue Archive-」', '102598', 5, 'VARIETY', '3', '9', '12', 14, 0, 'Unwelcome School', 'UNWELCOMESCHOOL'),
(1106, 'SUGAR RUSH「ブルーアーカイブ -Blue Archive-」', '102598', 10, 'VARIETY', '3', '6', '11', 13, 0, '彩りキャンバス', 'イロトリキヤンハス'),
(1107, 'KARUT「ブルーアーカイブ -Blue Archive-」', '102603', 20, 'VARIETY', '3', '8', '11', 13, 0, 'Glitch Street', 'GLITCHSTREET'),
(1108, 'Nor「ブルーアーカイブ -Blue Archive-」', '102598', 15, 'VARIETY', '3', '7', '10', 13, 0, 'Usagi Flap', 'USAGIFLAP'),
(1109, 'Nor「ブルーアーカイブ -Blue Archive-」', '102603', 25, 'VARIETY', '3', '7', '10', 13, 0, 'RE Aoharu', 'REAOHARU'),
(1113, 'B小町 ルビーCV伊駒ゆりえ、有馬かなCV潘めぐみ、MEMちょCV大久保瑠美', '100250', 2, 'POPSANIME', '3', '7', '10', 13, 0, 'POP IN 2', 'POPIN2'),
(7077, '曲:穴山大輔, 水野健治/歌:藤沢 柚子(CV久保田 梨沙)', '100769', 40, 'オンゲキ', '1', '5', '8', 12, 0, '夏色花火 -藤沢 柚子ソロver.-', 'ナツイロハナヒフシサワユスソロVER'),
(7078, '曲:穴山大輔, 水野健治/歌:早乙女 彩華(CV中島 唯)', '100770', 40, 'オンゲキ', '1', '5', '8', 12, 0, '夏色花火 -早乙女 彩華ソロver.-', 'ナツイロハナヒサオトメアヤカソロVER'),
(7079, 'Powerless柏木 咲姫(CV石見 舞菜香)', '100755', 40, 'オンゲキ', '3', '6', '11', 13, 0, 'Iudicium “Apocalypsis Mix” -柏木 咲姫ソロver.-', 'IUDICIUMAPOCALYPSISMIXカシワキサキソロVER'),
(7080, 'Powerless柏木 美亜(CV和氣 あず未)', '100756', 40, 'オンゲキ', '3', '6', '11', 13, 0, 'Iudicium “Apocalypsis Mix” -柏木 美亜ソロver.-', 'IUDICIUMAPOCALYPSISMIXカシワキミアソロVER'),
(7152, '曲:アオワイファイ/歌:星咲 あかり(CV赤尾 ひかる)', '100001', 40, 'オンゲキ', '2', '5', '10', 12, 0, 'WakeUP MakeUP FEVER! -星咲 あかりソロver.-', 'WAKEUPMAKEUPFEVERホシサキアカリソロVER'),
(7153, '曲:アオワイファイ/歌:高瀬 梨緒(CV久保 ユリカ)', '100028', 40, 'オンゲキ', '2', '5', '10', 12, 0, 'WakeUP MakeUP FEVER! -高瀬 梨緒ソロver.-', 'WAKEUPMAKEUPFEVERタカセリオソロVER'),
(7154, '曲:アオワイファイ/歌:藍原 椿(CV橋本 ちなみ)', '100042', 40, 'オンゲキ', '2', '5', '10', 12, 0, 'WakeUP MakeUP FEVER! -藍原 椿ソロver.-', 'WAKEUPMAKEUPFEVERアイハラツハキソロVER'),
(7155, '曲:アオワイファイ/歌:桜井 春菜(CV近藤 玲奈)', '100049', 40, 'オンゲキ', '2', '5', '10', 12, 0, 'WakeUP MakeUP FEVER! -桜井 春菜ソロver.-', 'WAKEUPMAKEUPFEVERサクライハルナソロVER'),
(7156, '曲:アオワイファイ/歌:東雲 つむぎ(CV和泉 風花)', '100452', 40, 'オンゲキ', '2', '5', '10', 12, 0, 'WakeUP MakeUP FEVER! -東雲 つむぎソロver.-', 'WAKEUPMAKEUPFEVERシメツムキソロVER'),
(8060, 'LeaF', '101320', 56, 'チュウマイ', '0', '0', '0', 0, 14, 'macrocosmos', 'MACROCOSMOS'),
(8177, 'オルタンシア「Re:ステージ!プリズムステップ」', '100915', 50, 'POPSANIME', '0', '0', '0', 0, 14, '君とインフィニティ -2021-', 'キミトインフイニテイ2021'),
(8178, 'aran', '3', 40, 'オンゲキ', '0', '0', '0', 0, 0, 'Random Access Emotions', 'RANDOMACCESSEMOTIONS'),
(8185, 'Tanchiky', '100476', 41, 'VARIETY', '0', '0', '0', 0, 0, 'ENERGY SYNERGY MATRIX', 'ENERGYSYNERGYMATRIX'),
(8186, 'HiTECH NINJA', '100031', 10, 'オンゲキ', '0', '0', '0', 0, 14, 'Dolphika', 'DOLPHIKA'),
(8187, '曲:アオワイファイ/歌:オンゲキシューターズ', '100236', 50, 'オンゲキ', '0', '0', '0', 0, 14, 'WakeUP MakeUP FEVER!', 'WAKEUPMAKEUPFEVER'),
(8188, 'LOVE☆MAXガールズ「ゴシックは魔法乙女」', '100816', 55, 'POPSANIME', '0', '0', '0', 0, 14, 'わたしたち魔法乙女です☆', 'ワタシタチマホウオトメテス');
INSERT IGNORE INTO `ongeki_game_event` (`id`) VALUES
('1500430101'),
('1500431601'),
('1500432001'),
('1500450101'),
('1500450301'),
('1500450302'),
('1500451601'),
('1500451701'),
('1500451902'),
('1500520101'),
('1500520102'),
('1500520301'),
('1500520302'),
('1500521301'),
('1500540101'),
('1500540102'),
('1500540103'),
('1500540301'),
('1500540302'),
('1500540303'),
('1500540304'),
('1500540305'),
('1500540501'),
('1500540502'),
('1500540601'),
('1500541501'),
('1500541502'),
('1500541503'),
('1500541601'),
('1500541602'),
('1500541701'),
('1500610101'),
('1500610102'),
('1500610301'),
('1500610302'),
('1500610303'),
('1500610701'),
('1500611301'),
('1500630101'),
('1500630102'),
('1500630301'),
('1500630501'),
('1500630601'),
('1500630801'),
('1500631501'),
('1500631601'),
('1500631701'),
('1500631801'),
('1500710101'),
('1500710102'),
('1500710103'),
('1500710301'),
('1500710302'),
('1500710303'),
('1500710304'),
('1500710305'),
('1500710501'),
('1500710701'),
('1500711301'),
('1500720101'),
('1500720102'),
('1500720103'),
('1500720301'),
('1500720302'),
('1500720303'),
('1500720304'),
('1500720305'),
('1500720501'),
('1500720502'),
('1500721501'),
('1500721502'),
('1500721601'),
('1500721602'),
('1500721701'),
('1500721802'),
('1500730101'),
('1500730301'),
('1500730801'),
('1500760101'),
('1500760501'),
('1500760601'),
('1500761401'),
('1500761501'),
('1500761601'),
('1500761701'),
('1500761801'),
('1500810101'),
('1500810102'),
('1500810103'),
('1500810301'),
('1500810302'),
('1500810303'),
('1500810701'),
('1500811301'),
('1500811401'),
('1500811501'),
('1500811502'),
('1500811503'),
('1500811601'),
('1500811701'),
('1500811902'),
('1500821801'),
('1500830101'),
('1500830102'),
('1500830301'),
('1500830302'),
('1500830303'),
('1500830304'),
('1500830305'),
('1500830306'),
('1500830501'),
('1500830601'),
('1500831501'),
('1500831502'),
('1500831601'),
('1500831701'),
('1500910701'),
('1500931801'),
('1501051801'),
('1501051802');
INSERT IGNORE INTO `ongeki_game_card` (id, name, nick_name, attribute, chara_id, school, gakunen, rarity, level_param, skill_id, cho_kaika_skill_id, card_number, version) VALUES
(100048, '【SSR】藍原 椿[サディスティック・スマイル]', 'サディスティック・スマイル', 'Leaf', 1005, '奏坂学園', '高校1年生', 'SSR', '60,257,280,295,307,317,0,0,0,322', 100106, 100107, '[O.N.G.E.K.I.]1.00-0057', '1.00'),
(102036, '【SSR】三角 葵[夏宵スターマイン]', '夏宵スターマイン', 'Aqua', 1002, 'プロモーション', '高校2年生', 'SSR', '60,257,280,295,307,317,0,0,0,322', 105020, 105021, '[O.N.G.E.K.I.]Special Card', '1.45'),
(102037, '【SSR】藍原 椿[夏宵スターマイン]', '夏宵スターマイン', 'Leaf', 1005, 'プロモーション', '高校1年生', 'SSR', '60,257,280,295,307,317,0,0,0,322', 105020, 105021, '[O.N.G.E.K.I.]Special Card', '1.45'),
(102049, '【SSR】結城 莉玖[I got a gig tonight!]', 'I got a gig tonight!', 'Fire', 1004, '奏坂学園', '高校1年生', 'SSR', '66,277,297,309,316,327,0,0,0,332', 150013, 150014, '[O.N.G.E.K.I.]1.50-0027', '1.50'),
(102050, '【SSR】逢坂 茜[Rule the World!!]', 'Rule the World!!', 'Fire', 1011, '奏坂学園', '高校3年生', 'SSR', '66,277,297,309,316,327,0,0,0,332', 150015, 150016, '[O.N.G.E.K.I.]1.50-0028', '1.50'),
(102297, '【SSR】柏木 美亜[Primera Fes. Bridal Stage]', 'Primera Fes. Bridal Stage', 'Fire', 1013, '奏坂学園', '中学2年生', 'SSR', '70,300,313,323,330,335,0,0,0,340', 115134, 115135, '[O.N.G.E.K.I.]1.50-0046', '1.50'),
(102298, '【SSR】珠洲島 有栖[Primera Fes. Bridal Stage]', 'Primera Fes. Bridal Stage', 'Aqua', 1012, '奏坂学園', '高校1年生', 'SSR', '60,257,280,295,307,317,0,0,0,322', 120006, 120007, '[O.N.G.E.K.I.]1.50-0047', '1.50'),
(102299, '【SSR】三角 葵[Primera Fes. Bridal Stage]', 'Primera Fes. Bridal Stage', 'Aqua', 1002, '奏坂学園', '高校2年生', 'SSR', '60,257,280,295,307,317,0,0,0,322', 115122, 115123, '[O.N.G.E.K.I.]1.50-0048', '1.50'),
(102300, '【SSR】柏木 美亜[Primera Fes. Bridal Stage(集合Ver.)対戦相手専用]', 'Primera Fes. Bridal Stage(集合Ver.)対戦相手専用', 'Fire', 1013, '奏坂学園', '中学2年生', 'SSR', '60,257,280,295,307,317,0,0,0,322', 100000, 100041, '[O.N.G.E.K.I.]1.50-', '1.50'),
(102510, '【SR】重音テト[お ま え]', 'お ま え', 'Fire', 46194, '重音テト', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100033, 100074, '[O.N.G.E.K.I.]1.50-E-0024', '1.50'),
(102511, '【SR】重音テト[踊れ 踊れ 嘘に踊れ]', '踊れ 踊れ 嘘に踊れ', 'Fire', 46194, '重音テト', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 105042, 105043, '[O.N.G.E.K.I.]1.50-E-0025', '1.50'),
(102512, '【SR】重音テト[踊ったもん勝ち]', '踊ったもん勝ち', 'Fire', 46194, '重音テト', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 120088, 120089, '[O.N.G.E.K.I.]1.50-E-0026', '1.50'),
(102515, '【SSR】重音テト[人マニア]', '人マニア', 'Fire', 46194, '重音テト', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 100138, 100139, '[O.N.G.E.K.I.]1.50-E-0029', '1.50'),
(102516, '【SSR】重音テト[ライアーダンサー]', 'ライアーダンサー', 'Fire', 46194, '重音テト', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 100031, 100072, '[O.N.G.E.K.I.]1.50-E-0030', '1.50'),
(102571, '【R】和泉 妃愛[生徒会副会長]', '生徒会副会長', 'Fire', 46203, 'ハミダシクリエイティブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 100005, 100046, '[O.N.G.E.K.I.]1.50-E-0039', '1.50'),
(102572, '【R】常磐 華乃[生徒会広報]', '生徒会広報', 'Fire', 46204, 'ハミダシクリエイティブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 110092, 110093, '[O.N.G.E.K.I.]1.50-E-0040', '1.50'),
(102573, '【R】錦 あすみ[生徒会書記]', '生徒会書記', 'Fire', 46205, 'ハミダシクリエイティブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 110148, 110149, '[O.N.G.E.K.I.]1.50-E-0041', '1.50'),
(102574, '【R】鎌倉 詩桜[前生徒会長]', '前生徒会長', 'Fire', 46206, 'ハミダシクリエイティブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 130006, 130007, '[O.N.G.E.K.I.]1.50-E-0042', '1.50'),
(102575, '【R】竜閑 天梨[生徒会お手伝い]', '生徒会お手伝い', 'Fire', 46207, 'ハミダシクリエイティブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 110142, 110143, '[O.N.G.E.K.I.]1.50-E-0043', '1.50'),
(102576, '【SR】和泉 妃愛[ダメ兄を甘やかすことに喜びを感じる人気声優]', 'ダメ兄を甘やかすことに喜びを感じる人気声優', 'Fire', 46203, 'ハミダシクリエイティブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 125012, 125013, '[O.N.G.E.K.I.]1.50-E-0044', '1.50'),
(102577, '【SR】常磐 華乃[黒歴史を抱える売れっ子イラストレーター]', '黒歴史を抱える売れっ子イラストレーター', 'Fire', 46204, 'ハミダシクリエイティブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100025, 100066, '[O.N.G.E.K.I.]1.50-E-0045', '1.50'),
(102578, '【SR】錦 あすみ[電脳世界「から」受肉した天使]', '電脳世界「から」受肉した天使', 'Fire', 46205, 'ハミダシクリエイティブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100016, 100057, '[O.N.G.E.K.I.]1.50-E-0046', '1.50'),
(102579, '【SR】鎌倉 詩桜[前会長にして元凶の気まぐれ小説家]', '前会長にして元凶の気まぐれ小説家', 'Fire', 46206, 'ハミダシクリエイティブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100033, 100074, '[O.N.G.E.K.I.]1.50-E-0047', '1.50'),
(102580, '【SR】竜閑 天梨[今をときめく人気読モ]', '今をときめく人気読モ', 'Fire', 46207, 'ハミダシクリエイティブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 115036, 115037, '[O.N.G.E.K.I.]1.50-E-0048', '1.50'),
(102581, '【SSR】和泉 妃愛[妃愛&あすみ]', '妃愛&あすみ', 'Fire', 46203, 'ハミダシクリエイティブ', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 130062, 130063, '[O.N.G.E.K.I.]1.50-E-0049', '1.50'),
(102582, '【SSR】和泉 妃愛[ハミダシクリエイティブ]', 'ハミダシクリエイティブ', 'Fire', 46203, 'ハミダシクリエイティブ', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 125046, 125047, '[O.N.G.E.K.I.]1.50-E-0050', '1.50'),
(102586, '【R】長瀬有花[RIOT MUSIC]', 'RIOT MUSIC', 'Aqua', 46208, '長瀬有花', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 100005, 100046, '[O.N.G.E.K.I.]1.50-E-0051', '1.50'),
(102588, '【SR】長瀬有花[長瀬有花の日常①]', '長瀬有花の日常①', 'Aqua', 46208, '長瀬有花', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100025, 100066, '[O.N.G.E.K.I.]1.50-E-0052', '1.50'),
(102589, '【SR】長瀬有花[長瀬有花の日常②]', '長瀬有花の日常②', 'Aqua', 46208, '長瀬有花', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100033, 100074, '[O.N.G.E.K.I.]1.50-E-0053', '1.50'),
(102590, '【SR】長瀬有花[長瀬有花の日常③]', '長瀬有花の日常③', 'Aqua', 46208, '長瀬有花', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100018, 100059, '[O.N.G.E.K.I.]1.50-E-0054', '1.50'),
(102591, '【SR】長瀬有花[長瀬有花の日常④]', '長瀬有花の日常④', 'Aqua', 46208, '長瀬有花', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 110086, 110087, '[O.N.G.E.K.I.]1.50-E-0055', '1.50'),
(102592, '【SR】長瀬有花[長瀬有花の日常⑤]', '長瀬有花の日常⑤', 'Aqua', 46208, '長瀬有花', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 110094, 110095, '[O.N.G.E.K.I.]1.50-E-0056', '1.50'),
(102593, '【SR】長瀬有花[長瀬有花の日常⑥]', '長瀬有花の日常⑥', 'Aqua', 46208, '長瀬有花', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 125022, 125023, '[O.N.G.E.K.I.]1.50-E-0057', '1.50'),
(102595, '【SSR】長瀬有花[Eureka]', 'Eureka', 'Aqua', 46208, '長瀬有花', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 110070, 110071, '[O.N.G.E.K.I.]1.50-E-0058', '1.50'),
(102596, '【SSR】長瀬有花[だつりょく系アーティスト]', 'だつりょく系アーティスト', 'Aqua', 46208, '長瀬有花', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 105020, 105021, '[O.N.G.E.K.I.]1.50-E-0059', '1.50'),
(102598, '【R】陸八魔 アル[便利屋68]', '便利屋68', 'Fire', 46209, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 100005, 100046, '[O.N.G.E.K.I.]1.50-E-0067', '1.50'),
(102599, '【R】鬼方 カヨコ[便利屋68]', '便利屋68', 'Fire', 46210, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 110092, 110093, '[O.N.G.E.K.I.]1.50-E-0068', '1.50'),
(102600, '【R】浅黄 ムツキ[便利屋68]', '便利屋68', 'Fire', 46211, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 110142, 110143, '[O.N.G.E.K.I.]1.50-E-0069', '1.50'),
(102601, '【R】伊草 ハルカ[便利屋68]', '便利屋68', 'Fire', 46212, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 110108, 110109, '[O.N.G.E.K.I.]1.50-E-0070', '1.50'),
(102602, '【R】桐藤 ナギサ[ティーパーティー]', 'ティーパーティー', 'Aqua', 46213, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 125050, 125051, '[O.N.G.E.K.I.]1.50-E-0096', '1.50'),
(102603, '【R】聖園 ミカ[ティーパーティー]', 'ティーパーティー', 'Aqua', 46214, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 100005, 100046, '[O.N.G.E.K.I.]1.50-E-0097', '1.50'),
(102604, '【R】百合園 セイア[ティーパーティー]', 'ティーパーティー', 'Aqua', 46215, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 100004, 100045, '[O.N.G.E.K.I.]1.50-E-0098', '1.50'),
(102605, '【R】阿慈谷 ヒフミ[補習授業部]', '補習授業部', 'Aqua', 46216, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 100004, 100045, '[O.N.G.E.K.I.]1.50-E-0099', '1.50'),
(102606, '【R】白洲 アズサ[補習授業部]', '補習授業部', 'Aqua', 46217, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 110092, 110093, '[O.N.G.E.K.I.]1.50-E-0100', '1.50'),
(102607, '【R】下江 コハル[補習授業部]', '補習授業部', 'Aqua', 46218, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 100003, 100044, '[O.N.G.E.K.I.]1.50-E-0101', '1.50'),
(102608, '【R】浦和 ハナコ[補習授業部]', '補習授業部', 'Aqua', 46219, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 125006, 125007, '[O.N.G.E.K.I.]1.50-E-0102', '1.50'),
(102609, '【R】柚鳥 ナツ[放課後スイーツ部]', '放課後スイーツ部', 'Leaf', 46220, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 110084, 110085, '[O.N.G.E.K.I.]1.50-E-0071', '1.50'),
(102610, '【R】杏山 カズサ[放課後スイーツ部]', '放課後スイーツ部', 'Leaf', 46221, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 100005, 100046, '[O.N.G.E.K.I.]1.50-E-0072', '1.50'),
(102611, '【R】栗村 アイリ[放課後スイーツ部]', '放課後スイーツ部', 'Leaf', 46222, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 105004, 105005, '[O.N.G.E.K.I.]1.50-E-0073', '1.50'),
(102612, '【R】伊原木 ヨシミ[放課後スイーツ部]', '放課後スイーツ部', 'Leaf', 46223, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 110092, 110093, '[O.N.G.E.K.I.]1.50-E-0074', '1.50'),
(102613, '【R】美甘 ネル[Cleaning&Clearing]', 'Cleaning&Clearing', 'Fire', 46224, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 110092, 110093, '[O.N.G.E.K.I.]1.50-E-0075', '1.50'),
(102614, '【R】一之瀬 アスナ[Cleaning&Clearing]', 'Cleaning&Clearing', 'Fire', 46225, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 100004, 100045, '[O.N.G.E.K.I.]1.50-E-0076', '1.50'),
(102615, '【R】角楯 カリン[Cleaning&Clearing]', 'Cleaning&Clearing', 'Fire', 46226, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 100005, 100046, '[O.N.G.E.K.I.]1.50-E-0077', '1.50'),
(102616, '【R】室笠 アカネ[Cleaning&Clearing]', 'Cleaning&Clearing', 'Fire', 46227, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 105112, 105113, '[O.N.G.E.K.I.]1.50-E-0078', '1.50'),
(102617, '【R】飛鳥馬 トキ[Cleaning&Clearing]', 'Cleaning&Clearing', 'Fire', 46228, 'ブルーアーカイブ', '-', 'R', '50,197,212,227,242,257,0,0,0,257', 110092, 110093, '[O.N.G.E.K.I.]1.50-E-0079', '1.50'),
(102628, '【SR】陸八魔 アル[外部経営顧問としての入社提案]', '外部経営顧問としての入社提案', 'Fire', 46209, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 120018, 120019, '[O.N.G.E.K.I.]1.50-E-0080', '1.50'),
(102629, '【SR】鬼方 カヨコ[雨の降る日の路地裏]', '雨の降る日の路地裏', 'Fire', 46210, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100025, 100066, '[O.N.G.E.K.I.]1.50-E-0081', '1.50'),
(102630, '【SR】浅黄 ムツキ[誰かからの特別な依頼]', '誰かからの特別な依頼', 'Fire', 46211, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100016, 100057, '[O.N.G.E.K.I.]1.50-E-0082', '1.50'),
(102631, '【SR】伊草 ハルカ[間違えて]', '間違えて', 'Fire', 46212, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 115008, 115009, '[O.N.G.E.K.I.]1.50-E-0083', '1.50'),
(102632, '【SR】桐藤 ナギサ[紅茶とコク]', '紅茶とコク', 'Aqua', 46213, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 110066, 110067, '[O.N.G.E.K.I.]1.50-E-0103', '1.50'),
(102633, '【SR】聖園 ミカ[屋根裏部屋のお姫様]', '屋根裏部屋のお姫様', 'Aqua', 46214, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 105042, 105043, '[O.N.G.E.K.I.]1.50-E-0104', '1.50'),
(102634, '【SR】百合園 セイア[眠り姫からの誘い]', '眠り姫からの誘い', 'Aqua', 46215, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 110086, 110087, '[O.N.G.E.K.I.]1.50-E-0105', '1.50'),
(102635, '【SR】阿慈谷 ヒフミ[偶然のお出かけ]', '偶然のお出かけ', 'Aqua', 46216, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 105052, 105053, '[O.N.G.E.K.I.]1.50-E-0106', '1.50'),
(102636, '【SR】白洲 アズサ[計画]', '計画', 'Aqua', 46217, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100025, 100066, '[O.N.G.E.K.I.]1.50-E-0107', '1.50'),
(102637, '【SR】下江 コハル[何するつもり?]', '何するつもり?', 'Aqua', 46218, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 115052, 115053, '[O.N.G.E.K.I.]1.50-E-0108', '1.50'),
(102638, '【SR】浦和 ハナコ[少し変わった女の子]', '少し変わった女の子', 'Aqua', 46219, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100018, 100059, '[O.N.G.E.K.I.]1.50-E-0109', '1.50'),
(102639, '【SR】柚鳥 ナツ[3時間目「実習発掘」]', '3時間目「実習発掘」', 'Leaf', 46220, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100017, 100058, '[O.N.G.E.K.I.]1.50-E-0084', '1.50'),
(102640, '【SR】杏山 カズサ[バッドタイミング]', 'バッドタイミング', 'Leaf', 46221, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 110094, 110095, '[O.N.G.E.K.I.]1.50-E-0085', '1.50'),
(102641, '【SR】栗村 アイリ[ちょっとしたデート]', 'ちょっとしたデート', 'Leaf', 46222, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 105000, 105001, '[O.N.G.E.K.I.]1.50-E-0086', '1.50'),
(102642, '【SR】伊原木 ヨシミ[その輝きの、すぐそばに]', 'その輝きの、すぐそばに', 'Leaf', 46223, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 100033, 100074, '[O.N.G.E.K.I.]1.50-E-0087', '1.50'),
(102643, '【SR】美甘 ネル[輝く夜に]', '輝く夜に', 'Fire', 46224, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 120018, 120019, '[O.N.G.E.K.I.]1.50-E-0088', '1.50'),
(102644, '【SR】一之瀬 アスナ[壊れたアスナ]', '壊れたアスナ', 'Fire', 46225, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 110136, 110137, '[O.N.G.E.K.I.]1.50-E-0089', '1.50'),
(102645, '【SR】角楯 カリン[メイドカフェ!]', 'メイドカフェ!', 'Fire', 46226, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 120074, 120075, '[O.N.G.E.K.I.]1.50-E-0090', '1.50'),
(102646, '【SR】室笠 アカネ[輝く午後を迎えて]', '輝く午後を迎えて', 'Fire', 46227, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 130000, 130001, '[O.N.G.E.K.I.]1.50-E-0091', '1.50'),
(102647, '【SR】飛鳥馬 トキ[メイドの中のメイド]', 'メイドの中のメイド', 'Fire', 46228, 'ブルーアーカイブ', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 110094, 110095, '[O.N.G.E.K.I.]1.50-E-0092', '1.50'),
(102658, '【SSR】聖園 ミカ[聖園ミカ、ついに登場~☆ って感じかな?]', '聖園ミカ、ついに登場~☆ って感じかな?', 'Aqua', 46214, 'ブルーアーカイブ', '-', 'SSR', '67,287,307,319,326,337,0,0,0,342', 115134, 115135, '[O.N.G.E.K.I.]1.50-E-0110', '1.50'),
(102659, '【SSR】美甘 ネル[コールサイン・ダブルオー]', 'コールサイン・ダブルオー', 'Fire', 46224, 'ブルーアーカイブ', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 120006, 120007, '[O.N.G.E.K.I.]1.50-E-0093', '1.50'),
(102660, '【SSR】阿慈谷 ヒフミ[私たちの、青春の物語を!!]', '私たちの、青春の物語を!!', 'Aqua', 46216, 'ブルーアーカイブ', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 110098, 110099, '[O.N.G.E.K.I.]1.50-E-0111', '1.50'),
(102661, '【SSR】栗村 アイリ[-ive aLIVE!]', '-ive aLIVE!', 'Leaf', 46222, 'ブルーアーカイブ', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 110068, 110069, '[O.N.G.E.K.I.]1.50-E-0094', '1.50'),
(102662, '【SSR】白洲 アズサ[Et Omnia Vanitas]', 'Et Omnia Vanitas', 'Aqua', 46217, 'ブルーアーカイブ', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 100144, 100145, '[O.N.G.E.K.I.]1.50-E-0112', '1.50'),
(102663, '【SSR】陸八魔 アル[あっはは!これくらい簡単よ!]', 'あっはは!これくらい簡単よ!', 'Fire', 46209, 'ブルーアーカイブ', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 110070, 110071, '[O.N.G.E.K.I.]1.50-E-0095', '1.50'),
(102664, '【SSR】桐藤 ナギサ[いつでも余裕をもって、優雅に]', 'いつでも余裕をもって、優雅に', 'Aqua', 46213, 'ブルーアーカイブ', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 105020, 105021, '[O.N.G.E.K.I.]1.50-301', '1.50'),
(102665, '【SSR】飛鳥馬 トキ[命令を遂行します。]', '命令を遂行します。', 'Fire', 46228, 'ブルーアーカイブ', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 125046, 125047, '[O.N.G.E.K.I.]1.50-302', '1.50'),
(102688, '【SSR】星咲 あかり[ハーバリウム・フェアリーズ]', 'ハーバリウム・フェアリーズ', 'Fire', 1000, '奏坂学園', '高校2年生', 'SSR', '67,290,310,322,332,340,0,0,0,345', 150001, 150002, '[O.N.G.E.K.I.]1.50-0029', '1.50'),
(102689, '【SSR】藤沢 柚子[ハーバリウム・フェアリーズ]', 'ハーバリウム・フェアリーズ', 'Leaf', 1001, '奏坂学園', '高校2年生', 'SSR', '67,290,310,322,332,340,0,0,0,345', 150005, 150006, '[O.N.G.E.K.I.]1.50-0030', '1.50'),
(102690, '【SSR】三角 葵[ハーバリウム・フェアリーズ]', 'ハーバリウム・フェアリーズ', 'Aqua', 1002, '奏坂学園', '高校2年生', 'SSR', '67,290,310,322,332,340,0,0,0,345', 150003, 150004, '[O.N.G.E.K.I.]1.50-0031', '1.50'),
(102691, '【SSR】高瀬 梨緒[ハーバリウム・フェアリーズ]', 'ハーバリウム・フェアリーズ', 'Aqua', 1003, '奏坂学園', '高校2年生', 'SSR', '67,290,310,322,332,340,0,0,0,345', 150003, 150004, '[O.N.G.E.K.I.]1.50-0032', '1.50'),
(102692, '【SSR】結城 莉玖[ハーバリウム・フェアリーズ]', 'ハーバリウム・フェアリーズ', 'Fire', 1004, '奏坂学園', '高校1年生', 'SSR', '67,290,310,322,332,340,0,0,0,345', 150001, 150002, '[O.N.G.E.K.I.]1.50-0033', '1.50'),
(102693, '【SSR】藍原 椿[ハーバリウム・フェアリーズ]', 'ハーバリウム・フェアリーズ', 'Leaf', 1005, '奏坂学園', '高校1年生', 'SSR', '67,290,310,322,332,340,0,0,0,345', 150005, 150006, '[O.N.G.E.K.I.]1.50-0034', '1.50'),
(102696, '【SSR】九條 楓[ハーバリウム・フェアリーズ]', 'ハーバリウム・フェアリーズ', 'Leaf', 1008, '奏坂学園', '高校3年生', 'SSR', '67,290,310,322,332,340,0,0,0,345', 150005, 150006, '[O.N.G.E.K.I.]1.50-0037', '1.50'),
(102699, '【SSR】逢坂 茜[ハーバリウム・フェアリーズ]', 'ハーバリウム・フェアリーズ', 'Fire', 1011, '奏坂学園', '高校3年生', 'SSR', '67,290,310,322,332,340,0,0,0,345', 150001, 150002, '[O.N.G.E.K.I.]1.50-0040', '1.50'),
(102700, '【SSR】珠洲島 有栖[ハーバリウム・フェアリーズ]', 'ハーバリウム・フェアリーズ', 'Aqua', 1012, '奏坂学園', '高校1年生', 'SSR', '67,290,310,322,332,340,0,0,0,345', 150003, 150004, '[O.N.G.E.K.I.]1.50-0041', '1.50'),
(102706, '【SSR】西館 ハク[アイシング・ドリーム]', 'アイシング・ドリーム', 'Fire', 32001, 'Re:ステージ!プリズムステップ', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 130002, 130003, '[O.N.G.E.K.I.]1.50-0019', '1.50'),
(102707, '【SSR】結城 莉玖[O.N.G.E.K.I. 7th Anniversary]', 'O.N.G.E.K.I. 7th Anniversary', 'Fire', 1004, '奏坂学園', '高校1年生', 'SSR', '65,275,295,307,314,325,0,0,0,330', 100034, 100075, '[O.N.G.E.K.I.]1.50-0049', '1.50'),
(102708, '【SSR】桜井 春菜[O.N.G.E.K.I. 7th Anniversary]', 'O.N.G.E.K.I. 7th Anniversary', 'Fire', 1007, '奏坂学園', '高校2年生', 'SSR', '66,280,300,312,319,330,0,0,0,335', 135038, 135039, '[O.N.G.E.K.I.]1.50-0050', '1.50'),
(102709, '【SSR】井之原 小星[O.N.G.E.K.I. 7th Anniversary]', 'O.N.G.E.K.I. 7th Anniversary', 'Leaf', 1010, '奏坂学園', '高校1年生', 'SSR', '65,275,295,307,314,325,0,0,0,330', 135074, 135075, '[O.N.G.E.K.I.]1.50-0051', '1.50'),
(102710, '【SSR】逢坂 茜[O.N.G.E.K.I. 7th Anniversary]', 'O.N.G.E.K.I. 7th Anniversary', 'Fire', 1011, '奏坂学園', '高校3年生', 'SSR', '65,275,295,307,314,325,0,0,0,330', 105020, 105021, '[O.N.G.E.K.I.]1.50-0052', '1.50'),
(102711, '【SSR】柏木 美亜[O.N.G.E.K.I. 7th Anniversary]', 'O.N.G.E.K.I. 7th Anniversary', 'Fire', 1013, '奏坂学園', '中学2年生', 'SSR', '66,280,300,312,319,330,0,0,0,335', 115120, 115121, '[O.N.G.E.K.I.]1.50-0053', '1.50'),
(102721, '【SR】西郷・R・いろり[もちもちダウナー有能]', 'もちもちダウナー有能', 'Fire', 46019, '名取さな', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 120068, 120069, '[O.N.G.E.K.I.]1.50-E-0060', '1.50'),
(102722, '【SR】名取さな[名取探偵事務所所長]', '名取探偵事務所所長', 'Fire', 46019, '名取さな', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 120018, 120019, '[O.N.G.E.K.I.]1.50-E-0061', '1.50'),
(102723, '【SR】名取さな[みんなで踊ろうおしりぷり音頭]', 'みんなで踊ろうおしりぷり音頭', 'Fire', 46019, '名取さな', '-', 'SR', '50,222,237,252,267,282,0,0,0,282', 115068, 115069, '[O.N.G.E.K.I.]1.50-E-0062', '1.50'),
(102724, '【SSR】名取さな[名取探偵事務所開店!!]', '名取探偵事務所開店!!', 'Fire', 46019, '名取さな', '-', 'SSR', '65,270,291,304,314,322,0,0,0,327', 115132, 115133, '[O.N.G.E.K.I.]1.50-E-0063', '1.50'),
(102725, '【SSR】名取さな[さなちゃんねる夏祭り]', 'さなちゃんねる夏祭り', 'Fire', 46019, '名取さな', '-', 'SSR', '65,270,291,304,314,322,0,0,0,327', 110004, 110005, '[O.N.G.E.K.I.]1.50-E-0064', '1.50'),
(102726, '【SSR】名取さな[ハロー・マイ・バースデイ]', 'ハロー・マイ・バースデイ', 'Fire', 46019, '名取さな', '-', 'SSR', '65,270,291,304,314,322,0,0,0,327', 130086, 130087, '[O.N.G.E.K.I.]1.50-E-0065', '1.50'),
(102727, '【SSR】名取さな[王国からの招待状]', '王国からの招待状', 'Fire', 46019, '名取さな', '-', 'SSR', '65,270,291,304,314,322,0,0,0,327', 120006, 120007, '[O.N.G.E.K.I.]1.50-E-0066', '1.50'),
(102734, '【SSR】旭 日向[Earthly Light(集合Ver.)]', 'Earthly Light(集合Ver.)', 'Leaf', 32001, 'Re:ステージ!プリズムステップ', '-', 'SSR', '60,257,280,295,307,317,0,0,0,322', 100000, 100041, '[O.N.G.E.K.I.]1.50-371', '1.50'),
(102738, '【SR+】珠洲島 有栖[Nexture 05「SIRIUS」]', 'Nexture 05「SIRIUS」', 'Aqua', 1012, '奏坂学園', '高校1年生', 'SRPlus', '53,240,263,278,290,300,0,0,0,300', 120072, 120073, '[O.N.G.E.K.I.]Special Card', '1.50');

View File

@@ -0,0 +1,11 @@
CREATE TABLE sega_card_timestamp
(
id BIGINT AUTO_INCREMENT NOT NULL,
created_at datetime(3) NOT NULL,
updated_at datetime(3) NOT NULL,
game VARCHAR(255) NOT NULL,
card_id BIGINT NOT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_sega_card_timestamp_on_sega_card FOREIGN KEY (card_id) REFERENCES sega_card (id) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT unq_sega_card_timestamp_on_game_card UNIQUE (game, card_id)
);

View File

@@ -0,0 +1,30 @@
-- Add new unlock columns
ALTER TABLE aqua_game_options ADD COLUMN mai2_unlock_music BIT NOT NULL DEFAULT 0;
ALTER TABLE aqua_game_options ADD COLUMN mai2_unlock_chara BIT NOT NULL DEFAULT 0;
ALTER TABLE aqua_game_options ADD COLUMN mai2_unlock_chara_max_level BIT NOT NULL DEFAULT 0;
ALTER TABLE aqua_game_options ADD COLUMN mai2_unlock_partners BIT NOT NULL DEFAULT 0;
ALTER TABLE aqua_game_options ADD COLUMN mai2_unlock_collectables BIT NOT NULL DEFAULT 0;
ALTER TABLE aqua_game_options ADD COLUMN mai2_unlock_tickets BIT NOT NULL DEFAULT 0;
ALTER TABLE aqua_game_options ADD COLUMN wacca_unlock_music BIT NOT NULL DEFAULT 0;
ALTER TABLE aqua_game_options ADD COLUMN wacca_unlock_plates BIT NOT NULL DEFAULT 0;
ALTER TABLE aqua_game_options ADD COLUMN wacca_unlock_collectables BIT NOT NULL DEFAULT 0;
ALTER TABLE aqua_game_options ADD COLUMN wacca_unlock_tickets BIT NOT NULL DEFAULT 0;
-- Migrate data
UPDATE aqua_game_options SET
mai2_unlock_music = unlock_music,
mai2_unlock_chara = unlock_chara,
mai2_unlock_chara_max_level = unlock_chara,
mai2_unlock_partners = unlock_chara,
mai2_unlock_collectables = unlock_collectables,
mai2_unlock_tickets = unlock_tickets,
wacca_unlock_music = unlock_music,
wacca_unlock_plates = unlock_chara | unlock_collectables,
wacca_unlock_collectables = unlock_collectables,
wacca_unlock_tickets = unlock_tickets;
-- Drop old columns
ALTER TABLE aqua_game_options DROP COLUMN unlock_music;
ALTER TABLE aqua_game_options DROP COLUMN unlock_chara;
ALTER TABLE aqua_game_options DROP COLUMN unlock_collectables;
ALTER TABLE aqua_game_options DROP COLUMN unlock_tickets;

View File

@@ -0,0 +1,112 @@
INSERT INTO chusan_game_event (id, type, end_date, start_date, enable)
VALUES
(17090, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17091, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17092, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17093, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17094, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17095, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17096, 11, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17100, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17101, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17102, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17103, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17104, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17105, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17106, 17, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17107, 4, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17108, 5, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17109, 7, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17150, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17151, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17152, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17153, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17154, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17155, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17156, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17157, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17158, 14, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17159, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17160, 11, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17200, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17201, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17202, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17203, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17204, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17205, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17206, 10, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17207, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17208, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17209, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17210, 17, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17211, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17213, 4, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17214, 5, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17250, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17251, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17252, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17256, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17257, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17258, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17259, 11, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17260, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17261, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17262, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17263, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17300, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17301, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17302, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17303, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17304, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17305, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17306, 17, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17307, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17308, 14, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17309, 4, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17310, 5, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17350, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17351, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17352, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17353, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17354, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17355, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17356, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17357, 10, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17358, 11, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17380, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17382, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17383, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17384, 7, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17385, 7, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17386, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17400, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17401, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17402, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17403, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17404, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17405, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17406, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17407, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17408, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17409, 17, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17410, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17411, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17412, 14, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17413, 4, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17414, 5, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17450, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17451, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17452, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17453, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17454, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17480, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17481, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17482, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17483, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17484, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
(17485, 17, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true)
ON DUPLICATE KEY UPDATE
type = VALUES(type),
end_date = VALUES(end_date),
start_date = VALUES(start_date),
enable = VALUES(enable);