Newer
Older
Twilight / src / main / kotlin / net / taehui / twilight / site / SiteAvatar.kt
@taehui taehui 13 days ago 11 KB v1.0-SNAPSHOT
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()
    }
}