package net.taehui.twilight.qwilight import com.fasterxml.jackson.databind.ObjectMapper import com.google.protobuf.ByteString import io.netty.channel.ChannelFuture import io.netty.channel.ChannelHandlerContext import io.netty.channel.SimpleChannelInboundHandler import net.taehui.CommentClass import net.taehui.EventClass import net.taehui.twilight.* import net.taehui.twilight.BundleIO.BundleVariety import net.taehui.twilight.BundleIO.BundleVariety.Companion.getBundleVariety import net.taehui.twilight.awilight.Component import net.taehui.twilight.system.* import org.apache.commons.codec.binary.Hex import org.apache.commons.compress.compressors.xz.XZCompressorOutputStream import org.apache.commons.io.FilenameUtils import org.apache.commons.io.IOUtils import org.apache.commons.lang3.RandomStringUtils import org.slf4j.LoggerFactory import java.net.InetAddress import java.net.InetSocketAddress import java.nio.channels.AsynchronousFileChannel import java.nio.channels.CompletionHandler import java.nio.file.Files import java.nio.file.Path import java.nio.file.StandardOpenOption import java.security.MessageDigest import java.time.LocalDateTime import java.util.* import java.util.concurrent.ConcurrentHashMap import kotlin.io.path.nameWithoutExtension class QwilightAvatar : SimpleChannelInboundHandler<EventClass.Event>(), Avatar { override val availableCSX = Any() override val siteYellMillis = LinkedList<Long>() override var lastSignInDate: LocalDateTime = LocalDateTime.MIN override lateinit var handler: ChannelHandlerContext override var isAwilight = false override var isAvailable = true private val savingDefaultNoteIDs = mutableListOf<String>() private val savingDefaultUIIDs = mutableListOf<String>() private val defaultNoteSaveStore = LinkedList<String>() private val defaultUISaveStore = LinkedList<Path>() override lateinit var avatarIP: InetAddress override var avatarEstimatedID = "" private val savingAsBundleIOs = ConcurrentHashMap<String, BundleIO>() private val savingBundleIOs = ConcurrentHashMap<String, BundleIO>() override var isEstablished = false override var isSignedIn = false override var isValve = false override var qwilightHash = "" override var qwilightDate = "" override var language = "ko-KR" override var qwilightID = "" override var qwilightName = "" override var avatarID = "" override var avatarName = "" override var avatarCompetence = 1 override var situationValue = NOTE_FILE_MODE override var situationText = "" override val isEnterQuitAware = true override val avatarNameTitle = "" override fun send(eventID: EventClass.Event.EventID, text: Any?, vararg data: ByteString?): ChannelFuture { return handler.writeAndFlush(EventClass.Event.newBuilder().apply { this.eventID = eventID if (text != null) { this.text = if (text is String) text else ObjectMapper().writeValueAsString(text) } this.addAllData(data.toList()) }.build()) } override fun send(event: EventClass.Event) { handler.writeAndFlush(event) } private fun hasAvailableCount(): Boolean { return savingBundleIOs.size + savingAsBundleIOs.size < BUNDLE_AVAILABLE_COUNT } private fun doCallBundle(targetAvatarID: String, isWindowOpen: Boolean) { DB.getBundles( Utility.getDefaultAvatarID(targetAvatarID) ).thenAccept { bundles -> if (bundles != null) { send(EventClass.Event.EventID.CALL_BUNDLE, object { val targetAvatar = targetAvatarID val targetValue = bundles.second val bundleLength = 5000000000 @JvmField val isWindowOpen = isWindowOpen val data = bundles.first.map { object { val date = it[0] as Long val bundleName = it[1] as String val bundleLength = it[2] as Long val bundleCompetence = it[3] as Int val bundleVariety = it[4] as Int } } }) } } } private fun translateTitleItem(target: TitleSystem.TitleItem): String { return when (language) { "ko-KR" -> target.title1042 else -> target.title } } override fun toString(): String { return if (isEstablished) { when (situationValue) { NOTE_FILE_MODE -> "[$loggerID] Note File Mode ($situationText)" DEFAULT_COMPUTING -> "[$loggerID] Default Computing ($situationText)" COMMENT_COMPUTING -> "[$loggerID] Comment Computing ($situationText)" AUTO_COMPUTING -> "[$loggerID] Auto Computing ($situationText)" QUIT_MODE -> "[$loggerID] Quit Computing ($situationText)" NET_COMPUTING -> "[$loggerID] Net Computing ($situationText)" IO_COMPUTING -> "[$loggerID] IO Computing ($situationText)" else -> "[$loggerID] Idle" } } else "[$loggerID] Not Established" } override fun channelActive(ctx: ChannelHandlerContext) { handler = ctx avatarIP = (handler.channel().remoteAddress() as InetSocketAddress).address isAwilight = avatarIP.isLoopbackAddress avatarEstimatedID = History.getAvatarID(remote) } override fun channelInactive(ctx: ChannelHandlerContext) { AvatarHandler.quitAvatar(avatarID) } public override fun channelRead0(ctx: ChannelHandlerContext, msg: EventClass.Event) { doIfValid(msg) { doIfAvailable { val jm = ObjectMapper() val eventText = msg.text val eventData = msg.dataList fun saveDefaultNote(eventText: String) { val text = if (eventText.isEmpty()) null else jm.readValue(eventText, Array<String>::class.java) if (text != null) { if (defaultNoteSaveStore.isEmpty()) { Files.list(TwilightComponent.DEFAULT_NOTE_ENTRY).use { defaultNoteFilePaths -> defaultNoteSaveStore.addAll(defaultNoteFilePaths.filter { defaultNoteFilePath -> FilenameUtils.isExtension( defaultNoteFilePath.fileName.toString(), "zip" ) }.map { defaultNoteFilePath -> defaultNoteFilePath.nameWithoutExtension }.toList().subtract(text.toList().toSet())) } } else { send(EventClass.Event.EventID.ALREADY_LOADING_BUNDLE, null) return } } val saveBundleID = UUID.randomUUID().toString() savingDefaultNoteIDs.add(saveBundleID) defaultNoteSaveStore.poll().let { if (it != null) { val bundleFileName = "$it.zip" val bundleFilePath = Path.of("Default", "Note", bundleFileName) val fileChannel = AsynchronousFileChannel.open(bundleFilePath) savingBundleIOs[saveBundleID] = BundleIO( bundleFilePath, fileChannel, it, BundleVariety.DEFAULT_NOTE, "", bundleFileName, false ) send( EventClass.Event.EventID.SAVE_BUNDLE, object { val bundleID = saveBundleID val bundleVariety = BundleVariety.DEFAULT_NOTE.value val bundleLength = fileChannel.size() } ) } else { send( EventClass.Event.EventID.NOTIFY_INFO, translateLanguage("savedDefaultNote") ) } } } fun saveDefaultUI(eventText: String) { val defaultUISaveStore = defaultUISaveStore if (eventText.toBoolean()) { if (defaultUISaveStore.isEmpty()) { Files.list(TwilightComponent.DEFAULT_UI_ENTRY).use { defaultUIFilePaths -> defaultUISaveStore.addAll(defaultUIFilePaths.filter { defaultUIFilePath -> FilenameUtils.isExtension( defaultUIFilePath.fileName.toString(), "zip" ) }.toList()) } } else { send(EventClass.Event.EventID.ALREADY_LOADING_BUNDLE, null) return } } val saveBundleID = UUID.randomUUID().toString() savingDefaultUIIDs.add(saveBundleID) defaultUISaveStore.poll().let { if (it != null) { val bundleFileName = it.fileName.toString() val targetDefaultUI = FilenameUtils.removeExtension(bundleFileName) val saveDefaultUIChannel = AsynchronousFileChannel.open(it) savingBundleIOs[saveBundleID] = BundleIO( it, saveDefaultUIChannel, targetDefaultUI, BundleVariety.DEFAULT_UI, "", bundleFileName, false ) send( EventClass.Event.EventID.SAVE_BUNDLE, object { val bundleID = saveBundleID val bundleVariety = BundleVariety.DEFAULT_UI.value val bundleLength = saveDefaultUIChannel.size() } ) } else { send( EventClass.Event.EventID.NOTIFY_INFO, translateLanguage("savedDefaultUI") ) } } } if (!isAwilight) { logEvent(msg) } when (msg.eventID) { EventClass.Event.EventID.ESTABLISH -> wantNotEstablished { val text = jm.readValue(eventText, JSON.QwilightEstablish::class.java) language = text.language val isValve = eventData.size > 0 Configure.establish(this, text.hash, text.date, isValve) { val avatarID = "*" + RandomStringUtils.randomAlphanumeric(19) if (isValve) { ValveSystem.putDrawing(avatarID, eventData[0]) } AvatarHandler.establish(this, avatarID, text.qwilightName, isValve, it) } } EventClass.Event.EventID.SIGN_IN -> wantNotSignedIn { if (allowSignIn()) { trySignInDate() val text = jm.readValue(eventText, JSON.QwilightSignIn::class.java) Configure.signIn( this, text.avatarID, text.avatarCipher ).thenAccept { if (it != null) { val avatarCompetence = it[3] as Int if (avatarCompetence >= 1) { val avatarID = it[1] as String val avatarName = it[2] as String AvatarHandler.handleSignIn( this, it[0] as String, avatarID, avatarName, avatarCompetence ) History.putHistory(this, avatarID) DB.getNotifyUbuntu(avatarID) .forEach { ubuntuID -> AvatarHandler.getAvatar( ubuntuID )?.let { ubuntu -> if (DB.isCrossUbuntu(avatarID, ubuntuID)) { ubuntu.send( EventClass.Event.EventID.NOTIFY_INFO, String.format( ubuntu.translateLanguage("ubuntuNotify"), avatarName ) ) } } } } else { send( EventClass.Event.EventID.NOTIFY_INFO, translateLanguage("unavailableAvatar") ) } } } } } EventClass.Event.EventID.NOT_SIGN_IN -> wantSignedIn { AvatarHandler.handleNotSignIn( it ) } EventClass.Event.EventID.CALL_UBUNTU -> wantSignedIn { avatarID -> DB.getUbuntu(avatarID).thenAccept { ubuntu -> send( EventClass.Event.EventID.CALL_UBUNTU, ubuntu.stream().map { val ubuntuID = it[0] val situationValue: Int val situationText: String if (DB.isCrossUbuntu(avatarID, ubuntuID)) { val avatarUbuntu = AvatarHandler.getAvatar(ubuntuID) situationValue = avatarUbuntu?.situationValue ?: NOT_SIGNED_IN situationText = avatarUbuntu?.situationText ?: DB.getLastDate(ubuntuID).toString() } else { situationValue = NOT_UBUNTU situationText = "" } object { val ubuntuID = ubuntuID val ubuntuName = it[1] val situationValue = situationValue val situationText = situationText } }.toArray() ) } } EventClass.Event.EventID.ENTER_SITE -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightEnterSite::class.java) SiteHandler.enterSite(UUID.fromString(text.siteID), this, text.siteCipher) } EventClass.Event.EventID.SITE_YELL -> wantEstablished { SiteHandler.putSiteYell( this, jm.readValue(eventText, JSON.QwilightSiteYell::class.java) ) } EventClass.Event.EventID.GET_SITE_YELLS -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightGetSiteYells::class.java) SiteHandler.getSiteYells(UUID.fromString(text.siteID), this, text) } EventClass.Event.EventID.SAVE_AS_BUNDLE -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightSaveAsBundle::class.java) val bundleVariety = text.bundleVariety val bundleName = text.bundleName val etc = text.etc fun saveAsBundle(bundleEntryPath: Path, saveAsBundleName: String, saveAsBundleID: String) { if (hasAvailableCount()) { if (!Files.isDirectory(bundleEntryPath)) { Files.createDirectories(bundleEntryPath) } val bundleFilePath = bundleEntryPath.resolve("$saveAsBundleName.tmp") savingAsBundleIOs[saveAsBundleID] = BundleIO( bundleFilePath, AsynchronousFileChannel.open( bundleFilePath, StandardOpenOption.CREATE, StandardOpenOption.WRITE ), saveAsBundleName, getBundleVariety(bundleVariety), etc, FilenameUtils.getName(text.bundleEntryPath) + getBundleVariety( bundleVariety ).fileVariety, true ) send( EventClass.Event.EventID.SAVE_AS_BUNDLE, object { val bundleID = saveAsBundleID val bundleVariety = bundleVariety val bundleName = bundleName val bundleEntryPath = text.bundleEntryPath } ) } else { send(EventClass.Event.EventID.ALREADY_LOADING_BUNDLE, null) } } if (getBundleVariety(bundleVariety) == BundleVariety.NET) { if (SiteHandler.isNetSiteHand(this, UUID.fromString(etc))) { saveAsBundle(TwilightComponent.BUNDLE_ENTRY_PATH, etc, etc) } } else { val saveAsBundleName = bundleName.ifEmpty { UUID.randomUUID().toString() } DB.hasBundleBefore( avatarID, saveAsBundleName ).thenAccept { if (it) { send( EventClass.Event.EventID.WARNING, translateLanguage("alreadyBundle") ) } else { saveAsBundle( TwilightComponent.BUNDLE_ENTRY_PATH.resolve(avatarID), saveAsBundleName, UUID.randomUUID().toString() ) } } } } EventClass.Event.EventID.SAVING_AS_BUNDLE -> wantEstablished { val savingAsBundleIOs = savingAsBundleIOs savingAsBundleIOs[eventText]?.let { val savingAsBundleData = eventData[0].asReadOnlyByteBuffer() it.fileChannel.write( savingAsBundleData, it.filePosition.toLong(), it, object : CompletionHandler<Int, BundleIO> { override fun completed(result: Int, attachment: BundleIO) = Unit override fun failed(exc: Throwable, attachment: BundleIO) { savingAsBundleIOs.remove(eventText)?.use { logFault(exc) } } }) it.saveFilePosition(savingAsBundleData.capacity()) } } EventClass.Event.EventID.STOP_SAVING_AS_BUNDLE -> wantEstablished { savingAsBundleIOs.remove(eventText)?.close() } EventClass.Event.EventID.SAVED_AS_BUNDLE -> wantEstablished { avatarID -> savingAsBundleIOs.remove(eventText)?.let { bundleIO -> bundleIO.fileChannel.write( eventData[0].asReadOnlyByteBuffer(), bundleIO.filePosition.toLong(), bundleIO, object : CompletionHandler<Int, BundleIO> { override fun completed(result: Int, attachment: BundleIO) { attachment.use { val etc = it.etc val bundleName = it.bundleName val bundleVariety = it.bundleVariety if (bundleVariety == BundleVariety.NET) { it.setFileVariety() send(EventClass.Event.EventID.SAVED_AS_BUNDLE, eventText) val bundleFileName = it.bundleFileName SiteHandler.putSiteYell( this@QwilightAvatar, JSON.QwilightSiteYell().apply { siteID = etc siteYell = String.format( translateLanguage("savedAsNetBundle"), bundleFileName ) } ) SiteHandler.setNetBundle( UUID.fromString(etc), bundleFileName ) } else { val bundleLength = it.getBundleLength() if (DB.isBundleLengthAvailable( avatarID, bundleLength ) ) { it.setFileVariety() DB.saveBundle( avatarID, bundleName, bundleLength, it.bundleVariety, etc ) send( EventClass.Event.EventID.SAVED_AS_BUNDLE, eventText ) doCallBundle(avatarID, false) } else { send( EventClass.Event.EventID.WARNING, translateLanguage("hasNotLength") ) } } } } override fun failed(exc: Throwable, attachment: BundleIO) { attachment.use { logFault(exc) } } }) } } EventClass.Event.EventID.SAVE_BUNDLE -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightSaveBundle::class.java) val etc = text.etc if (hasAvailableCount()) { if (Utility.isEtcNetBundle(etc)) { val bundleFileName = etc + BundleVariety.NET.fileVariety val bundleFilePath = TwilightComponent.BUNDLE_ENTRY_PATH.resolve(bundleFileName) val fileChannel = AsynchronousFileChannel.open(bundleFilePath) savingBundleIOs[etc] = BundleIO( bundleFilePath, fileChannel, text.bundleName, BundleVariety.NET, etc, bundleFileName, false ) send( EventClass.Event.EventID.SAVE_BUNDLE, object { val bundleID = etc val bundleVariety = BundleVariety.NET.value val bundleLength = fileChannel.size() } ) } else { val targetAvatarID = Utility.getDefaultAvatarID(text.avatarID) DB.getBundle( targetAvatarID, text.bundleName ).thenAccept { bundle -> if (bundle != null) { val bundleCompetence = bundle[0] as Int val bundleVariety = bundle[1] as Int val bundleEtc = bundle[2] as String val bundleName = bundle[3] as String if (isSU || Utility.getDefaultAvatarID(avatarID) == targetAvatarID || bundleCompetence == BUNDLE_CALLABLE || bundleCompetence == BUNDLE_AVATAR && isSignedIn || bundleCompetence == BUNDLE_UBUNTU && DB.isCrossUbuntu( avatarID, targetAvatarID ) ) { val saveBundleID = UUID.randomUUID().toString() val bundleFileName = bundleName + getBundleVariety(bundleVariety).fileVariety val bundleFilePath = TwilightComponent.BUNDLE_ENTRY_PATH.resolve(targetAvatarID) .resolve(bundleFileName) val fileChannel = AsynchronousFileChannel.open(bundleFilePath) savingBundleIOs[saveBundleID] = BundleIO( bundleFilePath, fileChannel, bundleName, getBundleVariety(bundleVariety), bundleEtc, bundleFileName, false ) send( EventClass.Event.EventID.SAVE_BUNDLE, object { val bundleID = saveBundleID val bundleVariety = bundleVariety val bundleLength = fileChannel.size() } ) val toNotifySaveBundle = DB.getNotifySaveBundle(targetAvatarID) if (toNotifySaveBundle == NOTIFY_SAVE_BUNDLE_CALLABLE || toNotifySaveBundle == NOTIFY_SAVE_BUNDLE_AVATAR && isSignedIn || toNotifySaveBundle == NOTIFY_SAVE_BUNDLE_UBUNTU && DB.isCrossUbuntu( avatarID, targetAvatarID ) ) { AvatarHandler.getAvatar( targetAvatarID )?.let { if (it !== this) { it.send( EventClass.Event.EventID.NOTIFY_INFO, String.format( it.translateLanguage("bundleNotify"), avatarName, bundleName ) ) } } } } else { send( EventClass.Event.EventID.WARNING, translateLanguage("lowerCompetenceAsSave") ) } } } } } else { send(EventClass.Event.EventID.ALREADY_LOADING_BUNDLE, null) } } EventClass.Event.EventID.SAVE_DEFAULT_NOTE -> wantEstablished { saveDefaultNote(eventText) } EventClass.Event.EventID.SAVE_DEFAULT_UI -> wantEstablished { saveDefaultUI(eventText) } EventClass.Event.EventID.SAVING_BUNDLE -> wantEstablished { val savingBundleIOs = savingBundleIOs savingBundleIOs[eventText]?.let { bundleIO -> val savingBundleData = bundleIO.data bundleIO.fileChannel.read( savingBundleData, bundleIO.filePosition.toLong(), bundleIO, object : CompletionHandler<Int, BundleIO> { override fun completed(result: Int, attachment: BundleIO) { attachment.saveFilePosition(result) if (result == savingBundleData.capacity()) { savingBundleIOs[eventText]?.let { send( EventClass.Event.EventID.SAVING_BUNDLE, eventText, ByteString.copyFrom(savingBundleData.flip()) ).addListener { future -> if (future.isSuccess) { savingBundleData.flip() it.fileChannel.read( savingBundleData, it.filePosition.toLong(), attachment, this ) } else { failed(future.cause(), attachment) } } } } else { savingBundleIOs[eventText]?.use { send( EventClass.Event.EventID.SAVED_BUNDLE, object { val bundleID = eventText val bundleVariety = it.bundleVariety.value val bundleName = it.bundleName val etc = it.etc }, ByteString.copyFrom(savingBundleData.flip()) ).addListener { future -> if (future.isSuccess) { savingBundleIOs.remove(eventText)?.close() when (it.bundleVariety) { BundleIO.BundleVariety.DEFAULT_NOTE -> saveDefaultNote("") BundleIO.BundleVariety.DEFAULT_UI -> saveDefaultUI(false.toString()) else -> Unit } } else { failed(future.cause(), attachment) } } } } } override fun failed(exc: Throwable, attachment: BundleIO) { savingBundleIOs.remove(eventText)?.let { bundleIO -> bundleIO.use { logFault(exc) } } } }) } } EventClass.Event.EventID.STOP_SAVING_BUNDLE -> wantEstablished { savingBundleIOs.remove(eventText)?.use { if (savingDefaultNoteIDs.remove(eventText)) { defaultNoteSaveStore.clear() } if (savingDefaultUIIDs.remove(eventText)) { defaultUISaveStore.clear() } } } EventClass.Event.EventID.NEW_UBUNTU -> wantSignedIn { if (Utility.getDefaultAvatarID( it ) == eventText ) { send(EventClass.Event.EventID.WARNING, translateLanguage("ubuntuIsYou")) } else { DB.saveUbuntu(it, eventText).thenAccept { isOK -> if (!isOK) { send(EventClass.Event.EventID.WARNING, translateLanguage("alreadyUbuntu")) } } } } EventClass.Event.EventID.CALL_BUNDLE -> wantEstablished { doCallBundle( Utility.getDefaultAvatarID(eventText), true ) } EventClass.Event.EventID.CALL_CONFIGURE -> wantSignedIn { DB.getConfigure(it).thenAccept { twilightConfigure -> send( EventClass.Event.EventID.CALL_CONFIGURE, object { val silentSiteCompetence = twilightConfigure[0] val toNotifyUbuntuCompetence = twilightConfigure[1] val defaultBundleCompetence = twilightConfigure[2] val ioCompetence = twilightConfigure[3] val toNotifySaveBundle = twilightConfigure[4] } ) } } EventClass.Event.EventID.WIPE_BUNDLE -> wantSignedIn { DB.wipeBundle(it, eventText) } EventClass.Event.EventID.WIPE_UBUNTU -> wantSignedIn { DB.wipeUbuntu(it, eventText) } EventClass.Event.EventID.SET_BUNDLE -> wantSignedIn { DB.setBundle( jm.readValue(eventText, JSON.QwilightSetBundle::class.java), it ) } EventClass.Event.EventID.SET_CONFIGURE -> wantSignedIn { DB.setConfigure( jm.readValue(eventText, JSON.QwilightSetConfigure::class.java), it ) } EventClass.Event.EventID.NEW_SITE -> wantEstablished { doNewSite(jm.readValue(eventText, JSON.QwilightNewSite::class.java)) } EventClass.Event.EventID.NEW_SILENT_SITE -> wantEstablished { doNewSilentSite( eventText ) } EventClass.Event.EventID.COMMENT -> wantSignedIn { val text = jm.readValue(eventText, JSON.QwilightComment::class.java) val dataID = text.dataID val multiplier = text.multiplier val autoMode = text.autoMode val noteSaltMode = text.noteSaltMode val audioMultiplier = text.audioMultiplier val faintNoteMode = text.faintNoteMode val judgmentMode = text.judgmentMode val hitPointsMode = text.hitPointsMode val noteMobilityMode = text.noteMobilityMode val longNoteMode = text.longNoteMode val inputFavorMode = text.inputFavorMode val noteModifyMode = text.noteModifyMode val lowestJudgmentConditionMode = text.lowestJudgmentConditionMode val stand = text.stand val band = text.highestBand val point = text.point val salt = text.salt val isPaused = text.isPaused val inputFlags = text.inputFlags val commentFileContents = eventData[1] if (longNoteMode != INPUT_LONG_NOTE_MODE && judgmentMode != FAVOR_JUDGMENT_MODE && hitPointsMode != FAVOR_HIT_POINTS_MODE && hitPointsMode != TEST_HIT_POINTS_MODE && noteModifyMode != LONG_NOTE_NOTE_MODIFY_MODE) { val noteFileContents = eventData[0].toByteArray() val hashComputer512 = MessageDigest.getInstance("SHA-512") hashComputer512.update(noteFileContents) val hashComputer128 = MessageDigest.getInstance("MD5") hashComputer128.update(noteFileContents) val hashComputer256 = MessageDigest.getInstance("SHA-256") hashComputer256.update(noteFileContents) val noteID512 = Hex.encodeHexString(hashComputer512.digest()) val noteID128 = Hex.encodeHexString(hashComputer128.digest()) val noteID256 = Hex.encodeHexString(hashComputer256.digest()) val targetComputing = BaseCompiler.handleCompile(noteFileContents) if (!targetComputing.isBanned && !BannedNote.isBanned(noteID512)) { logFuture { Files.write(TwilightComponent.NOTE_ENTRY_PATH.resolve(noteID512), noteFileContents) DB.setNote( "$noteID512:$dataID", noteID128, noteID256, targetComputing ) hashComputer512.reset() val inputMode = text.inputMode val lastTitles = DB.getTitleItems(it) val lastAvatarAbility = when (inputMode) { Component.InputMode.INPUT_MODE_51 -> DB.getAvatarAbility5K(avatarID) Component.InputMode.INPUT_MODE_71 -> DB.getAvatarAbility7K(avatarID) Component.InputMode.INPUT_MODE_9 -> DB.getAvatarAbility9K(avatarID) else -> 0.0 } val lastAvatarLevel = DB.getAvatarLevels(it)[0] val comment = CommentClass.Comment.parseFrom(commentFileContents) val commentFileData = commentFileContents.toByteArray() val commentID = Hex.encodeHexString(hashComputer512.digest(commentFileData)) if (DB.saveComment( multiplier, autoMode, noteSaltMode, audioMultiplier, faintNoteMode, judgmentMode, hitPointsMode, noteMobilityMode, longNoteMode, inputFavorMode, noteModifyMode, lowestJudgmentConditionMode, stand, band, comment.lowestJudgment == 0, point, salt, commentID, it, "$noteID512:$dataID", isPaused, inputFlags ) ) { val commentFilePath = TwilightComponent.COMMENT_ENTRY_PATH.resolve(noteID512) Files.createDirectories(commentFilePath) commentFileData.inputStream().use { XZCompressorOutputStream(Files.newOutputStream(commentFilePath.resolve("$commentID.xz"))).use { os -> IOUtils.copy(it, os) } } } val titles = DB.getTitleItems(it) val avatarAbility = when (inputMode) { Component.InputMode.INPUT_MODE_51 -> DB.getAvatarAbility5K(avatarID) Component.InputMode.INPUT_MODE_71 -> DB.getAvatarAbility7K(avatarID) Component.InputMode.INPUT_MODE_9 -> DB.getAvatarAbility9K(avatarID) else -> 0.0 } val avatarLevel = DB.getAvatarLevels(it)[0] val distanceTitles = titles.filter { !lastTitles.contains(it) }.toList() if (distanceTitles.isNotEmpty()) { distanceTitles.forEach { send( EventClass.Event.EventID.NEW_TITLE, translateTitleItem(it) ) } } val distanceAvatarAbility = avatarAbility - lastAvatarAbility if (0.01 <= distanceAvatarAbility) { send( EventClass.Event.EventID.ABILITY_UP, String.format( translateLanguage("abilityUp"), distanceAvatarAbility ) ) } else if (0 < distanceAvatarAbility) { send( EventClass.Event.EventID.ABILITY_UP, translateLanguage("abilityUpMini") ) } if (lastAvatarLevel < avatarLevel) { send(EventClass.Event.EventID.LEVEL_UP, null) } val twilightCommentSiteYell = JSON.TwilightCommentSiteYell( it, avatarName, targetComputing.artist, targetComputing.title, targetComputing.genre, targetComputing.levelText, targetComputing.level, stand, hitPointsMode ) if (SiteHandler.hasAvatar( this, SiteHandler.commentSiteID ) ) { SiteHandler.putSiteYell( SiteHandler.commentSiteID, twilightCommentSiteYell ) } } } } } EventClass.Event.EventID.VALVE_COMMENT -> wantNotSignedIn { val text = jm.readValue(eventText, JSON.QwilightValveComment::class.java) val noteFileContents = eventData[0].toByteArray() val hashComputer512 = MessageDigest.getInstance("SHA-512") hashComputer512.update(noteFileContents) val hashComputer128 = MessageDigest.getInstance("MD5") hashComputer128.update(noteFileContents) val hashComputer256 = MessageDigest.getInstance("SHA-256") hashComputer256.update(noteFileContents) val noteID512 = Hex.encodeHexString(hashComputer512.digest()) val noteID128 = Hex.encodeHexString(hashComputer128.digest()) val noteID256 = Hex.encodeHexString(hashComputer256.digest()) val targetComputing = BaseCompiler.handleCompile(noteFileContents) if (!targetComputing.isBanned) { logFuture { Files.write(TwilightComponent.NOTE_ENTRY_PATH.resolve(noteID512), noteFileContents) DB.setNote( "$noteID512:${text.dataID}", noteID128, noteID256, targetComputing ) if (SiteHandler.hasAvatar( this, SiteHandler.commentSiteID ) ) { SiteHandler.putSiteYell( SiteHandler.commentSiteID, JSON.TwilightCommentSiteYell( avatarID, avatarName, targetComputing.artist, targetComputing.title, targetComputing.genre, targetComputing.levelText, targetComputing.level, text.stand, text.hitPointsMode ) ) } } } } EventClass.Event.EventID.QUIT_SITE -> wantEstablished { SiteHandler.quitAvatar( this, UUID.fromString(eventText) ) } EventClass.Event.EventID.SET_SITUATION -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetSituation::class.java) this.situationValue = text.situationValue this.situationText = text.situationText } EventClass.Event.EventID.EXILE_AVATAR -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightExileAvatar::class.java) AvatarHandler.getAvatar(text.avatarID)?.let { SiteHandler.exileAvatar( UUID.fromString( text.siteID ), this, it ) } } EventClass.Event.EventID.LEVY_NET -> wantEstablished { SiteHandler.setLevying( this, UUID.fromString(eventText) ) } EventClass.Event.EventID.COMPILED -> wantEstablished { SiteHandler.setCompiled( this, jm.readValue(eventText, JSON.QwilightCompiled::class.java) ) } EventClass.Event.EventID.CALL_NET -> wantEstablished { SiteHandler.setNetData( this, msg.qwilightCallNet, if (eventData.isEmpty()) null else CommentClass.Comment.parseFrom( eventData[0] ) ) } EventClass.Event.EventID.SET_MODE_COMPONENT -> wantEstablished { SiteHandler.setModeComponent( this, jm.readValue(eventText, JSON.QwilightSetModeComponent::class.java) ) } EventClass.Event.EventID.SET_FAVOR_NOTE_FILE -> wantEstablished { SiteHandler.setFavorNoteFile( this, UUID.fromString(eventText) ) } EventClass.Event.EventID.SET_FAVOR_MODE_COMPONENT -> wantEstablished { SiteHandler.setFavorModeComponent( this, UUID.fromString(eventText) ) } EventClass.Event.EventID.SET_FAVOR_AUDIO_MULTIPLIER -> wantEstablished { SiteHandler.setFavorAudioMultiplier( this, UUID.fromString(eventText) ) } EventClass.Event.EventID.SET_AVATAR_GROUP -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetAvatarGroup::class.java) SiteHandler.setAvatarGroup( this, UUID.fromString(text.siteID), text ) } EventClass.Event.EventID.SET_AUTO_SITE_HAND -> wantEstablished { SiteHandler.setAutoSiteHand( this, UUID.fromString(eventText) ) } EventClass.Event.EventID.SET_VALID_HUNTER_MODE -> wantEstablished { SiteHandler.setValidHunterMode( this, UUID.fromString(eventText) ) } EventClass.Event.EventID.SET_VALID_NET_MODE -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetValidNetMode::class.java) SiteHandler.setValidNetMode( this, UUID.fromString(text.siteID), text ) } EventClass.Event.EventID.SET_ALLOWED_POSTABLE_ITEMS -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetAllowedPostableItems::class.java) SiteHandler.setAllowedPostableItems( this, UUID.fromString(text.siteID), text ) } EventClass.Event.EventID.CALL_NET_SITE_COMMENTS -> wantEstablished { SiteHandler.doCallNetSiteComments( this, UUID.fromString(eventText) ) } EventClass.Event.EventID.SET_NOTE_FILE -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetNoteFile::class.java) SiteHandler.setNoteFileContents(this, UUID.fromString(text.siteID), text) } EventClass.Event.EventID.SET_SITE_HAND -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetSiteHand::class.java) AvatarHandler.getAvatar(text.avatarID)?.let { SiteHandler.setSiteHand( this, UUID.fromString(text.siteID), it ) } } EventClass.Event.EventID.SET_SITE_NAME -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetSiteName::class.java) setSiteName(text.siteID, text.siteName) } EventClass.Event.EventID.POST_FILE -> wantEstablished { logFuture { val tmpPath = Configure.path.tmpPath.resolve( RandomStringUtils.randomAlphanumeric(8) + "." + eventText ) Files.write(Configure.path.wwwPath.resolve(tmpPath), eventData[0].toByteArray()) send( EventClass.Event.EventID.POST_FILE, "${Configure.www.remote}/${ tmpPath.toString().replace(" ".toRegex(), "%20") }" ) } } EventClass.Event.EventID.WWW_LEVEL -> wantSignedIn { avatarID -> val text = jm.readValue(eventText, JSON.QwilightWwwLevel::class.java) DB.getClearedLevelIDs(avatarID).thenAccept { val levelItems = LevelSystem.getSatisfiedLevelItems(text) levelItems.filter { levelItem -> !it.contains( levelItem.levelID ) } .forEach { levelItem -> val lastTitles = DB.getTitleItems(avatarID) DB.setWwwLevel(avatarID, levelItem.levelID) val titles = DB.getTitleItems(avatarID) val distanceTitles = titles.filter { !lastTitles.contains(it) }.toList() if (distanceTitles.isNotEmpty()) { distanceTitles.forEach { send( EventClass.Event.EventID.NEW_TITLE, translateTitleItem(it) ) } } send(EventClass.Event.EventID.WWW_LEVEL, levelItem.title) } } } EventClass.Event.EventID.CALL_IO -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightCallIO::class.java) val targetAvatarID = Utility.getDefaultAvatarID(text.avatarID) if (Utility.getDefaultAvatarID(avatarID) == targetAvatarID) { send(EventClass.Event.EventID.WARNING, translateLanguage("ioAvatarIsYou")) } else { AvatarHandler.getAvatar(text.avatarID)?.let { ioAvatar -> DB.getIOCompetence( targetAvatarID ).thenAccept { if (it == IO_CALLABLE || it == IO_AVATAR && isSignedIn || it == IO_UBUNTU && DB.isCrossUbuntu( avatarID, targetAvatarID ) ) { ioAvatar.send( EventClass.Event.EventID.CALL_IO, object { val handlerID = text.handlerID val avatarID = avatarID val ioMillis = text.ioMillis } ) } else { send( EventClass.Event.EventID.WARNING, translateLanguage("lowerCompetenceAsIO") ) } } } } } EventClass.Event.EventID.IO_NOT -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightIONot::class.java) AvatarHandler.getAvatar(text.avatarID)?.send( EventClass.Event.EventID.IO_NOT, text.handlerID ) } EventClass.Event.EventID.CALL_IO_COMPONENT -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightCallIOComponent::class.java) AvatarHandler.getAvatar(text.avatarID)?.send( EventClass.Event.EventID.CALL_IO_COMPONENT, object { val avatarID = avatarID val avatarName = this@QwilightAvatar.avatarName val noteID = text.noteID val handlerID = text.handlerID @JvmField val isFailMode = text.isFailMode val data = text.data val ioHandlerID = text.ioHandlerID val ioMillis = text.ioMillis val targetIOMillis = text.targetIOMillis } ) } EventClass.Event.EventID.COMPILED_IO -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightCompiledIO::class.java) AvatarHandler.getAvatar(text.avatarID)?.send( EventClass.Event.EventID.COMPILED_IO, object { val handlerID = text.handlerID @JvmField val isCompiled = text.isCompiled val avatarID = avatarID val avatarName = this@QwilightAvatar.avatarName } ) } EventClass.Event.EventID.LEVY_IO -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightLevyIO::class.java) text.avatarIDs.forEach { AvatarHandler.getAvatar(it)?.send( EventClass.Event.EventID.LEVY_IO, object { val handlerID = text.handlerID val levyingWait = text.levyingWait val lastStand = text.lastStand @JvmField val isF = text.isF val multiplier = text.multiplier val audioMultiplier = text.audioMultiplier val ioMillis = text.ioMillis }, eventData[0] ) } } EventClass.Event.EventID.IO_INPUT -> wantEstablished { val qwilightIOInput = msg.qwilightIOInput qwilightIOInput.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventClass.Event.newBuilder().apply { eventID = EventClass.Event.EventID.IO_INPUT twilightIOInput = EventClass.Event.TwilightIOInput.newBuilder().apply { handlerID = qwilightIOInput.handlerID input = qwilightIOInput.input this.power = qwilightIOInput.power }.build() }.build() ) } } EventClass.Event.EventID.IO_JUDGE -> wantEstablished { val qwilightIOJudge = msg.qwilightIOJudge qwilightIOJudge.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventClass.Event.newBuilder().apply { eventID = EventClass.Event.EventID.IO_JUDGE twilightIOJudge = EventClass.Event.TwilightIOJudge.newBuilder().apply { handlerID = qwilightIOJudge.handlerID noteID = qwilightIOJudge.noteID this.judged = qwilightIOJudge.judged }.build() }.build() ) } } EventClass.Event.EventID.IO_NOTE_VISIBILITY -> wantEstablished { val qwilightIONoteVisibility = msg.qwilightIONoteVisibility qwilightIONoteVisibility.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventClass.Event.newBuilder().apply { eventID = EventClass.Event.EventID.IO_NOTE_VISIBILITY twilightIONoteVisibility = EventClass.Event.TwilightIONoteVisibility.newBuilder().apply { handlerID = qwilightIONoteVisibility.handlerID noteID = qwilightIONoteVisibility.noteID setValidJudgedNotes = qwilightIONoteVisibility.setValidJudgedNotes setNoteFailed = qwilightIONoteVisibility.setNoteFailed }.build() }.build() ) } } EventClass.Event.EventID.IO_JUDGMENT_METER -> wantEstablished { val qwilightIOJudgmentMeter = msg.qwilightIOJudgmentMeter qwilightIOJudgmentMeter.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventClass.Event.newBuilder().apply { eventID = EventClass.Event.EventID.IO_JUDGMENT_METER twilightIOJudgmentMeter = EventClass.Event.TwilightIOJudgmentMeter.newBuilder().apply { handlerID = qwilightIOJudgmentMeter.handlerID input = qwilightIOJudgmentMeter.input judgmentMeter = qwilightIOJudgmentMeter.judgmentMeter assist = qwilightIOJudgmentMeter.assist }.build() }.build() ) } } EventClass.Event.EventID.IO_MULTIPLIER -> wantEstablished { val qwilightIOMultiplier = msg.qwilightIOMultiplier qwilightIOMultiplier.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventClass.Event.newBuilder().apply { eventID = EventClass.Event.EventID.IO_MULTIPLIER twilightIOMultiplier = EventClass.Event.TwilightIOMultiplier.newBuilder().apply { handlerID = qwilightIOMultiplier.handlerID multiplier = qwilightIOMultiplier.multiplier }.build() }.build() ) } } EventClass.Event.EventID.IO_AUDIO_MULTIPLIER -> wantEstablished { val qwilightIOAudioMultiplier = msg.qwilightIOAudioMultiplier qwilightIOAudioMultiplier.avatarIDsList.forEach { AvatarHandler.getAvatar(it)?.send( EventClass.Event.newBuilder().apply { eventID = EventClass.Event.EventID.IO_AUDIO_MULTIPLIER twilightIOAudioMultiplier = EventClass.Event.TwilightIOAudioMultiplier.newBuilder().apply { handlerID = qwilightIOAudioMultiplier.handlerID audioMultiplier = qwilightIOAudioMultiplier.audioMultiplier }.build() }.build() ) } } EventClass.Event.EventID.IO_PAUSE -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightIOPause::class.java) text.avatarIDs.forEach { AvatarHandler.getAvatar(it)?.send( EventClass.Event.EventID.IO_PAUSE, object { val handlerID = text.handlerID @JvmField val isPaused = text.isPaused } ) } } EventClass.Event.EventID.IO_QUIT -> wantEstablished { avatarID -> val text = jm.readValue(eventText, JSON.QwilightIOQuit::class.java) text.avatarIDs.forEach { AvatarHandler.getAvatar(it)?.send( EventClass.Event.EventID.IO_QUIT, object { val avatarID = (if (text.isBanned) "" else avatarID) val handlerID = text.handlerID } ) } } EventClass.Event.EventID.AUDIO_INPUT -> wantEstablished { SiteHandler.audioInput( UUID.fromString(eventText), this, eventData[0] ) } EventClass.Event.EventID.QUIT_NET -> wantEstablished { SiteHandler.stopSiteNet( this, UUID.fromString(eventText) ) } EventClass.Event.EventID.COMMENTARY -> wantSignedIn { val text = jm.readValue(eventText, JSON.QwilightCommentary::class.java) DB.setCommentary(text.noteID, it, text.commentary).thenAccept { isOK -> if (!isOK) { send(EventClass.Event.EventID.WARNING, translateLanguage("hasNotComment")) } } } EventClass.Event.EventID.AVATAR_TITLE -> wantSignedIn { DB.setAvatarTitle(it, eventText).thenRun { AvatarHandler.sendInvalidateAvatarTitle( it ) } } EventClass.Event.EventID.AVATAR_EDGE -> wantSignedIn { DB.setAvatarEdge(it, eventText).thenRun { AvatarHandler.sendInvalidateAvatarEdge( it ) } } EventClass.Event.EventID.SET_LANGUAGE -> wantEstablished { language = eventText } EventClass.Event.EventID.SET_FAVOR -> wantSignedIn { DB.setFavor(jm.readValue(eventText, JSON.QwilightSetFavor::class.java), avatarID) } EventClass.Event.EventID.POST_ITEM -> wantEstablished { val qwilightPostItem = msg.qwilightPostItem SiteHandler.postItem( UUID.fromString(qwilightPostItem.siteID), this, qwilightPostItem ) } else -> Unit } } } } override val loggerID: String get() = if (isEstablished) { if (isSignedIn) { "{ID: $avatarID, Name: $avatarName, IP: $remote, Date: v$qwilightDate (${ qwilightHash.substring( 0, 8 ) })}" } else { if (isAwilight) { "{ID: $avatarID, Name: $avatarName}" } else if (avatarEstimatedID.isNotEmpty()) { "{ID: $avatarID ($avatarEstimatedID?), Name: $avatarName, IP: $remote, Date: v$qwilightDate (${ qwilightHash.substring( 0, 8 ) })}" } else { "{ID: $avatarID, Name: $avatarName, IP: $remote, Date: v$qwilightDate (${ qwilightHash.substring( 0, 8 ) })}" } } } else { if (qwilightDate.isEmpty() || qwilightHash.isEmpty()) { "{IP: $remote}}" } else { "{IP: $remote}, Date: v$qwilightDate (${qwilightHash.substring(0, 8)})}" } } override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) { logFault(cause) } override fun logInfo(toNotify: String) { LoggerFactory.getLogger(javaClass).info("[{}] {}", loggerID, toNotify) } override fun logFault(e: Throwable) { if (Utility.isValidFault(e)) { LoggerFactory.getLogger(javaClass).error("[{}] {}", loggerID, Utility.getFault(e)) } } companion object { const val NOT_UBUNTU = 0 const val NOT_SIGNED_IN = 1 const val NOTIFY_UBUNTU = 0 const val NOTIFY_SAVE_BUNDLE_CALLABLE = 0 const val NOTIFY_SAVE_BUNDLE_AVATAR = 1 const val NOTIFY_SAVE_BUNDLE_UBUNTU = 2 const val IO_CALLABLE = 0 const val IO_AVATAR = 3 const val IO_UBUNTU = 1 const val BUNDLE_CALLABLE = 0 const val BUNDLE_AVATAR = 3 const val BUNDLE_UBUNTU = 1 const val BUNDLE_VOID = 2 const val SILENT_SITE_CALLABLE = 0 const val SILENT_SITE_AVATAR = 3 const val SILENT_SITE_UBUNTU = 1 const val NOTE_FILE_MODE = 2 const val DEFAULT_COMPUTING = 3 const val COMMENT_COMPUTING = 4 const val AUTO_COMPUTING = 5 const val QUIT_MODE = 6 const val NET_COMPUTING = 7 const val IO_COMPUTING = 8 const val BUNDLE_AVAILABLE_COUNT = 1 const val INPUT_LONG_NOTE_MODE = 2 const val FAVOR_JUDGMENT_MODE = 5 const val FAVOR_HIT_POINTS_MODE = 6 const val TEST_HIT_POINTS_MODE = 7 const val LONG_NOTE_NOTE_MODIFY_MODE = 2 } }