advent-of-code/src/downloader/Downloader.kt

164 lines
6.1 KiB
Kotlin

package downloader
import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlHandler
import com.mohamedrejeb.ksoup.html.parser.KsoupHtmlParser
import fuel.Fuel
import fuel.method
import kotlinx.coroutines.runBlocking
import java.io.FileNotFoundException
import java.nio.charset.Charset
import java.nio.file.Files
import java.nio.file.Paths
import java.time.LocalDate
import java.time.Month
import java.time.ZoneId
fun main() {
var cookie = "<insert your session cookie here or store in gradle.properties>"
if (Files.exists(Paths.get("gradle.properties"))) {
val cookieFromProps = Files.readAllLines(Paths.get("gradle.properties"))
.map { it.split("=") }
.filter { it.size == 2 }
.filter { it[0] == "cookie" }
.map { it[1] }.firstOrNull()
if (cookieFromProps != null) {
cookie = cookieFromProps
}
}
val downloader = Downloader(2022, "aoc2022", cookie)
downloader.updateToLatest()
}
class Downloader(val year: Int, val packageName: String, val sessionCookie: String) {
companion object {
val INPUT_FILENAME = "Day%02d.txt"
val HTML_FILENAME = "Day%02d_description.html"
val DESC_FILENAME = "Day%02d_description.txt"
val TEMPLATE_FILENAME = "Day_template.txt"
val GENCLASS_FILENAME = "Day%02d.kt"
}
fun updateToLatest() {
val targetDir = Paths.get("src", packageName)
if (!Files.exists(targetDir)) {
Files.createDirectories(targetDir)
}
val now = LocalDate.now(ZoneId.of("UTC-1"))
val lastDay = if (now.isBefore(LocalDate.of(year, Month.DECEMBER, 25))) {
if (now.isAfter(LocalDate.of(year, Month.NOVEMBER, 30))) {
now.dayOfMonth
} else {
throw IllegalStateException("You're too early.")
}
} else {
25
}
for (day in 1..lastDay) {
val inputFile = targetDir.resolve(INPUT_FILENAME.format(day))
val htmlFile = targetDir.resolve(HTML_FILENAME.format(day))
val descriptionFile = targetDir.resolve(DESC_FILENAME.format(day))
val genClassFile = targetDir.resolve(GENCLASS_FILENAME.format(day))
if (!Files.exists(inputFile)) {
println("Attempting to download input for day $day")
val (code, data) = downloadInput(day)
if (code != 200) {
throw FileNotFoundException("Could not download input for $year/$day: Status $code, Message: $data")
}
Files.write(inputFile, data.toByteArray(Charset.defaultCharset()))
} else {
println("Skipping input for day $day")
}
if (!Files.exists(htmlFile)) {
println("Attempting to download html for day $day")
val (code, data) = downloadDescription(day)
if (code != 200) {
throw FileNotFoundException("Could not download html for $year/$day: Status $code, Message: $data")
}
Files.write(htmlFile, data.toByteArray(Charset.defaultCharset()))
} else {
println("Skipping html for day $day")
}
var description = ""
var testInput = ""
if (Files.exists(htmlFile)) {
val html = Files.readAllBytes(htmlFile).toString(Charsets.UTF_8)
var enableDescription = false
var preFound = false
var codeFound = false
val handler = KsoupHtmlHandler
.Builder()
.onOpenTag { name, attributes, isImplied ->
when (name) {
"article" -> enableDescription = (attributes["class"] == "day-desc")
"pre" -> preFound = enableDescription
"code" -> codeFound = preFound
}
}
.onCloseTag { name, isImplied ->
when (name) {
"article" -> enableDescription = false
"pre" -> preFound = false
"code" -> codeFound = false
"h2" -> if (enableDescription) description += "\n"
}
}
.onText { text ->
if (enableDescription) {
description += text
}
if (preFound) {
testInput += text
}
}
.build()
val ksoupHtmlParser = KsoupHtmlParser(
handler = handler,
)
ksoupHtmlParser.write(html)
ksoupHtmlParser.end()
testInput = testInput.trim()
Files.writeString(descriptionFile, description)
}
if (!Files.exists(genClassFile)) {
var template = Files.readString(Paths.get("src", TEMPLATE_FILENAME))
template = template.replace("\${package}", packageName)
template = template.replace("\${day}", "%02d".format(day))
template = template.replace("\${testinput}", testInput)
template = template.replace("\${description}", description)
Files.writeString(genClassFile, template)
}
}
}
fun downloadInput(day: Int): Pair<Int, String> {
return downloadStuff(day, "/input")
}
fun downloadDescription(day: Int): Pair<Int, String> {
return downloadStuff(day, "")
}
fun downloadStuff(day: Int, suffix: String): Pair<Int, String> {
return runBlocking {
val req = Fuel.method(
url = "https://adventofcode.com/$year/day/$day$suffix",
method = "GET",
headers = mapOf(
"User-Agent" to "git.platon42.de/chrisly42/advent-of-code-2023",
"Cookie" to "session=$sessionCookie"
)
)
req.statusCode to req.body
}
}
}