package net.taehui.twilight.site import EventOuterClass 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.qwilight.QwilightAvatar.Companion.random import net.taehui.twilight.system.AvatarHandler import net.taehui.twilight.system.AvatarIPSystem import net.taehui.twilight.system.Configure 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.nio.file.Path 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 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 = "" override val isEnterQuitAware = false override val avatarNameTitle = "🌐 " private val fragments = mutableListOf<ByteBuf>() 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.random(19, 0, 0, false, false, null, random)}", 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(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 filesPath = Path.of("files").resolve( "${RandomStringUtils.random(8, 0, 0, false, false, null, random)}.$eventText" ) Files.write(Configure.path.wwwPath.resolve(filesPath), data) send( EventOuterClass.Event.EventID.POST_FILE, "https://taehui.ddns.net/qwilight/${ filesPath.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) { LoggerFactory.getLogger(javaClass).error("[{}] {}", loggerID, Utility.getFaultText(e)) } companion object { val random = Random() } }