Skip to content

Commit c647dd4

Browse files
committed
enable session cookie from outside
1 parent 9b6a17d commit c647dd4

File tree

2 files changed

+187
-176
lines changed

2 files changed

+187
-176
lines changed

src/main/kotlin/tr/emreone/kotlin_utils/automation/AdventOfCode.kt

Lines changed: 175 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,179 @@ var globalTestData: String? = null
3131
field = null
3232
}
3333

34-
@ExperimentalTime
35-
fun main() {
36-
verbose = false
37-
with(aocTerminal) {
38-
println(red("\n~~~ Advent Of Code Runner ~~~\n"))
39-
val dayClasses = getAllDayClasses().sortedBy(::dayNumber)
40-
val totalDuration = dayClasses.map { it.execute() }.reduceOrNull(Duration::plus)
41-
println("\nTotal runtime: ${red("$totalDuration")}")
34+
//@ExperimentalTime
35+
//fun main() {
36+
// verbose = false
37+
// with(aocTerminal) {
38+
// println(red("\n~~~ Advent Of Code Runner ~~~\n"))
39+
// val dayClasses = getAllDayClasses().sortedBy(::dayNumber)
40+
// val totalDuration = dayClasses.map { it.execute() }.reduceOrNull(Duration::plus)
41+
// println("\nTotal runtime: ${red("$totalDuration")}")
42+
// }
43+
//}
44+
45+
class AdventOfCode(var session: String? = null) {
46+
47+
private val web = AoCWebInterface(getSessionCookie())
48+
49+
fun sendToClipboard(a: Any?): Boolean {
50+
if (a in listOf(null, 0, -1, Day.NotYetImplemented)) return false
51+
return runCatching {
52+
val clipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard
53+
val transferable: Transferable = StringSelection(a.toString())
54+
clipboard.setContents(transferable, null)
55+
}.isSuccess
56+
}
57+
58+
fun getPuzzleInput(aocPuzzle: AoCPuzzle): List<String> {
59+
val cached = readInputFileOrNull(aocPuzzle)
60+
if (!cached.isNullOrEmpty()) return cached
61+
62+
return web.downloadInput(aocPuzzle).onSuccess {
63+
writeInputFile(aocPuzzle, it)
64+
}.getOrElse {
65+
listOf("Unable to download ${aocPuzzle}: $it")
66+
}
67+
}
68+
69+
fun submitAnswer(aocPuzzle: AoCPuzzle, part: Part, answer: String): AoCWebInterface.Verdict =
70+
web.submitAnswer(aocPuzzle, part, answer)
71+
72+
private val logFormat = DateTimeFormatter.ofPattern("HH:mm:ss")
73+
74+
fun appendSubmitLog(aocPuzzle: AoCPuzzle, part: Part, answer: String, verdict: AoCWebInterface.Verdict) {
75+
val now = LocalDateTime.now()
76+
val nowText = logFormat.format(now)
77+
val id = idFor(aocPuzzle, part)
78+
val text =
79+
"$nowText - $id - submitted \"$answer\" - ${if (verdict is AoCWebInterface.Verdict.Correct) "OK" else "FAIL with ${verdict::class.simpleName}"}"
80+
appendSubmitLog(aocPuzzle, text)
81+
appendSubmitLog(aocPuzzle, verdict.text)
82+
if (verdict is AoCWebInterface.Verdict.WithWait) {
83+
val locked = now + verdict.wait.toJavaDuration()
84+
appendSubmitLog(
85+
aocPuzzle,
86+
"$nowText - $id - LOCKED until ${DateTimeFormatter.ISO_DATE_TIME.format(locked)}"
87+
)
88+
}
89+
}
90+
91+
class PreviousSubmitted(
92+
private val locked: LocalDateTime?,
93+
private val answers: List<String>,
94+
private val log: List<String>,
95+
) {
96+
operator fun contains(answer: String) = answer in answers
97+
fun isNotEmpty() = answers.isNotEmpty()
98+
99+
override fun toString() = (
100+
listOf(
101+
brightMagenta("Previously submitted answers were:"),
102+
"${log.size} attempts in total".takeIf { log.size > 3 }
103+
) +
104+
log.takeLast(3).map { it.highlight() } +
105+
lockInfo()
106+
)
107+
.filterNotNull()
108+
.joinToString("\n", postfix = "\n ")
109+
110+
private fun String.highlight() =
111+
split("\"", limit = 3).mapIndexed { index, s -> if (index == 1) brightMagenta(s) else s }.joinToString("")
112+
113+
private fun lockInfo() = locked?.let {
114+
if (isStillLocked)
115+
brightRed("Locked until $it")
116+
else
117+
yellow("Had been locked, but is free again!")
118+
}
119+
120+
private val isStillLocked get() = locked?.let { LocalDateTime.now() < it } == true
121+
122+
val waitSecondsOrNull
123+
get() = locked?.let {
124+
val now = LocalDateTime.now()
125+
(it.toEpochSecond(ZoneOffset.UTC) - now.toEpochSecond(ZoneOffset.UTC))
126+
}.takeIf { (it ?: 0) > 0 }
127+
128+
fun waitUntilFree() {
129+
isStillLocked || return
130+
with(aocTerminal) {
131+
do {
132+
cursor.move { startOfLine();clearLine() }
133+
print(brightRed("Waiting $waitSecondsOrNull more seconds..."))
134+
Thread.sleep(500)
135+
} while (LocalDateTime.now() < locked!!)
136+
cursor.move { startOfLine(); clearLine() }
137+
println("Fire!")
138+
}
139+
}
140+
}
141+
142+
fun previouslySubmitted(aocPuzzle: AoCPuzzle, part: Part): PreviousSubmitted =
143+
readSubmitLog(aocPuzzle)
144+
.filter { idFor(aocPuzzle, part) in it }
145+
.let { relevant ->
146+
val answers = relevant
147+
.filter { "submitted" in it }
148+
.mapNotNull { log ->
149+
log.split("\"").getOrNull(1)?.let { it to log }
150+
}
151+
152+
val locked = if ("LOCKED" in relevant.lastOrNull().orEmpty()) {
153+
val lock = relevant.last().substringAfter("until ")
154+
LocalDateTime.from(DateTimeFormatter.ISO_DATE_TIME.parse(lock))
155+
} else null
156+
157+
PreviousSubmitted(locked, answers.map { it.first }, answers.map { it.second })
158+
}
159+
160+
private fun idFor(aocPuzzle: AoCPuzzle, part: Part) =
161+
"${aocPuzzle.year} day ${aocPuzzle.day} part $part"
162+
163+
private fun getSessionCookie() =
164+
this.session
165+
?: System.getenv("AOC_COOKIE")
166+
?: object {}.javaClass.getResource("session-cookie")
167+
?.readText()
168+
?.lines()
169+
?.firstOrNull { it.isNotBlank() }
170+
?: warn("No session cookie in environment or file found, will not be able to talk to AoC server.")
171+
172+
private fun readInputFileOrNull(aocPuzzle: AoCPuzzle): List<String>? {
173+
val file = File(fileNameFor(aocPuzzle))
174+
file.exists() || return null
175+
return file.readLines()
176+
}
177+
178+
private fun writeInputFile(aocPuzzle: AoCPuzzle, content: List<String>) {
179+
File(pathNameForYear(aocPuzzle)).mkdirs()
180+
File(fileNameFor(aocPuzzle)).writeText(content.joinToString("\n"))
181+
}
182+
183+
private fun readSubmitLog(aocPuzzle: AoCPuzzle): List<String> {
184+
val file = File(submitLogFor(aocPuzzle))
185+
file.exists() || return emptyList()
186+
return file.readLines()
187+
}
188+
189+
private fun pathNameForYear(aocPuzzle: AoCPuzzle): String {
190+
return "puzzles/${aocPuzzle.year}"
191+
}
192+
193+
private fun fileNameFor(aocPuzzle: AoCPuzzle): String {
194+
return "${pathNameForYear(aocPuzzle)}/day${"%02d".format(aocPuzzle.day)}.txt"
195+
}
196+
197+
private fun submitLogFor(aocPuzzle: AoCPuzzle): String {
198+
return "${pathNameForYear(aocPuzzle)}/submit.log"
199+
}
200+
201+
private fun appendSubmitLog(aocPuzzle: AoCPuzzle, content: String) {
202+
File(submitLogFor(aocPuzzle)).appendText("\n$content")
42203
}
43204
}
44205

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

@@ -97,8 +259,11 @@ data class TestData(val input: String, val expectedPart1: Any?, val expectedPart
97259
println(gray("Checking part $part against $expectation..."))
98260
val actual = partFun()
99261
val match = actual == Day.NotYetImplemented || "$actual" == expectation
100-
if (!match) {
101-
aocTerminal.danger("Checking of part $part failed")
262+
if (match) {
263+
aocTerminal.success("✅ Test succeeded.")
264+
}
265+
else {
266+
aocTerminal.danger("❌ Checking of part $part failed")
102267
println("Expected: ${brightRed(expectation)}")
103268
println(" Actual: ${brightRed("$actual")}")
104269
println(yellow("Check demo ${TextStyles.bold("input")} and demo ${TextStyles.bold("expectation")}!"))
@@ -201,166 +366,6 @@ fun Any?.restrictWidth(minWidth: Int, maxWidth: Int) = with("$this") {
201366
}
202367
}
203368

204-
object AoC {
205-
206-
private val web = AoCWebInterface(getSessionCookie())
207-
208-
fun sendToClipboard(a: Any?): Boolean {
209-
if (a in listOf(null, 0, -1, Day.NotYetImplemented)) return false
210-
return runCatching {
211-
val clipboard: Clipboard = Toolkit.getDefaultToolkit().systemClipboard
212-
val transferable: Transferable = StringSelection(a.toString())
213-
clipboard.setContents(transferable, null)
214-
}.isSuccess
215-
}
216-
217-
fun getPuzzleInput(aocPuzzle: AoCPuzzle): List<String> {
218-
val cached = readInputFileOrNull(aocPuzzle)
219-
if (!cached.isNullOrEmpty()) return cached
220-
221-
return web.downloadInput(aocPuzzle).onSuccess {
222-
writeInputFile(aocPuzzle, it)
223-
}.getOrElse {
224-
listOf("Unable to download ${aocPuzzle}: $it")
225-
}
226-
}
227-
228-
fun submitAnswer(aocPuzzle: AoCPuzzle, part: Part, answer: String): AoCWebInterface.Verdict =
229-
web.submitAnswer(aocPuzzle, part, answer)
230-
231-
private val logFormat = DateTimeFormatter.ofPattern("HH:mm:ss")
232-
233-
fun appendSubmitLog(aocPuzzle: AoCPuzzle, part: Part, answer: String, verdict: AoCWebInterface.Verdict) {
234-
val now = LocalDateTime.now()
235-
val nowText = logFormat.format(now)
236-
val id = idFor(aocPuzzle, part)
237-
val text =
238-
"$nowText - $id - submitted \"$answer\" - ${if (verdict is AoCWebInterface.Verdict.Correct) "OK" else "FAIL with ${verdict::class.simpleName}"}"
239-
appendSubmitLog(aocPuzzle, text)
240-
appendSubmitLog(aocPuzzle, verdict.text)
241-
if (verdict is AoCWebInterface.Verdict.WithWait) {
242-
val locked = now + verdict.wait.toJavaDuration()
243-
appendSubmitLog(
244-
aocPuzzle,
245-
"$nowText - $id - LOCKED until ${DateTimeFormatter.ISO_DATE_TIME.format(locked)}"
246-
)
247-
}
248-
}
249-
250-
class PreviousSubmitted(
251-
private val locked: LocalDateTime?,
252-
private val answers: List<String>,
253-
private val log: List<String>,
254-
) {
255-
operator fun contains(answer: String) = answer in answers
256-
fun isNotEmpty() = answers.isNotEmpty()
257-
258-
override fun toString() = (
259-
listOf(
260-
brightMagenta("Previously submitted answers were:"),
261-
"${log.size} attempts in total".takeIf { log.size > 3 }
262-
) +
263-
log.takeLast(3).map { it.highlight() } +
264-
lockInfo()
265-
)
266-
.filterNotNull()
267-
.joinToString("\n", postfix = "\n ")
268-
269-
private fun String.highlight() =
270-
split("\"", limit = 3).mapIndexed { index, s -> if (index == 1) brightMagenta(s) else s }.joinToString("")
271-
272-
private fun lockInfo() = locked?.let {
273-
if (isStillLocked)
274-
brightRed("Locked until $it")
275-
else
276-
yellow("Had been locked, but is free again!")
277-
}
278-
279-
private val isStillLocked get() = locked?.let { LocalDateTime.now() < it } == true
280-
281-
val waitSecondsOrNull
282-
get() = locked?.let {
283-
val now = LocalDateTime.now()
284-
(it.toEpochSecond(ZoneOffset.UTC) - now.toEpochSecond(ZoneOffset.UTC))
285-
}.takeIf { (it ?: 0) > 0 }
286-
287-
fun waitUntilFree() {
288-
isStillLocked || return
289-
with(aocTerminal) {
290-
do {
291-
cursor.move { startOfLine();clearLine() }
292-
print(brightRed("Waiting $waitSecondsOrNull more seconds..."))
293-
Thread.sleep(500)
294-
} while (LocalDateTime.now() < locked!!)
295-
cursor.move { startOfLine(); clearLine() }
296-
println("Fire!")
297-
}
298-
}
299-
}
300-
301-
fun previouslySubmitted(aocPuzzle: AoCPuzzle, part: Part): PreviousSubmitted =
302-
readSubmitLog(aocPuzzle)
303-
.filter { idFor(aocPuzzle, part) in it }
304-
.let { relevant ->
305-
val answers = relevant
306-
.filter { "submitted" in it }
307-
.mapNotNull { log ->
308-
log.split("\"").getOrNull(1)?.let { it to log }
309-
}
310-
311-
val locked = if ("LOCKED" in relevant.lastOrNull().orEmpty()) {
312-
val lock = relevant.last().substringAfter("until ")
313-
LocalDateTime.from(DateTimeFormatter.ISO_DATE_TIME.parse(lock))
314-
} else null
315-
316-
PreviousSubmitted(locked, answers.map { it.first }, answers.map { it.second })
317-
}
318-
319-
private fun idFor(aocPuzzle: AoCPuzzle, part: Part) =
320-
"${aocPuzzle.year} day ${aocPuzzle.day} part $part"
321-
322-
private fun getSessionCookie() =
323-
System.getenv("AOC_COOKIE")
324-
?: object {}.javaClass.getResource("session-cookie")
325-
?.readText()
326-
?.lines()
327-
?.firstOrNull { it.isNotBlank() }
328-
?: warn("No session cookie in environment or file found, will not be able to talk to AoC server.")
329-
330-
private fun readInputFileOrNull(aocPuzzle: AoCPuzzle): List<String>? {
331-
val file = File(fileNameFor(aocPuzzle))
332-
file.exists() || return null
333-
return file.readLines()
334-
}
335-
336-
private fun writeInputFile(aocPuzzle: AoCPuzzle, content: List<String>) {
337-
File(pathNameForYear(aocPuzzle)).mkdirs()
338-
File(fileNameFor(aocPuzzle)).writeText(content.joinToString("\n"))
339-
}
340-
341-
private fun readSubmitLog(aocPuzzle: AoCPuzzle): List<String> {
342-
val file = File(submitLogFor(aocPuzzle))
343-
file.exists() || return emptyList()
344-
return file.readLines()
345-
}
346-
347-
private fun pathNameForYear(aocPuzzle: AoCPuzzle): String {
348-
return "puzzles/${aocPuzzle.year}"
349-
}
350-
351-
private fun fileNameFor(aocPuzzle: AoCPuzzle): String {
352-
return "${pathNameForYear(aocPuzzle)}/day${"%02d".format(aocPuzzle.day)}.txt"
353-
}
354-
355-
private fun submitLogFor(aocPuzzle: AoCPuzzle): String {
356-
return "${pathNameForYear(aocPuzzle)}/submit.log"
357-
}
358-
359-
private fun appendSubmitLog(aocPuzzle: AoCPuzzle, content: String) {
360-
File(submitLogFor(aocPuzzle)).appendText("\n$content")
361-
}
362-
}
363-
364369
@Suppress("NOTHING_TO_INLINE")
365370
inline fun useSystemProxies() {
366371
System.setProperty("java.net.useSystemProxies", "true")

0 commit comments

Comments
 (0)