17 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
40 changed files with 763 additions and 202 deletions

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

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

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

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

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,8 +35,10 @@ 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
@@ -80,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() }
@@ -264,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

@@ -101,8 +101,7 @@ class CardController(
val games = migrate.split(',')
cardGameService.migrate(card, games)
fedy.onCardLinked(card.luid, oldExtId = card.extId, ghostExtId = u.ghostCard.extId,
games.map { Fedy.getGameName(it) }.filterNotNull())
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()}")
@@ -207,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()
@@ -225,7 +225,9 @@ class CardGameService(
val remainingGames = dataRepos.keys.toMutableSet()
games.forEach { game ->
val dataRepo = dataRepos[game] ?: return@forEach
migrateCard(game, dataRepo, cardRepo, crd)
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

View File

@@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.RestController
import java.security.MessageDigest
import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.net.components.JWT
import icu.samnyan.aqua.net.db.AquaGameOptions
import icu.samnyan.aqua.net.db.AquaNetUser
import icu.samnyan.aqua.net.db.AquaUserServices
import icu.samnyan.aqua.net.games.mai2.Mai2Import
@@ -27,7 +28,9 @@ 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
@@ -41,11 +44,11 @@ class FedyProps {
var remote: String = ""
}
data class UserProfilePicture(val url: Str, val lastUpdatedMs: Long)
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 profilePicture: UserProfilePicture?, val gameOptions: Map<Str, Any?>?,
)
private data class UserUpdatedEvent(val user: UserBasicInfo, val isNewlyCreated: Bool)
@@ -66,21 +69,23 @@ private data class FedyEvent(
@API("/api/v2/fedy", consumes = ["multipart/form-data"])
class Fedy(
val jwt: JWT,
val us: AquaUserServices,
val emailProps: EmailProperties,
val cardRepo: CardRepository,
val cardService: CardService,
val mai2Import: Mai2Import,
val mai2UserDataRepo: Mai2UserDataRepo,
val mai2UploadUserPlaylog: Mai2UploadUserPlaylogHandler,
val mai2UpsertUserAll: Mai2UpsertUserAllHandler,
val chu3UserDataRepo: Chu3UserDataRepo,
val ongekiUserDataRepo: OgkUserDataRepo,
val waccaUserDataRepo: WcUserRepo,
val props: FedyProps,
val paths: PathProps,
val transactionManager: PlatformTransactionManager
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() {
@@ -127,7 +132,7 @@ class Fedy(
} caught { UserRegisterRes(error = it) }
}
data class UserUpdateReq(val auId: Long, val fields: Map<Str, Str?>?)
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) {
@@ -138,12 +143,16 @@ class Fedy(
if (k == "email") { ru.email = us.validateEmail(v) }
else us.update(ru, k, v)
}
pfpFile?.run {
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())
@@ -157,25 +166,31 @@ class Fedy(
?.let { paths.aquaNetPortrait.path() / it }?.takeIf { it.isRegularFile() }
?.let { UserProfilePicture(
url = "/uploads/net/portrait/${profilePicture}",
lastUpdatedMs = it.getLastModifiedTime().toMillis()
) }
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 exportOptions: ExportOptions)
data class DataPullRes(val error: FedyErr? = null, val result: Any? = null)
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 = when (req.game) {
"mai2" -> mai2Import.export(card, req.exportOptions)
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 DataPushReq(val extId: 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("/data/push")
fun handleDataPush(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: DataPushReq): Any = handleFedy(key) {
@@ -188,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) }
@@ -198,7 +214,7 @@ class Fedy(
}
else -> 406 - "Unsupported game"
} }
cardService.updateCardTimestamp(card, req.game, now = Instant.ofEpochMilli(req.updatedAtMs), resetCreatedAt = req.removeOldData)
SUCCESS
}
@@ -221,9 +237,9 @@ class Fedy(
var pairedCard = cardService.tryLookup(req.pairedLuid)?.maybeGhost()
if (pairedCard?.extId != card?.extId) {
var isGhost = pairedCard?.isGhost == true
var isFresh = pairedCard != null && isCardFresh(pairedCard)
if (isGhost && isFresh) isPairedLuidDiverged = true
else if (!isGhost && card?.isGhost == true) {
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) }
@@ -276,17 +292,16 @@ class Fedy(
log.info("Fedy /card/unlink : Unlinked card ${card.id} (${card.luid}) from user ${cu.auId} (${cu.username})")
}
fun onUserUpdated(u: AquaNetUser, isNew: Bool = false) = maybeNotifyAsync(FedyEvent(userUpdated = UserUpdatedEvent(u.fedyBasicInfo(), isNew)))
fun onCardCreated(luid: Str, extId: Long) = maybeNotifyAsync(FedyEvent(cardCreated = CardCreatedEvent(luid, extId)))
fun onCardLinked(luid: Str, oldExtId: Long?, ghostExtId: Long, migratedGames: List<Str>) = maybeNotifyAsync(FedyEvent(cardLinked = CardLinkedEvent(luid, oldExtId, ghostExtId, migratedGames)))
fun onCardUnlinked(luid: Str) = maybeNotifyAsync(FedyEvent(cardUnlinked = CardUnlinkedEvent(luid)))
fun onDataUpdated(extId: Long, game: Str, removeOldData: Bool) = maybeNotifyAsync({
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(event: FedyEvent) = maybeNotifyAsync({ event })
private fun maybeNotifyAsync(getEvent: () -> FedyEvent?) = if (!props.enabled && !suppressEvents.get()) {} else CompletableFuture.runAsync {
private fun maybeNotifyAsync(getEvent: () -> FedyEvent?) = if (!props.enabled || suppressEvents.get()) {} else CompletableFuture.runAsync {
var event: FedyEvent? = null
try {
event = getEvent()
@@ -326,7 +341,7 @@ class Fedy(
// Apparently existing cards could possibly be fresh and never used in any game. Treat them as new cards.
private fun isCardFresh(c: Card): Bool {
fun <T : IUserData> checkForGame(repo: GenericUserDataRepo<T>, card: Card): Bool = repo.findByCard(card) == null
fun <T : IUserData> checkForGame(repo: GenericUserDataRepo<T>, card: Card): Bool = repo.findByCard(card) != null
return when {
checkForGame(mai2UserDataRepo, c) -> false
checkForGame(chu3UserDataRepo, c) -> false
@@ -345,18 +360,12 @@ class Fedy(
const val KEY_HEADER = "X-Fedy-Key"
const val REQ_PART = "request"
const val PFP_PART = "profilePicture"
@Suppress("UNCHECKED_CAST")
val GAME_OPTIONS_FIELDS = listOf(
O::mai2UnlockMusic, O::mai2UnlockChara, O::mai2UnlockCharaMaxLevel, O::mai2UnlockPartners, O::mai2UnlockCollectables, O::mai2UnlockTickets
).map { it as Var<O, Any?> }.associateBy { it.name }
val log = logger()
fun getGameName(gameId: Str) = when (gameId) {
"mai2" -> "mai2"
"SDEZ" -> "mai2"
"chu3" -> "chu3"
"SDHD" -> "chu3"
"ongeki" -> "mu3"
"SDDT" -> "mu3"
"wacca" -> "wacca"
"SDFE" -> "wacca"
else -> null // Not supported
}
}
}
typealias O = AquaGameOptions

View File

@@ -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,22 +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.beans.factory.annotation.Autowired
import org.springframework.context.annotation.Lazy
import org.springframework.security.crypto.password.PasswordEncoder
import org.springframework.web.bind.annotation.RestController
import org.springframework.web.multipart.MultipartFile
import java.time.Instant
import java.time.LocalDateTime
import kotlin.io.path.writeBytes
@RestController
@@ -28,6 +21,7 @@ class UserRegistrar(
val hasher: PasswordEncoder,
val turnstileService: TurnstileService,
val emailService: EmailService,
val fedy: Fedy,
val geoIP: GeoIP,
val jwt: JWT,
val confirmationRepo: EmailConfirmationRepo,
@@ -37,7 +31,6 @@ class UserRegistrar(
val emailProps: EmailProperties,
final val paths: PathProps
) {
@Autowired @Lazy lateinit var fedy: Fedy
val portraitPath = paths.aquaNetPortrait.path()
companion object {
@@ -93,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

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

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

View File

@@ -7,6 +7,7 @@ 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
@@ -17,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
@@ -50,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
@@ -57,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, *>>,
@@ -71,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 {
@@ -148,7 +150,7 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
}
}
Fedy.getGameName(game)?.let { fedy.onDataUpdated(u.ghostCard.extId, it, true) }
cardService.updateCardTimestamp(u.ghostCard, gameName, resetCreatedAt = true)
SUCCESS
}

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

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

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

@@ -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,6 +30,7 @@ class ChusanController(
val cmMapper: BasicMapper,
val db: Chu3Repos,
val us: AquaUserServices,
val cardService: CardService,
val versionHelper: ChusanVersionHelper,
val props: ChusanProps,
val pop: GameMusicPopularity,

View File

@@ -236,9 +236,7 @@ fun ChusanController.chusanInit() {
) + userDict
if (user.card?.status == CardStatus.MIGRATED_TO_MINATO) {
res["userName"] = "JiaQQqun / ChangeDNS"
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

@@ -75,6 +75,8 @@ fun ChusanController.cmApiInit() {
)
}
u.card?.let { cardService.updateCardTimestamp(it, "chu3") }
mapOf(
"returnCode" to 1,
"apiName" to "CMUpsertUserGachaApi",
@@ -85,13 +87,15 @@ fun ChusanController.cmApiInit() {
"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")
}
@@ -114,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"}"""
}
@@ -36,6 +38,12 @@ fun ChusanController.upsertApiInit() {
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 {
@@ -185,8 +193,10 @@ fun ChusanController.upsertApiInit() {
}.also { db.userCMissionProgress.save(it) }
}
}
u.card?.let { cardService.updateCardTimestamp(it, "chu3") }
}
"""{"returnCode":1}"""
}
}
}

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

@@ -115,10 +115,7 @@ fun Maimai2ServletController.initApis() {
)
if (d.card?.status == CardStatus.MIGRATED_TO_MINATO) {
res["userName"] = "JiaQQqun / ChangeDNS"
res["dispRate"] = 1
res["playerRating"] = 66564
res["totalAwake"] = 7114
res["userName"] = "${res["userName"]}"
}
res
@@ -134,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
@@ -150,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
@@ -231,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}

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>()
@@ -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") { data["userId"]?.long?.let { fedy.onDataUpdated(it, "mai2", false) } }
}
}
} 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

@@ -197,6 +197,8 @@ fun OngekiController.cmApiInit() {
}
}
u.card?.let { cardService.updateCardTimestamp(it, "ongeki") }
null
}
@@ -250,6 +252,8 @@ fun OngekiController.cmApiInit() {
}
}
u.card?.let { cardService.updateCardTimestamp(it, "ongeki") }
null
}
@@ -313,6 +317,8 @@ fun OngekiController.cmApiInit() {
}
}
u.card?.let { cardService.updateCardTimestamp(it, "ongeki") }
null
}
@@ -342,4 +348,4 @@ fun OngekiController.cmApiInit() {
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

@@ -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 / ChangeDNS"
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

@@ -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 / ChangeDNS"
}
// if (u.card?.status == CardStatus.MIGRATED_TO_MINATO) {
// status[1] = "${status[1]}@AquaDX"
// }
u.run { ls(
"0 status" - status,

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);