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.io.IOException 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 allowedTVStore = ConcurrentSkipListSet<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 (e: 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 (e: 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 { allowedTVStore.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 loadAllowedTV() { try { allowedTVStore.addAll( ObjectMapper().readValue( Paths.get("Allowed TV.json").toFile().absoluteFile, object : TypeReference<ConcurrentSkipListSet<String>>() {}) ) logInfo("Loaded Allowed TV") } catch (e: IOException) { logFault(e) } } fun saveAllowedTV() { Files.newOutputStream(Paths.get("Allowed TV.json")) .use { ObjectMapper().writerWithDefaultPrettyPrinter().writeValue(it, allowedTVStore) } logInfo("Saved Allowed TV") } fun putAllowedTV(tv: String) { allowedTVStore.add(tv) } fun wipeAllowedTV(tv: String) { allowedTVStore.remove(tv) } }