working checker
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -43,3 +43,4 @@ bin/
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Mac OS ###
 | 
					### Mac OS ###
 | 
				
			||||||
.DS_Store
 | 
					.DS_Store
 | 
				
			||||||
 | 
					/chromedriver.log
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,56 +0,0 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.config
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.openqa.selenium.chrome.ChromeDriver
 | 
					 | 
				
			||||||
import org.openqa.selenium.chrome.ChromeDriverService
 | 
					 | 
				
			||||||
import org.openqa.selenium.chrome.ChromeOptions
 | 
					 | 
				
			||||||
import org.springframework.context.annotation.Bean
 | 
					 | 
				
			||||||
import org.springframework.context.annotation.Configuration
 | 
					 | 
				
			||||||
import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter
 | 
					 | 
				
			||||||
import org.springframework.web.client.RestTemplate
 | 
					 | 
				
			||||||
import kotlinx.serialization.json.Json
 | 
					 | 
				
			||||||
import java.io.File
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Configuration
 | 
					 | 
				
			||||||
class AppConfig {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Bean
 | 
					 | 
				
			||||||
    fun restTemplate(): RestTemplate {
 | 
					 | 
				
			||||||
        return RestTemplate()
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Bean
 | 
					 | 
				
			||||||
    fun json(): Json {
 | 
					 | 
				
			||||||
        return Json {
 | 
					 | 
				
			||||||
            ignoreUnknownKeys = true
 | 
					 | 
				
			||||||
            coerceInputValues = true
 | 
					 | 
				
			||||||
            prettyPrint = true
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Bean
 | 
					 | 
				
			||||||
    fun additionalMessageConverters(json: Json): List<KotlinSerializationJsonHttpMessageConverter> {
 | 
					 | 
				
			||||||
        return listOf(KotlinSerializationJsonHttpMessageConverter(json))
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Bean(destroyMethod = "quit")
 | 
					 | 
				
			||||||
    fun chromeDriver(): ChromeDriver {
 | 
					 | 
				
			||||||
        // Set log file to suppress console output (optional)
 | 
					 | 
				
			||||||
        val logFile = File("chromedriver.log")
 | 
					 | 
				
			||||||
        val service = ChromeDriverService.Builder()
 | 
					 | 
				
			||||||
            .withLogFile(logFile) // Redirect logs to a file
 | 
					 | 
				
			||||||
            .withSilent(true) // Suppress console output
 | 
					 | 
				
			||||||
            .withVerbose(false) // Explicitly disable verbose logging
 | 
					 | 
				
			||||||
            .build()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Optional: Set hideCommandPromptWindow for Windows
 | 
					 | 
				
			||||||
        System.setProperty("webdriver.chrome.hideCommandPromptWindow", "true")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        val options = ChromeOptions().apply {
 | 
					 | 
				
			||||||
            addArguments("--headless")
 | 
					 | 
				
			||||||
            addArguments("--disable-gpu")
 | 
					 | 
				
			||||||
            addArguments("--no-sandbox")
 | 
					 | 
				
			||||||
            addArguments("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return ChromeDriver(service, options)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,11 +1,62 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto
 | 
					package net.xintanalabs.rssotto.config
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import kotlinx.serialization.Serializable
 | 
					import org.openqa.selenium.chrome.ChromeDriver
 | 
				
			||||||
import org.springframework.boot.context.properties.ConfigurationProperties
 | 
					import org.openqa.selenium.chrome.ChromeDriverService
 | 
				
			||||||
import org.springframework.boot.context.properties.bind.ConstructorBinding
 | 
					import org.openqa.selenium.chrome.ChromeOptions
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.Bean
 | 
				
			||||||
 | 
					import org.springframework.context.annotation.Configuration
 | 
				
			||||||
 | 
					import org.springframework.http.converter.json.KotlinSerializationJsonHttpMessageConverter
 | 
				
			||||||
 | 
					import org.springframework.web.client.RestTemplate
 | 
				
			||||||
 | 
					import kotlinx.serialization.json.Json
 | 
				
			||||||
 | 
					import org.springframework.web.reactive.function.client.WebClient
 | 
				
			||||||
 | 
					import java.io.File
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Serializable
 | 
					@Configuration
 | 
				
			||||||
@ConfigurationProperties(prefix = "version-checker")
 | 
					class ApplicationConfig {
 | 
				
			||||||
data class ApplicationConfig(
 | 
					
 | 
				
			||||||
    val intervalMinutes: Int
 | 
					    @Bean
 | 
				
			||||||
)
 | 
					    fun restTemplate(): RestTemplate {
 | 
				
			||||||
 | 
					        return RestTemplate()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Bean
 | 
				
			||||||
 | 
					    fun json(): Json {
 | 
				
			||||||
 | 
					        return Json {
 | 
				
			||||||
 | 
					            ignoreUnknownKeys = true
 | 
				
			||||||
 | 
					            coerceInputValues = true
 | 
				
			||||||
 | 
					            prettyPrint = true
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Bean
 | 
				
			||||||
 | 
					    fun webClient(builder: WebClient.Builder): WebClient {
 | 
				
			||||||
 | 
					        return builder.build()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Bean
 | 
				
			||||||
 | 
					    fun additionalMessageConverters(json: Json): List<KotlinSerializationJsonHttpMessageConverter> {
 | 
				
			||||||
 | 
					        return listOf(KotlinSerializationJsonHttpMessageConverter(json))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Bean(destroyMethod = "quit")
 | 
				
			||||||
 | 
					    fun chromeDriver(): ChromeDriver {
 | 
				
			||||||
 | 
					        // Set log file to suppress console output (optional)
 | 
				
			||||||
 | 
					        val logFile = File("chromedriver.log")
 | 
				
			||||||
 | 
					        val service = ChromeDriverService.Builder()
 | 
				
			||||||
 | 
					            .withLogFile(logFile) // Redirect logs to a file
 | 
				
			||||||
 | 
					            .withSilent(true) // Suppress console output
 | 
				
			||||||
 | 
					            .withVerbose(false) // Explicitly disable verbose logging
 | 
				
			||||||
 | 
					            .build()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Optional: Set hideCommandPromptWindow for Windows
 | 
				
			||||||
 | 
					        System.setProperty("webdriver.chrome.hideCommandPromptWindow", "true")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val options = ChromeOptions().apply {
 | 
				
			||||||
 | 
					            addArguments("--headless")
 | 
				
			||||||
 | 
					            addArguments("--disable-gpu")
 | 
				
			||||||
 | 
					            addArguments("--no-sandbox")
 | 
				
			||||||
 | 
					            addArguments("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124")
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return ChromeDriver(service, options)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.serialization.Serializable
 | 
				
			||||||
 | 
					import org.springframework.boot.context.properties.ConfigurationProperties
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Serializable
 | 
				
			||||||
 | 
					@ConfigurationProperties(prefix = "version-checker")
 | 
				
			||||||
 | 
					data class ApplicationConfigProperties(
 | 
				
			||||||
 | 
					    val intervalMinutes: Int
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -2,8 +2,10 @@ package net.xintanalabs.rssotto
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import org.springframework.boot.autoconfigure.SpringBootApplication
 | 
					import org.springframework.boot.autoconfigure.SpringBootApplication
 | 
				
			||||||
import org.springframework.boot.runApplication
 | 
					import org.springframework.boot.runApplication
 | 
				
			||||||
 | 
					import org.springframework.scheduling.annotation.EnableScheduling
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@SpringBootApplication
 | 
					@SpringBootApplication
 | 
				
			||||||
 | 
					@EnableScheduling
 | 
				
			||||||
class RssotoApplication
 | 
					class RssotoApplication
 | 
				
			||||||
 | 
					
 | 
				
			||||||
fun main(args: Array<String>) {
 | 
					fun main(args: Array<String>) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,8 +9,8 @@ import org.springframework.web.reactive.function.client.awaitExchange
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
@Component
 | 
					@Component
 | 
				
			||||||
class ApiChecker(private val webClient: WebClient) : IVersionChecker {
 | 
					class ApiChecker(private val webClient: WebClient) : IVersionChecker {
 | 
				
			||||||
    override suspend fun getLatestVersion(paramsDict: Map<String, String>, source: Source): String {
 | 
					    override suspend fun getLatestVersion(paramsDict: Map<String, String>): String {
 | 
				
			||||||
        val url = paramsDict["url"]?.takeIf { it.isNotEmpty() }
 | 
					       val url = paramsDict["url"]?.takeIf { it.isNotEmpty() }
 | 
				
			||||||
            ?: throw IllegalArgumentException("API URL required")
 | 
					            ?: throw IllegalArgumentException("API URL required")
 | 
				
			||||||
        val jsonPath = paramsDict["jsonPath"]?.takeIf { it.isNotEmpty() }
 | 
					        val jsonPath = paramsDict["jsonPath"]?.takeIf { it.isNotEmpty() }
 | 
				
			||||||
            ?: throw IllegalArgumentException("jsonPath required")
 | 
					            ?: throw IllegalArgumentException("jsonPath required")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,5 +3,5 @@ package net.xintanalabs.rssotto.components.checkers
 | 
				
			|||||||
import net.xintanalabs.rssotto.model.Source
 | 
					import net.xintanalabs.rssotto.model.Source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IVersionChecker {
 | 
					interface IVersionChecker {
 | 
				
			||||||
    suspend fun getLatestVersion(paramsDict: Map<String, String>, source: Source): String
 | 
					    suspend fun getLatestVersion(paramsDict: Map<String, String>): String
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,70 +0,0 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.components.checkers
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import kotlinx.coroutines.*
 | 
					 | 
				
			||||||
import net.xintanalabs.rssotto.ApplicationConfig
 | 
					 | 
				
			||||||
import net.xintanalabs.rssotto.model.App
 | 
					 | 
				
			||||||
import net.xintanalabs.rssotto.model.CheckerType
 | 
					 | 
				
			||||||
import net.xintanalabs.rssotto.model.Source
 | 
					 | 
				
			||||||
import net.xintanalabs.rssotto.services.AppService
 | 
					 | 
				
			||||||
import net.xintanalabs.rssotto.services.CheckerTypeService
 | 
					 | 
				
			||||||
import net.xintanalabs.rssotto.services.SourceService
 | 
					 | 
				
			||||||
import org.slf4j.LoggerFactory
 | 
					 | 
				
			||||||
import org.springframework.scheduling.annotation.Async
 | 
					 | 
				
			||||||
import org.springframework.scheduling.annotation.Scheduled
 | 
					 | 
				
			||||||
import org.springframework.stereotype.Component
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Component
 | 
					 | 
				
			||||||
class VersionCheckTask(
 | 
					 | 
				
			||||||
    private val config: ApplicationConfig,
 | 
					 | 
				
			||||||
    private val appService: AppService,
 | 
					 | 
				
			||||||
    private val  sourceService: SourceService,
 | 
					 | 
				
			||||||
    private val checkerTypeService: CheckerTypeService
 | 
					 | 
				
			||||||
    //private val versionService: VersionService
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private val logger = LoggerFactory.getLogger(VersionCheckTask::class.java)
 | 
					 | 
				
			||||||
    private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //@Scheduled(fixedRateString = "\${version-checker.interval-minutes}000", initialDelay = 1000)
 | 
					 | 
				
			||||||
    //@Async
 | 
					 | 
				
			||||||
    fun checkVersions() {
 | 
					 | 
				
			||||||
        scope.launch {
 | 
					 | 
				
			||||||
            val apps: List<App> = appService.getAllApps()
 | 
					 | 
				
			||||||
            var sources: List<Source> = sourceService.getAllSources()
 | 
					 | 
				
			||||||
            var checkerTypes: List<CheckerType> = checkerTypeService.getAllCheckerTypes()
 | 
					 | 
				
			||||||
            logger.info("Starting version check task for ${apps.size} sources")
 | 
					 | 
				
			||||||
            apps.map { app ->
 | 
					 | 
				
			||||||
                // async {
 | 
					 | 
				
			||||||
                    // try {
 | 
					 | 
				
			||||||
                        /*
 | 
					 | 
				
			||||||
                        val source: Source? = sources.find { s -> s.id == app.source }
 | 
					 | 
				
			||||||
                        if (source != null) {
 | 
					 | 
				
			||||||
                            val checkerType: CheckerType? = checkerTypes.find { ct -> ct.id == source.checkerType }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            app.params
 | 
					 | 
				
			||||||
                            val params = mutableMapOf<String, String>()
 | 
					 | 
				
			||||||
                            app.url.let { params["url"] = it }
 | 
					 | 
				
			||||||
                            app.jsonPath?.let { params["jsonPath"] = it }
 | 
					 | 
				
			||||||
                            app.regex?.let { params["regex"] = it }
 | 
					 | 
				
			||||||
                            app.mode?.let { params["mode"] = it }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            val sourceDef = SourceDef(
 | 
					 | 
				
			||||||
                                defaults = mapOf(
 | 
					 | 
				
			||||||
                                    "regex" to (app.regex ?: ""),
 | 
					 | 
				
			||||||
                                    "mode" to (app.mode ?: "default")
 | 
					 | 
				
			||||||
                                )
 | 
					 | 
				
			||||||
                            )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            val version = versionService.checkVersion(app.type, params, sourceDef)
 | 
					 | 
				
			||||||
                            logger.info("Version for ${app.url} (${app.type}): $version")*/
 | 
					 | 
				
			||||||
                            // Optionally save to DB (if using JPA)
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    //} catch (e: Exception) {
 | 
					 | 
				
			||||||
                        //logger.error("Failed to check version for ${app.url}: ${e.message}", e)
 | 
					 | 
				
			||||||
                    //}
 | 
					 | 
				
			||||||
                // }
 | 
					 | 
				
			||||||
            // }.awaitAll()
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,38 +0,0 @@
 | 
				
			|||||||
package net.xintanalabs.rssotto.components.checkers
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import org.openqa.selenium.chrome.ChromeDriver
 | 
					 | 
				
			||||||
import org.openqa.selenium.chrome.ChromeDriverService
 | 
					 | 
				
			||||||
import org.openqa.selenium.chrome.ChromeOptions
 | 
					 | 
				
			||||||
import org.springframework.context.annotation.Bean
 | 
					 | 
				
			||||||
import org.springframework.context.annotation.Configuration
 | 
					 | 
				
			||||||
import org.springframework.web.client.RestTemplate
 | 
					 | 
				
			||||||
import org.springframework.web.reactive.function.client.WebClient
 | 
					 | 
				
			||||||
import java.io.File
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Configuration
 | 
					 | 
				
			||||||
class WebConfig {
 | 
					 | 
				
			||||||
    @Bean
 | 
					 | 
				
			||||||
    fun restTemplate(): RestTemplate = RestTemplate()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Bean
 | 
					 | 
				
			||||||
    fun webClient(): WebClient = WebClient.builder().build()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    @Bean
 | 
					 | 
				
			||||||
    fun chromeDriver(): ChromeDriver {
 | 
					 | 
				
			||||||
        val logFile = File("chromedriver.log")
 | 
					 | 
				
			||||||
        val service = ChromeDriverService.Builder()
 | 
					 | 
				
			||||||
            .withLogFile(logFile) // Redirect logs to a file
 | 
					 | 
				
			||||||
            .withSilent(true) // Suppress console output
 | 
					 | 
				
			||||||
            .withVerbose(false) // Explicitly disable verbose logging
 | 
					 | 
				
			||||||
            .build()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        val options = ChromeOptions().apply {
 | 
					 | 
				
			||||||
            addArguments("--headless")
 | 
					 | 
				
			||||||
            addArguments("--disable-gpu")
 | 
					 | 
				
			||||||
            addArguments("--no-sandbox")
 | 
					 | 
				
			||||||
            addArguments("--user-agent=Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/91.0.4472.124")
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        return ChromeDriver(service, options)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -3,7 +3,9 @@ package net.xintanalabs.rssotto.components.checkers.scrape
 | 
				
			|||||||
import kotlinx.coroutines.delay
 | 
					import kotlinx.coroutines.delay
 | 
				
			||||||
import net.xintanalabs.rssotto.components.checkers.IVersionChecker
 | 
					import net.xintanalabs.rssotto.components.checkers.IVersionChecker
 | 
				
			||||||
import net.xintanalabs.rssotto.model.Source
 | 
					import net.xintanalabs.rssotto.model.Source
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.tasks.ScheduledTasks
 | 
				
			||||||
import org.openqa.selenium.chrome.ChromeDriver
 | 
					import org.openqa.selenium.chrome.ChromeDriver
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
import org.springframework.stereotype.Component
 | 
					import org.springframework.stereotype.Component
 | 
				
			||||||
import org.springframework.web.client.RestTemplate
 | 
					import org.springframework.web.client.RestTemplate
 | 
				
			||||||
import java.util.regex.Pattern
 | 
					import java.util.regex.Pattern
 | 
				
			||||||
@@ -13,31 +15,29 @@ class ScrapeChecker(
 | 
				
			|||||||
    private val restTemplate: RestTemplate,
 | 
					    private val restTemplate: RestTemplate,
 | 
				
			||||||
    private val chromeDriver: ChromeDriver
 | 
					    private val chromeDriver: ChromeDriver
 | 
				
			||||||
) : IVersionChecker {
 | 
					) : IVersionChecker {
 | 
				
			||||||
    override suspend fun getLatestVersion(paramsDict: Map<String, String>, source: Source): String {
 | 
					
 | 
				
			||||||
 | 
					    private val log = LoggerFactory.getLogger(ScrapeChecker::class.java)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    override suspend fun getLatestVersion(paramsDict: Map<String, String>): String {
 | 
				
			||||||
        val url = paramsDict["url"]?.takeIf { it.isNotEmpty() }
 | 
					        val url = paramsDict["url"]?.takeIf { it.isNotEmpty() }
 | 
				
			||||||
            ?: throw IllegalArgumentException("URL required")
 | 
					            ?: throw IllegalArgumentException("URL required")
 | 
				
			||||||
 | 
					        log.info("Url : {}", url)
 | 
				
			||||||
        val mode = getValueOrDefault(paramsDict, "mode", source)
 | 
					        val mode = paramsDict["mode"]
 | 
				
			||||||
 | 
					        log.info("Mode : {}", mode)
 | 
				
			||||||
        val fetcher: IScrapeFetcher = when (mode) {
 | 
					        val fetcher: IScrapeFetcher = when (mode) {
 | 
				
			||||||
            "selenium" -> SeleniumFetcher(chromeDriver)
 | 
					            "selenium" -> SeleniumFetcher(chromeDriver)
 | 
				
			||||||
            "jsoup" -> JSoupFetcher()
 | 
					            "jsoup" -> JSoupFetcher()
 | 
				
			||||||
            else -> DefaultScrapeFetcher(restTemplate)
 | 
					            else -> DefaultScrapeFetcher(restTemplate)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        val response = fetcher.fetch(url)
 | 
					        val response = fetcher.fetch(url)
 | 
				
			||||||
 | 
					 | 
				
			||||||
        val cleanedResponse = response.replace(">\\s+<".toRegex(), "><")
 | 
					        val cleanedResponse = response.replace(">\\s+<".toRegex(), "><")
 | 
				
			||||||
        val regex = getValueOrDefault(paramsDict, "regex", source)
 | 
					        log.info("Response : {}", cleanedResponse)
 | 
				
			||||||
 | 
					        val regex = paramsDict["regex"]
 | 
				
			||||||
 | 
					        log.info("Regex : {}", regex)
 | 
				
			||||||
        val match = Pattern.compile(regex).matcher(cleanedResponse)
 | 
					        val match = Pattern.compile(regex).matcher(cleanedResponse)
 | 
				
			||||||
        if (!match.find() || match.groupCount() < 1) {
 | 
					        if (!match.find() || match.groupCount() < 1) {
 | 
				
			||||||
            throw Exception("No match with regex in response")
 | 
					            throw Exception("No match with regex in response")
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        return match.group(1)
 | 
					        return match.group(1)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    private fun getValueOrDefault(dict: Map<String, String>, key: String, source: Source): String {
 | 
					 | 
				
			||||||
        return dict[key]?.takeIf { it.isNotEmpty() }
 | 
					 | 
				
			||||||
            ?: source.defaults[key]
 | 
					 | 
				
			||||||
            ?: ""
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,33 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.controller
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.constants.Constants
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.services.AppService
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.services.CheckerTypeService
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.services.LatestVersionFinderService
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.services.SourceService
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.PostMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RequestMapping
 | 
				
			||||||
 | 
					import org.springframework.web.bind.annotation.RestController
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@RestController
 | 
				
			||||||
 | 
					@RequestMapping("${Constants.API_BASE_PATH_V0}/exec")
 | 
				
			||||||
 | 
					class LatestVersionFinderController(
 | 
				
			||||||
 | 
					    private val latestVersionFinderService: LatestVersionFinderService,
 | 
				
			||||||
 | 
					    private val appService: AppService,
 | 
				
			||||||
 | 
					    private val sourceService: SourceService,
 | 
				
			||||||
 | 
					    private val checkerTypeService: CheckerTypeService
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					    @PostMapping("/version-check")
 | 
				
			||||||
 | 
					    suspend fun executeVersionCheck(): String? {
 | 
				
			||||||
 | 
					        return try {
 | 
				
			||||||
 | 
					            val apps = appService.getAllApps()
 | 
				
			||||||
 | 
					            val sources = sourceService.getAllSources()
 | 
				
			||||||
 | 
					            val checkerTypes = checkerTypeService.getAllCheckerTypes()
 | 
				
			||||||
 | 
					            latestVersionFinderService.getAllLatestAppVersions(apps,sources, checkerTypes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            "ok"
 | 
				
			||||||
 | 
					        } catch(e: Exception) {
 | 
				
			||||||
 | 
					            e.message
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -14,11 +14,19 @@ import org.springframework.web.bind.annotation.RestController
 | 
				
			|||||||
class SourceController(private val sourceService: SourceService) {
 | 
					class SourceController(private val sourceService: SourceService) {
 | 
				
			||||||
    @PostMapping
 | 
					    @PostMapping
 | 
				
			||||||
    suspend fun createSource(@RequestBody source: Source): Source {
 | 
					    suspend fun createSource(@RequestBody source: Source): Source {
 | 
				
			||||||
        return sourceService.createSource(source)
 | 
					        return try {
 | 
				
			||||||
 | 
					            sourceService.createSource(source)
 | 
				
			||||||
 | 
					        } catch (e: Exception) {
 | 
				
			||||||
 | 
					            throw RuntimeException(e.message, e)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @GetMapping
 | 
					    @GetMapping
 | 
				
			||||||
    suspend fun getAllSources(): List<Source> {
 | 
					    suspend fun getAllSources(): List<Source> {
 | 
				
			||||||
        return sourceService.getAllSources()
 | 
					        return try {
 | 
				
			||||||
 | 
					            sourceService.getAllSources()
 | 
				
			||||||
 | 
					        } catch (e: Exception) {
 | 
				
			||||||
 | 
					            throw RuntimeException(e.message, e)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -2,11 +2,13 @@ package net.xintanalabs.rssotto.model
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.fasterxml.jackson.annotation.JsonInclude
 | 
					import com.fasterxml.jackson.annotation.JsonInclude
 | 
				
			||||||
import org.springframework.data.annotation.Id
 | 
					import org.springframework.data.annotation.Id
 | 
				
			||||||
 | 
					import org.springframework.data.mongodb.core.mapping.Field
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@JsonInclude(JsonInclude.Include.NON_NULL)
 | 
					@JsonInclude(JsonInclude.Include.NON_NULL)
 | 
				
			||||||
data class Source(
 | 
					data class Source(
 | 
				
			||||||
    @Id val id: String? = null,
 | 
					    @Id val id: String? = null,
 | 
				
			||||||
    val name: String,
 | 
					    val name: String,
 | 
				
			||||||
 | 
					    @Field("type")
 | 
				
			||||||
    val checkerType: String,
 | 
					    val checkerType: String,
 | 
				
			||||||
    val defaults: Map<String, String> = mapOf()
 | 
					    val defaults: Map<String, String> = mapOf()
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,72 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.services
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.components.checkers.CheckerFactory
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.components.checkers.IVersionChecker
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.components.checkers.scrape.ScrapeChecker
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.App
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.CheckerType
 | 
				
			||||||
 | 
					import net.xintanalabs.rssotto.model.Source
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Service
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Service
 | 
				
			||||||
 | 
					class LatestVersionFinderService(private val checkerFactory: CheckerFactory) {
 | 
				
			||||||
 | 
					    private val log = LoggerFactory.getLogger(LatestVersionFinderService::class.java)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun getLatestAppVersion(app: App, sources: List<Source>, checkerTypes: List<CheckerType>): String? {
 | 
				
			||||||
 | 
					        val source: Source? = sources.find { s -> s.id == app.source }
 | 
				
			||||||
 | 
					        var appVersion: String? = null
 | 
				
			||||||
 | 
					        if (source != null) {
 | 
				
			||||||
 | 
					            val checkerType: CheckerType? = checkerTypes.find { ct -> ct.id == source.checkerType }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (checkerType != null) {
 | 
				
			||||||
 | 
					                val checker: IVersionChecker = checkerFactory.createChecker(checkerType.name)
 | 
				
			||||||
 | 
					                val paramsMap: Map<String, String> = mapOf<String, String>(
 | 
				
			||||||
 | 
					                    "url" to  getUrl(app, source),
 | 
				
			||||||
 | 
					                    "regex" to  getRegex(app, source),
 | 
				
			||||||
 | 
					                    "mode" to getMode(app, source)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                appVersion = checker.getLatestVersion(paramsMap)
 | 
				
			||||||
 | 
					                log.info("App (${app.name}, latest versión ${appVersion}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return appVersion
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun getAllLatestAppVersions(
 | 
				
			||||||
 | 
					        apps: List<App>,
 | 
				
			||||||
 | 
					        sources: List<Source>,
 | 
				
			||||||
 | 
					        checkerTypes: List<CheckerType>
 | 
				
			||||||
 | 
					    ): Map<String, String?> {
 | 
				
			||||||
 | 
					        val versionsMap: MutableMap<String, String?> = mutableMapOf<String, String?>()
 | 
				
			||||||
 | 
					        apps.map { app ->
 | 
				
			||||||
 | 
					            if (app.id !== null) {
 | 
				
			||||||
 | 
					                val appLatestVersion: String? = getLatestAppVersion(app, sources, checkerTypes)
 | 
				
			||||||
 | 
					                versionsMap[app.id] = appLatestVersion
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return versionsMap
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getValue(field: String, app: App, source: Source): String {
 | 
				
			||||||
 | 
					        var value: String? = app.params[field]
 | 
				
			||||||
 | 
					        if (value == null) {
 | 
				
			||||||
 | 
					            value = source.defaults[field]
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return value ?: ""
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getMode(app: App, source: Source): String {
 | 
				
			||||||
 | 
					        return getValue("mode", app, source)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getUrl(app: App, source: Source): String {
 | 
				
			||||||
 | 
					        return getValue("url", app, source)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private fun getRegex(app: App, source: Source): String {
 | 
				
			||||||
 | 
					        return getValue("regex", app, source)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					package net.xintanalabs.rssotto.tasks
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import org.slf4j.LoggerFactory
 | 
				
			||||||
 | 
					import org.springframework.scheduling.annotation.Scheduled
 | 
				
			||||||
 | 
					import org.springframework.stereotype.Component
 | 
				
			||||||
 | 
					import java.text.SimpleDateFormat
 | 
				
			||||||
 | 
					import java.util.Date
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Component
 | 
				
			||||||
 | 
					class ScheduledTasks {
 | 
				
			||||||
 | 
					    private val log = LoggerFactory.getLogger(ScheduledTasks::class.java)
 | 
				
			||||||
 | 
					    private val dateFormat = SimpleDateFormat("HH:mm:ss")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @Scheduled(fixedRate = 500000)
 | 
				
			||||||
 | 
					    fun reportCurrentTime() {
 | 
				
			||||||
 | 
					        log.info("The time is now {}", dateFormat.format(Date()))
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user