Skip to content

Instantly share code, notes, and snippets.

@scarf005
Last active July 15, 2023 11:50
Show Gist options
  • Save scarf005/b399147ad55ee917eeb08b75eef0ba89 to your computer and use it in GitHub Desktop.
Save scarf005/b399147ad55ee917eeb08b75eef0ba89 to your computer and use it in GitHub Desktop.
fetches advent of code inputs
import * as dotenv from "https://deno.land/[email protected]/dotenv/mod.ts"
import { basename } from "https://deno.land/[email protected]/path/mod.ts"
import { Command } from "https://deno.land/x/[email protected]/command/mod.ts"
import { z } from "https://deno.land/x/[email protected]/mod.ts"
import { asynciter } from "https://deno.land/x/[email protected]/mod.ts"
import { difference } from "https://deno.land/x/[email protected]/mod.ts"
import { match, P } from "npm:ts-pattern@^5.0.3"
const cookieKey = "AOC_SESSION"
const cookieSchema = z.string().min(1, "Session cannot be empty!")
const getCookie = async () => {
const env = Deno.env.get(cookieKey) ?? (await dotenv.load())[cookieKey]
return cookieSchema.parse(env)
}
type AuthedFetch = (input: RequestInfo | URL) => Promise<Response>
type MkAuthedFetch = (session: string) => AuthedFetch
/** Generate fetch with session cookie */
const authedFetch: MkAuthedFetch = (session) => (input) =>
fetch(input, { headers: { cookie: `session=${session}` } })
type GetInput = (fetch: AuthedFetch) => (year: number) => (day: number) => Promise<string>
/** Fetches AOC input */
const getInput: GetInput = (fetch) => (year) => async (day) =>
(await fetch(`https://adventofcode.com/${year}/day/${day}/input`)).text()
/** if it's before December 1, return previous year */
const getDefaultYear = () => {
const now = new Date()
const isDecember = now.getMonth() === 11
return now.getFullYear() - (isDecember ? 0 : 1)
}
/** How many days of inputs are there for a given year */
const countInputs = (year: number) => {
const now = new Date()
const { currentYear, currentMonth, currentDay } = {
currentYear: now.getFullYear(),
currentMonth: now.getMonth(),
currentDay: now.getDate(),
}
const countOngoing = () =>
match(currentMonth).with(11, () => Math.min(currentDay, 25)).otherwise(() => 0)
const length = match(year)
.with(P.number.lt(2015 /* when AOC started */), P.number.gt(currentYear), () => 0)
.with(currentYear, countOngoing)
.otherwise(() => 25)
return new Set(Array.from({ length }, (_, i) => i + 1))
}
const cachedInputs = async (path: string) => {
const result = await asynciter(Deno.readDir(path))
.filter((file) => file.isFile && file.name.endsWith(".txt"))
.map(({ name }) => parseInt(basename(name), 10))
.collect()
return new Set(result)
}
const main = () =>
new Command()
.name("fetch-aoc")
.description("Fetch Advent of Code inputs.")
.option("-y, --year <year:number>", "year to fetch", { default: getDefaultYear() })
.option("-E, --cookie <cookie:string>", "session cookie, defaults to AOC_SESSION env variable")
.option("--cache <path:string>", "path to cache inputs", { default: "./inputs" })
.action(async ({ year, cache, cookie = getCookie() }) => {
const cachePath = `${cache}/${year}`
const uncachedInputs = difference(countInputs(year), await cachedInputs(cachePath))
const size = uncachedInputs.size
if (size === 0) {
return console.log(`All inputs for ${year} are cached`)
}
console.log(`Fetching ${size} inputs for ${year}...`)
const query = getInput(authedFetch(await cookie))(year)
await asynciter([...uncachedInputs])
.concurrentUnorderedMap(async (day) => ({ day, input: await query(day) }))
.concurrentUnorderedMap(({ day, input }) =>
Deno.writeTextFile(`${cachePath}/${day}.txt`, input)
)
.collect()
})
if (import.meta.main) {
await main().parse(Deno.args)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment