package net.taehui.twilight import com.fasterxml.jackson.databind.ObjectMapper import com.google.protobuf.ByteString import io.netty.channel.ChannelFuture import io.netty.channel.ChannelHandlerContext import net.taehui.twilight.qwilight.QwilightAvatar import net.taehui.twilight.system.* import org.apache.hc.client5.http.HttpResponseException import org.apache.hc.client5.http.classic.methods.HttpPatch import org.apache.hc.client5.http.classic.methods.HttpPost import org.apache.hc.client5.http.impl.classic.BasicHttpClientResponseHandler import org.apache.hc.client5.http.impl.classic.HttpClients import org.apache.hc.core5.http.ContentType import org.apache.hc.core5.http.io.entity.StringEntity import java.net.InetAddress import java.time.LocalDateTime import java.util.* import java.util.concurrent.CompletableFuture interface Avatar : AutoCloseable, Logger { val availableCSX: Any val siteYellMillis: LinkedList<Long> var lastLogInDate: LocalDateTime var handler: ChannelHandlerContext var isAwilight: Boolean var isAvailable: Boolean val isEnterQuitAware: Boolean var isLoggedIn: Boolean var isEstablished: Boolean var avatarID: String var avatarName: String var avatarEstimatedID: String var avatarIP: InetAddress var language: String var situationValue: Int var situationText: String var isValve: Boolean var avatarCompetence: Int var qwilightHash: String var qwilightDate: String var qwilightID: String var qwilightName: String val avatarNameTitle: String val loggerID: String fun handleLogIn(avatar: Avatar, avatarID: String, avatarCipher: String): CompletableFuture<Array<Any>?> { return logValueFuture { HttpClients.createDefault().use { val jm = ObjectMapper() val dataPost = HttpPost(Configure.www.taehui + "/avatar/getTotem") dataPost.setHeader("millis", System.currentTimeMillis()) dataPost.setHeader("X-Real-IP", avatar.remote) dataPost.entity = StringEntity( jm.writeValueAsString( object { val avatarID = avatarID val avatarCipher = avatarCipher }), ContentType.APPLICATION_JSON ) try { val taehuiGetTotem = jm.readValue( it.execute(dataPost, BasicHttpClientResponseHandler()), JSON.TaehuiGetTotem::class.java ) arrayOf( taehuiGetTotem.totem, taehuiGetTotem.avatarID, taehuiGetTotem.avatarName, taehuiGetTotem.level ) } catch (e: HttpResponseException) { avatar.send(EventOuterClass.Event.EventID.WARNING, avatar.translateLanguage("wrongAvatar")) null } } } } fun handleLogIn(avatar: Avatar, totem: String): CompletableFuture<Array<Any>?> { return logValueFuture { HttpClients.createDefault().use { val data = HttpPatch(Configure.www.taehui + "/avatar/totem") data.setHeader("X-Real-IP", avatar.remote) data.setHeader("millis", System.currentTimeMillis()) data.setHeader("totem", totem) try { val taehuiTotem = ObjectMapper().readValue( it.execute(data, BasicHttpClientResponseHandler()), JSON.TaehuiTotem::class.java ) arrayOf(taehuiTotem.avatarID, taehuiTotem.avatarName, taehuiTotem.level) } catch (e: HttpResponseException) { avatar.send(EventOuterClass.Event.EventID.FAILED_VALIDATE_TOTEM, null) null } } } } fun doIfValid(event: EventOuterClass.Event, onValid: () -> Unit) { val millis = System.currentTimeMillis() if (millis < 10 * 60 * 1000 + event.millis) { if (event.eventID == EventOuterClass.Event.EventID.ESTABLISH || avatarID == event.avatarID) { onValid() } else { logInfo("$avatarID != ${event.avatarID}") } } else { logInfo("$millis << ${event.millis}") } } fun doIfAvailable(onAvailable: () -> Unit) { synchronized(availableCSX) { if (isAvailable) { onAvailable() } } } fun wantLoggedIn(toHandle: (String) -> Unit) { wantEstablished { if (isLoggedIn) { toHandle(it) } } } fun wantNotLoggedIn(toHandle: () -> Unit) { wantEstablished { if (!isLoggedIn) { toHandle() } } } fun wantEstablished(toHandle: (String) -> Unit) { if (isEstablished) { toHandle(avatarID) } } fun wantNotEstablished(toHandle: () -> Unit) { if (!isEstablished) { toHandle() } } val remote: String get() = avatarIP.hostAddress fun translateLanguage(target: String): String { return LanguageSystem.getLanguage(language, target) } val isSU: Boolean get() = avatarCompetence == 2 fun isMillisSuitable(targetMillis: Long): Boolean { if (siteYellMillis.size >= 5) { val millis = siteYellMillis.poll() siteYellMillis.offer(targetMillis) return targetMillis - millis >= 5000 } siteYellMillis.offer(targetMillis) return true } fun allowLogIn(): Boolean { return LocalDateTime.now().minusSeconds(1).isAfter(lastLogInDate) } fun tryLogInDate() { lastLogInDate = LocalDateTime.now() } fun handleNotLogIn() { if (isLoggedIn) { SiteHandler.quitAvatar(this) isLoggedIn = false avatarID = qwilightID avatarName = avatarNameTitle + qwilightName avatarCompetence = 1 setLastDate() send(EventOuterClass.Event.EventID.NOT_LOG_IN, object { val avatarID = this@Avatar.avatarID val avatarName = this@Avatar.avatarName }) } } fun setLastDate() { if (isLoggedIn && !isAwilight) { DB.setLastDate(Utility.getDefaultAvatarID(avatarID)) } } fun putSite(siteData: JSON.QwilightNewSite) { if (siteData.siteName.isNotEmpty() && !siteData.siteName.startsWith('@')) { val siteID = UUID.randomUUID() if (siteData.isNetSite) { SiteHandler.putNetSite(this, siteID, siteData) } else { SiteHandler.putSite(this, siteID, siteData) } } else { send(EventOuterClass.Event.EventID.WARNING, translateLanguage("siteNameNotValid")) } } fun putSilentSite(targetAvatarID: String) { if (Utility.getDefaultAvatarID(avatarID) == Utility.getDefaultAvatarID( targetAvatarID ) ) { send(EventOuterClass.Event.EventID.WARNING, translateLanguage("silentSiteAvatarIsYou")) } else { AvatarHandler.getAvatar(targetAvatarID)?.let { avatar -> DB.getSilentSiteCompetence( Utility.getDefaultAvatarID( targetAvatarID ) ).thenAccept { if (isLoggedIn) { if (it == QwilightAvatar.SILENT_SITE_CALLABLE || it == QwilightAvatar.SILENT_SITE_AVATAR && isLoggedIn || it == QwilightAvatar.SILENT_SITE_UBUNTU && DB.isCrossUbuntu( avatarID, targetAvatarID ) ) { SiteHandler.putSilentSite(this, avatar) } else { send(EventOuterClass.Event.EventID.WARNING, translateLanguage("lowerCompetenceAsSilentSite")) } } else { send(EventOuterClass.Event.EventID.WARNING, translateLanguage("silentSiteAvatarIsNotLoggedIn")) } } } } } fun setSiteName(siteID: String, siteName: String) { if (siteName.isNotEmpty() && !siteName.startsWith('@')) { SiteHandler.setSiteName(this, UUID.fromString(siteID), siteName) } else { send(EventOuterClass.Event.EventID.WARNING, translateLanguage("siteNameNotValid")) } } fun handleLogIn(totem: String, avatarID: String, avatarName: String, avatarCompetence: Int) { if (!isLoggedIn) { SiteHandler.quitAvatar(this) isLoggedIn = true this.avatarID = avatarID this.avatarName = avatarNameTitle + avatarName this.avatarCompetence = avatarCompetence setLastDate() send(EventOuterClass.Event.EventID.LOG_IN, object { val totem = totem val avatarID = avatarID val avatarName = avatarName }) } } fun setEstablished(avatarID: String, qwilightName: String, isValve: Boolean) { isEstablished = true qwilightID = avatarID this.qwilightName = qwilightName this.isValve = isValve this.avatarID = qwilightID avatarName = avatarNameTitle + this.qwilightName send(EventOuterClass.Event.EventID.ESTABLISH, object { val avatarID = avatarID val avatarName = this@Avatar.avatarName }) } fun send(eventID: EventOuterClass.Event.EventID, text: Any?, vararg data: ByteString?): ChannelFuture fun send(event: EventOuterClass.Event) override fun close() { handler.close() } }