Skip to content

Commit

Permalink
enable session cookie from outside
Browse files Browse the repository at this point in the history
  • Loading branch information
EmRe-One committed Dec 7, 2023
1 parent 9b6a17d commit c647dd4
Show file tree
Hide file tree
Showing 2 changed files with 187 additions and 176 deletions.
345 changes: 175 additions & 170 deletions src/main/kotlin/tr/emreone/kotlin_utils/automation/AdventOfCode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,179 @@ var globalTestData: String? = null
field = null
}

@ExperimentalTime
fun main() {
verbose = false
with(aocTerminal) {
println(red("\n~~~ Advent Of Code Runner ~~~\n"))
val dayClasses = getAllDayClasses().sortedBy(::dayNumber)
val totalDuration = dayClasses.map { it.execute() }.reduceOrNull(Duration::plus)
println("\nTotal runtime: ${red("$totalDuration")}")
//@ExperimentalTime
//fun main() {
// verbose = false
// with(aocTerminal) {
// println(red("\n~~~ Advent Of Code Runner ~~~\n"))
// val dayClasses = getAllDayClasses().sortedBy(::dayNumber)
// val totalDuration = dayClasses.map { it.execute() }.reduceOrNull(Duration::plus)
// println("\nTotal runtime: ${red("$totalDuration")}")
// }
//}

class AdventOfCode(var session: String? = null) {

private val web = AoCWebInterface(getSessionCookie())

fun sendToClipboard(a: Any?): Boolean {
if (a in listOf(null, 0, -1, Day.NotYetImplemented)) return false
return runCatching {
val clipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard
val transferable: Transferable = StringSelection(a.toString())
clipboard.setContents(transferable, null)
}.isSuccess
}

fun getPuzzleInput(aocPuzzle: AoCPuzzle): List<String> {
val cached = readInputFileOrNull(aocPuzzle)
if (!cached.isNullOrEmpty()) return cached

return web.downloadInput(aocPuzzle).onSuccess {
writeInputFile(aocPuzzle, it)
}.getOrElse {
listOf("Unable to download ${aocPuzzle}: $it")
}
}

fun submitAnswer(aocPuzzle: AoCPuzzle, part: Part, answer: String): AoCWebInterface.Verdict =
web.submitAnswer(aocPuzzle, part, answer)

private val logFormat = DateTimeFormatter.ofPattern("HH:mm:ss")

fun appendSubmitLog(aocPuzzle: AoCPuzzle, part: Part, answer: String, verdict: AoCWebInterface.Verdict) {
val now = LocalDateTime.now()
val nowText = logFormat.format(now)
val id = idFor(aocPuzzle, part)
val text =
"$nowText - $id - submitted \"$answer\" - ${if (verdict is AoCWebInterface.Verdict.Correct) "OK" else "FAIL with ${verdict::class.simpleName}"}"
appendSubmitLog(aocPuzzle, text)
appendSubmitLog(aocPuzzle, verdict.text)
if (verdict is AoCWebInterface.Verdict.WithWait) {
val locked = now + verdict.wait.toJavaDuration()
appendSubmitLog(
aocPuzzle,
"$nowText - $id - LOCKED until ${DateTimeFormatter.ISO_DATE_TIME.format(locked)}"
)
}
}

class PreviousSubmitted(
private val locked: LocalDateTime?,
private val answers: List<String>,
private val log: List<String>,
) {
operator fun contains(answer: String) = answer in answers
fun isNotEmpty() = answers.isNotEmpty()

override fun toString() = (
listOf(
brightMagenta("Previously submitted answers were:"),
"${log.size} attempts in total".takeIf { log.size > 3 }
) +
log.takeLast(3).map { it.highlight() } +
lockInfo()
)
.filterNotNull()
.joinToString("\n", postfix = "\n ")

private fun String.highlight() =
split("\"", limit = 3).mapIndexed { index, s -> if (index == 1) brightMagenta(s) else s }.joinToString("")

private fun lockInfo() = locked?.let {
if (isStillLocked)
brightRed("Locked until $it")
else
yellow("Had been locked, but is free again!")
}

private val isStillLocked get() = locked?.let { LocalDateTime.now() < it } == true

val waitSecondsOrNull
get() = locked?.let {
val now = LocalDateTime.now()
(it.toEpochSecond(ZoneOffset.UTC) - now.toEpochSecond(ZoneOffset.UTC))
}.takeIf { (it ?: 0) > 0 }

fun waitUntilFree() {
isStillLocked || return
with(aocTerminal) {
do {
cursor.move { startOfLine();clearLine() }
print(brightRed("Waiting $waitSecondsOrNull more seconds..."))
Thread.sleep(500)
} while (LocalDateTime.now() < locked!!)
cursor.move { startOfLine(); clearLine() }
println("Fire!")
}
}
}

fun previouslySubmitted(aocPuzzle: AoCPuzzle, part: Part): PreviousSubmitted =
readSubmitLog(aocPuzzle)
.filter { idFor(aocPuzzle, part) in it }
.let { relevant ->
val answers = relevant
.filter { "submitted" in it }
.mapNotNull { log ->
log.split("\"").getOrNull(1)?.let { it to log }
}

val locked = if ("LOCKED" in relevant.lastOrNull().orEmpty()) {
val lock = relevant.last().substringAfter("until ")
LocalDateTime.from(DateTimeFormatter.ISO_DATE_TIME.parse(lock))
} else null

PreviousSubmitted(locked, answers.map { it.first }, answers.map { it.second })
}

private fun idFor(aocPuzzle: AoCPuzzle, part: Part) =
"${aocPuzzle.year} day ${aocPuzzle.day} part $part"

private fun getSessionCookie() =
this.session
?: System.getenv("AOC_COOKIE")
?: object {}.javaClass.getResource("session-cookie")
?.readText()
?.lines()
?.firstOrNull { it.isNotBlank() }
?: warn("No session cookie in environment or file found, will not be able to talk to AoC server.")

private fun readInputFileOrNull(aocPuzzle: AoCPuzzle): List<String>? {
val file = File(fileNameFor(aocPuzzle))
file.exists() || return null
return file.readLines()
}

private fun writeInputFile(aocPuzzle: AoCPuzzle, content: List<String>) {
File(pathNameForYear(aocPuzzle)).mkdirs()
File(fileNameFor(aocPuzzle)).writeText(content.joinToString("\n"))
}

private fun readSubmitLog(aocPuzzle: AoCPuzzle): List<String> {
val file = File(submitLogFor(aocPuzzle))
file.exists() || return emptyList()
return file.readLines()
}

private fun pathNameForYear(aocPuzzle: AoCPuzzle): String {
return "puzzles/${aocPuzzle.year}"
}

private fun fileNameFor(aocPuzzle: AoCPuzzle): String {
return "${pathNameForYear(aocPuzzle)}/day${"%02d".format(aocPuzzle.day)}.txt"
}

private fun submitLogFor(aocPuzzle: AoCPuzzle): String {
return "${pathNameForYear(aocPuzzle)}/submit.log"
}

private fun appendSubmitLog(aocPuzzle: AoCPuzzle, content: String) {
File(submitLogFor(aocPuzzle)).appendText("\n$content")
}
}


fun getAllDayClasses(): Collection<Class<out Day>> =
Reflections("").getSubTypesOf(Day::class.java).filter { it.simpleName.matches(Regex("Day\\d+")) }

Expand Down Expand Up @@ -97,8 +259,11 @@ data class TestData(val input: String, val expectedPart1: Any?, val expectedPart
println(gray("Checking part $part against $expectation..."))
val actual = partFun()
val match = actual == Day.NotYetImplemented || "$actual" == expectation
if (!match) {
aocTerminal.danger("Checking of part $part failed")
if (match) {
aocTerminal.success("✅ Test succeeded.")
}
else {
aocTerminal.danger("❌ Checking of part $part failed")
println("Expected: ${brightRed(expectation)}")
println(" Actual: ${brightRed("$actual")}")
println(yellow("Check demo ${TextStyles.bold("input")} and demo ${TextStyles.bold("expectation")}!"))
Expand Down Expand Up @@ -201,166 +366,6 @@ fun Any?.restrictWidth(minWidth: Int, maxWidth: Int) = with("$this") {
}
}

object AoC {

private val web = AoCWebInterface(getSessionCookie())

fun sendToClipboard(a: Any?): Boolean {
if (a in listOf(null, 0, -1, Day.NotYetImplemented)) return false
return runCatching {
val clipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard
val transferable: Transferable = StringSelection(a.toString())
clipboard.setContents(transferable, null)
}.isSuccess
}

fun getPuzzleInput(aocPuzzle: AoCPuzzle): List<String> {
val cached = readInputFileOrNull(aocPuzzle)
if (!cached.isNullOrEmpty()) return cached

return web.downloadInput(aocPuzzle).onSuccess {
writeInputFile(aocPuzzle, it)
}.getOrElse {
listOf("Unable to download ${aocPuzzle}: $it")
}
}

fun submitAnswer(aocPuzzle: AoCPuzzle, part: Part, answer: String): AoCWebInterface.Verdict =
web.submitAnswer(aocPuzzle, part, answer)

private val logFormat = DateTimeFormatter.ofPattern("HH:mm:ss")

fun appendSubmitLog(aocPuzzle: AoCPuzzle, part: Part, answer: String, verdict: AoCWebInterface.Verdict) {
val now = LocalDateTime.now()
val nowText = logFormat.format(now)
val id = idFor(aocPuzzle, part)
val text =
"$nowText - $id - submitted \"$answer\" - ${if (verdict is AoCWebInterface.Verdict.Correct) "OK" else "FAIL with ${verdict::class.simpleName}"}"
appendSubmitLog(aocPuzzle, text)
appendSubmitLog(aocPuzzle, verdict.text)
if (verdict is AoCWebInterface.Verdict.WithWait) {
val locked = now + verdict.wait.toJavaDuration()
appendSubmitLog(
aocPuzzle,
"$nowText - $id - LOCKED until ${DateTimeFormatter.ISO_DATE_TIME.format(locked)}"
)
}
}

class PreviousSubmitted(
private val locked: LocalDateTime?,
private val answers: List<String>,
private val log: List<String>,
) {
operator fun contains(answer: String) = answer in answers
fun isNotEmpty() = answers.isNotEmpty()

override fun toString() = (
listOf(
brightMagenta("Previously submitted answers were:"),
"${log.size} attempts in total".takeIf { log.size > 3 }
) +
log.takeLast(3).map { it.highlight() } +
lockInfo()
)
.filterNotNull()
.joinToString("\n", postfix = "\n ")

private fun String.highlight() =
split("\"", limit = 3).mapIndexed { index, s -> if (index == 1) brightMagenta(s) else s }.joinToString("")

private fun lockInfo() = locked?.let {
if (isStillLocked)
brightRed("Locked until $it")
else
yellow("Had been locked, but is free again!")
}

private val isStillLocked get() = locked?.let { LocalDateTime.now() < it } == true

val waitSecondsOrNull
get() = locked?.let {
val now = LocalDateTime.now()
(it.toEpochSecond(ZoneOffset.UTC) - now.toEpochSecond(ZoneOffset.UTC))
}.takeIf { (it ?: 0) > 0 }

fun waitUntilFree() {
isStillLocked || return
with(aocTerminal) {
do {
cursor.move { startOfLine();clearLine() }
print(brightRed("Waiting $waitSecondsOrNull more seconds..."))
Thread.sleep(500)
} while (LocalDateTime.now() < locked!!)
cursor.move { startOfLine(); clearLine() }
println("Fire!")
}
}
}

fun previouslySubmitted(aocPuzzle: AoCPuzzle, part: Part): PreviousSubmitted =
readSubmitLog(aocPuzzle)
.filter { idFor(aocPuzzle, part) in it }
.let { relevant ->
val answers = relevant
.filter { "submitted" in it }
.mapNotNull { log ->
log.split("\"").getOrNull(1)?.let { it to log }
}

val locked = if ("LOCKED" in relevant.lastOrNull().orEmpty()) {
val lock = relevant.last().substringAfter("until ")
LocalDateTime.from(DateTimeFormatter.ISO_DATE_TIME.parse(lock))
} else null

PreviousSubmitted(locked, answers.map { it.first }, answers.map { it.second })
}

private fun idFor(aocPuzzle: AoCPuzzle, part: Part) =
"${aocPuzzle.year} day ${aocPuzzle.day} part $part"

private fun getSessionCookie() =
System.getenv("AOC_COOKIE")
?: object {}.javaClass.getResource("session-cookie")
?.readText()
?.lines()
?.firstOrNull { it.isNotBlank() }
?: warn("No session cookie in environment or file found, will not be able to talk to AoC server.")

private fun readInputFileOrNull(aocPuzzle: AoCPuzzle): List<String>? {
val file = File(fileNameFor(aocPuzzle))
file.exists() || return null
return file.readLines()
}

private fun writeInputFile(aocPuzzle: AoCPuzzle, content: List<String>) {
File(pathNameForYear(aocPuzzle)).mkdirs()
File(fileNameFor(aocPuzzle)).writeText(content.joinToString("\n"))
}

private fun readSubmitLog(aocPuzzle: AoCPuzzle): List<String> {
val file = File(submitLogFor(aocPuzzle))
file.exists() || return emptyList()
return file.readLines()
}

private fun pathNameForYear(aocPuzzle: AoCPuzzle): String {
return "puzzles/${aocPuzzle.year}"
}

private fun fileNameFor(aocPuzzle: AoCPuzzle): String {
return "${pathNameForYear(aocPuzzle)}/day${"%02d".format(aocPuzzle.day)}.txt"
}

private fun submitLogFor(aocPuzzle: AoCPuzzle): String {
return "${pathNameForYear(aocPuzzle)}/submit.log"
}

private fun appendSubmitLog(aocPuzzle: AoCPuzzle, content: String) {
File(submitLogFor(aocPuzzle)).appendText("\n$content")
}
}

@Suppress("NOTHING_TO_INLINE")
inline fun useSystemProxies() {
System.setProperty("java.net.useSystemProxies", "true")
Expand Down
Loading

0 comments on commit c647dd4

Please sign in to comment.