Newer
Older
Twilight / src / main / kotlin / net / taehui / twilight / system / TVSystem.kt
@taehui taehui 13 days ago 5 KB v1.0-SNAPSHOT
package net.taehui.twilight.system

import com.fasterxml.jackson.core.type.TypeReference
import com.fasterxml.jackson.databind.ObjectMapper
import net.taehui.twilight.Logger
import org.openqa.selenium.By
import org.openqa.selenium.TimeoutException
import org.openqa.selenium.WebDriver
import org.openqa.selenium.edge.EdgeDriver
import org.openqa.selenium.edge.EdgeOptions
import org.openqa.selenium.support.ui.ExpectedConditions
import org.openqa.selenium.support.ui.WebDriverWait
import java.nio.file.Files
import java.nio.file.Paths
import java.time.Duration
import java.util.concurrent.*

object TVSystem : Logger {
    class TVItem(val href: String, val title: String, val text: String) {
        override fun hashCode(): Int {
            return href.hashCode()
        }

        override fun equals(other: Any?): Boolean {
            return href == (other as TVItem).href
        }

        override fun toString(): String {
            return text
        }
    }

    private val ses: ScheduledExecutorService = Executors.newSingleThreadScheduledExecutor {
        Executors.defaultThreadFactory().newThread(it).apply {
            isDaemon = true
        }
    }
    private var tv: WebDriver? = null
    private var future: ScheduledFuture<*>? = null
    private var bannedTVStore = CopyOnWriteArraySet<String>()

    fun handleSystem() {
        if (tv != null) {
            logInfo("TV is already running")
        } else if (Configure.mode.tv) {
            try {
                tv = EdgeDriver(EdgeOptions().apply {
                    addArguments("--headless", "--no-sandbox")
                })
                val pendingElements = mutableMapOf<TVItem, Int>()
                var lastElements = emptySet<TVItem>()

                future = ses.scheduleWithFixedDelay({
                    tv?.let { it ->
                        it.get("https://www.twitch.tv/directory/game/Qwilight")
                        val test0 = By.cssSelector("[data-test-selector=TitleAndChannel]")
                        val elements0 = try {
                            WebDriverWait(
                                it,
                                Duration.ofSeconds(1)
                            ).until(ExpectedConditions.visibilityOfAllElementsLocatedBy(test0))
                            it.findElements(test0) ?: throw TimeoutException()
                        } catch (_: TimeoutException) {
                            emptyList()
                        }.map { element ->
                            TVItem(
                                element.getAttribute("href") ?: "",
                                element.findElement(By.tagName("h3")).text,
                                element.findElement(By.tagName("p")).text
                            )
                        }.toSet()

                        it.get("https://chzzk.naver.com/category/GAME/Qwilight")
                        val test1 =
                            By.cssSelector("#layout-body > div > section > div > ul > li > div > div > div:nth-child(2)")
                        val elements1 = try {
                            WebDriverWait(
                                it,
                                Duration.ofSeconds(1)
                            ).until(ExpectedConditions.visibilityOfAllElementsLocatedBy(test1))
                            it.findElements(test1)
                        } catch (_: TimeoutException) {
                            emptyList()
                        }.map {
                            val titleElement = it.findElement(By.cssSelector("a"))
                            TVItem(
                                titleElement.getAttribute("href") ?: "",
                                titleElement.text.split("\n")[0],
                                it.findElement(By.cssSelector("div > a > span > span")).text
                            )
                        }.toSet()

                        val elements = elements0.plus(elements1).filter {
                            !bannedTVStore.contains(it.text)
                        }.toSet()
                        elements.subtract(lastElements).filter {
                            pendingElements.remove(it) == null
                        }.forEach {
                            SiteHandler.putSiteYell(it)
                        }
                        lastElements.subtract(elements).forEach {
                            pendingElements[it] = 0
                        }
                        pendingElements.toMap().forEach {
                            if (it.value < 5) {
                                pendingElements[it.key] = it.value + 1
                            } else {
                                pendingElements.remove(it.key)
                            }
                        }
                        lastElements = elements
                    }
                }, 0L, 1L, TimeUnit.MINUTES)
            } catch (e: Exception) {
                logFault(e)
            }
        } else {
            logInfo("TV is disabled")
        }
    }

    val tvStatus: String
        get() = "{Title: ${tv?.title}, Page: ${tv?.pageSource}, URL: ${tv?.currentUrl}, Handles: [${tv?.windowHandles?.joinToString()}]}"

    fun dispose() {
        if (tv != null) {
            future?.cancel(false)
            tv?.quit()
            tv = null
        } else {
            logInfo("TV is already disposed")
        }
    }

    fun loadBannedTV() {
        bannedTVStore = ObjectMapper().readValue(
            Paths.get("Banned TV.json").toFile().absoluteFile,
            object : TypeReference<CopyOnWriteArraySet<String>>() {})
        logInfo("Loaded Banned TV")

        saveBannedTV()
    }

    fun saveBannedTV() {
        Files.newOutputStream(Paths.get("Banned TV.json"))
            .use { ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(it, bannedTVStore) }
        logInfo("Saved Banned TV")
    }

    fun putBannedTV(tv: String) {
        bannedTVStore.add(tv)
    }

    fun wipeBannedTV(tv: String) {
        bannedTVStore.remove(tv)
    }
}