package net.taehui.twilight.site import com.fasterxml.jackson.core.JsonProcessingException import com.fasterxml.jackson.databind.ObjectMapper import com.google.protobuf.ByteString import io.netty.buffer.ByteBuf import io.netty.buffer.Unpooled import io.netty.channel.ChannelFuture import io.netty.channel.ChannelHandlerContext import io.netty.channel.SimpleChannelInboundHandler import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame import io.netty.handler.codec.http.websocketx.WebSocketFrame import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler.HandshakeComplete import net.taehui.twilight.Avatar import net.taehui.twilight.JSON import net.taehui.twilight.Utility import net.taehui.twilight.qwilight.QwilightAvatar import net.taehui.twilight.system.AvatarHandler import net.taehui.twilight.system.Configure import net.taehui.twilight.system.AvatarIPSystem import net.taehui.twilight.system.SiteHandler import org.apache.commons.lang3.RandomStringUtils import org.slf4j.LoggerFactory import java.net.InetAddress import java.net.InetSocketAddress import java.net.UnknownHostException import java.nio.file.Files import java.time.LocalDateTime import java.util.* class SiteAvatar : SimpleChannelInboundHandler<WebSocketFrame>(), Avatar { override val availableCSX = Any() override val siteYellMillis = LinkedList<Long>() override var lastLogInDate: LocalDateTime = LocalDateTime.MIN override lateinit var handler: ChannelHandlerContext override var isAwilight = false override var isAvailable = true override lateinit var avatarIP: InetAddress override var isEstablished = false override var isLoggedIn = 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 avatarEstimatedID = "" override var avatarCompetence = 1 override var situationValue = QwilightAvatar.NOTE_FILE_MODE override var situationText = "" private val fragments = mutableListOf<ByteBuf>() override val isEnterQuitAware = false override val avatarNameTitle = "🌐 " override fun send(eventID: EventOuterClass.Event.EventID, text: Any?, vararg data: ByteString?): ChannelFuture { return handler.writeAndFlush( BinaryWebSocketFrame( Unpooled.wrappedBuffer( EventOuterClass.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().toByteArray() ) ) ) } override fun send(event: EventOuterClass.Event) { handler.writeAndFlush(event) } public override fun channelRead0(ctx: ChannelHandlerContext, msg: WebSocketFrame) { val fragmentContents = msg.content() fragmentContents.retain() fragments.add(fragmentContents) if (msg.isFinalFragment) { val event = EventOuterClass.Event.parseFrom(Unpooled.wrappedBuffer(*fragments.toTypedArray()).nioBuffer()) fragments.forEach { it.release() } fragments.clear() doIfValid(event) { doIfAvailable { logEvent(event) val jm = ObjectMapper() val eventText = event.text when (event.eventID) { EventOuterClass.Event.EventID.ESTABLISH -> wantNotEstablished { val text = jm.readValue(eventText, JSON.QwilightEstablish::class.java) language = text.language AvatarHandler.establish( this, "*${RandomStringUtils.randomAlphanumeric(19)}", remote, isValve = false, isPause = false ) } EventOuterClass.Event.EventID.LOG_IN -> wantNotLoggedIn { if (allowLogIn()) { tryLogInDate() try { val text = jm.readValue(eventText, JSON.QwilightLogIn::class.java) handleLogIn(this, text.avatarID, text.avatarCipher).thenAccept { if (it != null) { val avatarID = it[1] as String AvatarHandler.handleLogIn( this, (it[0] as String), "@$avatarID", (it[2] as String), it[3] as Int ) AvatarIPSystem.putAvatarIP(this, avatarID) } } } catch (e: JsonProcessingException) { handleLogIn(this, eventText).thenAccept { if (it != null) { val avatarID = it[0] as String AvatarHandler.handleLogIn( this, eventText, "@$avatarID", it[1] as String, it[2] as Int ) AvatarIPSystem.putAvatarIP(this, avatarID) } } } } } EventOuterClass.Event.EventID.NOT_LOG_IN -> wantLoggedIn { handleNotLogIn() } EventOuterClass.Event.EventID.SITE_YELL -> wantEstablished { SiteHandler.putSiteYell(this, jm.readValue(eventText, JSON.QwilightSiteYell::class.java), true) } EventOuterClass.Event.EventID.GET_SITE_YELLS -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightGetSiteYells::class.java) SiteHandler.getSiteYells(UUID.fromString(text.siteID), this, text) } EventOuterClass.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 ) } } EventOuterClass.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 ) } } EventOuterClass.Event.EventID.SET_SITE_NAME -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightSetSiteName::class.java) setSiteName(text.siteID, text.siteName) } EventOuterClass.Event.EventID.ENTER_SITE -> wantEstablished { val text = jm.readValue(eventText, JSON.QwilightEnterSite::class.java) SiteHandler.enterSite(SiteHandler.getSiteID(text.siteID), this, text.siteCipher) } EventOuterClass.Event.EventID.QUIT_SITE -> wantEstablished { SiteHandler.quitAvatar( this, UUID.fromString(eventText) ) } EventOuterClass.Event.EventID.NEW_SITE -> wantEstablished { putSite(jm.readValue(eventText, JSON.QwilightNewSite::class.java)) } EventOuterClass.Event.EventID.NEW_SILENT_SITE -> wantEstablished { putSilentSite( eventText ) } EventOuterClass.Event.EventID.POST_FILE -> wantEstablished { logFuture { val data = event.getData(0).toByteArray() val tmpPath = Configure.path.tmpPath.resolve( "${RandomStringUtils.randomAlphanumeric(8)}.$eventText" ) Files.write(Configure.path.wwwPath.resolve(tmpPath), data) send( EventOuterClass.Event.EventID.POST_FILE, "${Configure.www.remote}/${ tmpPath.toString().replace(" ".toRegex(), "%20") }" ) } } else -> Unit } } } } } override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) { if (evt is HandshakeComplete) { handler = ctx avatarIP = try { InetAddress.getByName(evt.requestHeaders()["X-Real-IP"]) } catch (e: UnknownHostException) { (handler.channel().remoteAddress() as InetSocketAddress).address } } } override fun toString(): String { return "[$loggerID] Idle" } override fun channelInactive(ctx: ChannelHandlerContext) { AvatarHandler.quitAvatar(avatarID) } override val loggerID: String get() = if (isEstablished) { if (isLoggedIn) { "{ID: $avatarID, Name: $avatarName, IP: $remote}" } else { if (avatarEstimatedID.isNotEmpty()) { "{ID: $avatarID ($avatarEstimatedID?), Name: $avatarName, IP: $remote}" } else { "{ID: $avatarID, Name: $avatarName, IP: $remote}" } } } else { "{IP: $remote}}" } 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.getFaultText(e)) } } }