UNPKG

aoc-automation

Version:

Advent of Code tool to automate the repetitive parts of AoC.

298 lines (297 loc) 10.9 kB
import fetch from "node-fetch"; import { JSDOM } from "jsdom"; import { readFileSync, writeFileSync, existsSync, statSync } from "fs"; import kleur from "kleur"; const USER_AGENT_HEADER = { "User-Agent": "github.com/terryaney/aoc-automation by terry.aney@icloud.com" }; const strToNum = (time) => { const entries = { one: 1, two: 2, three: 3, four: 4, five: 5, six: 6, seven: 7, eight: 8, nine: 9, ten: 10 }; return entries[time] || NaN; }; let canSubmit = true; let delayStart = 0; let delayAmount = 0; var Status = /* @__PURE__ */ ((Status2) => { Status2[Status2["SOLVED"] = 0] = "SOLVED"; Status2[Status2["WRONG"] = 1] = "WRONG"; Status2[Status2["ERROR"] = 2] = "ERROR"; return Status2; })(Status || {}); const timeToReadable = (d, h, m, s) => { return (d !== 0 ? `${d}d ` : "") + (h !== 0 ? `${h}h ` : "") + (m !== 0 ? `${m}m ` : "") + (s !== 0 ? `${s}s ` : ""); }; const msToReadable = (ms) => { const msSecond = 1e3; const msMinute = 60 * msSecond; const msHour = 60 * msMinute; const msDay = 24 * msHour; const d = Math.floor(ms / msDay); const h = Math.floor((ms - msDay * d) / msHour); const m = Math.floor((ms - msDay * d - msHour * h) / msMinute); const s = Math.floor( (ms - msDay * d - msHour * h - msMinute * m) / msSecond ); return timeToReadable(d, h, m, s); }; const handleErrors = (e) => { if (e.message === "400" || e.message === "500") { console.log( kleur.red("INVALID SESSION KEY\n\n") + "Please make sure that the session key in the .env file is correct.\nYou can find your session key in the 'session' cookie at:\nhttps://adventofcode.com\n\n" + kleur.bold( "Restart the script after changing the .env file.\n" ) ); } else if (e.message.startsWith("5")) { console.log(kleur.red("SERVER ERROR")); } else if (e.message === "404") { console.log(kleur.yellow("CHALLENGE NOT YET AVAILABLE")); } else { console.log( kleur.red( "UNEXPECTED ERROR\nPlease check your internet connection.\n\nIf you think it's a bug, create an issue on github.\nHere are some details to include:\n" ) ); console.log(e); } return 2 /* ERROR */; }; const getPuzzleInfo = async (year, day) => { const API_URL = process.env.AOC_API ?? "https://adventofcode.com"; try { const res = await fetch(`${API_URL}/${year}/day/${day}`, { headers: { cookie: `session=${process.env.AOC_SESSION_KEY}`, ...USER_AGENT_HEADER } }); if (res.status !== 200) { throw new Error(String(res.status)); } let part1 = await res.text(); let matches = part1.match(/<h2>--- Day \d+: (.*?) ---<\/h2>/); const title = matches ? matches[1] : null; const starParts = part1.split("--- Part Two ---"); part1 = starParts[0]; const part2 = starParts.length == 2 ? starParts[1] : void 0; matches = part1.match(/<pre><code>(.*?)<\/code><\/pre>/s); const testData = matches ? matches[1].trim() : null; matches = part1.match(/<code><em>(.*?)<\/em><\/code>/gs); const expected = matches ? matches[matches.length - 1].match(/<em>(.*?)<\/em>/)[1].trim() : "0"; let testData2 = null; let expected2 = null; if (part2 != void 0) { matches = part2.match(/<pre><code>(.*?)<\/code><\/pre>/s); testData2 = matches ? matches[1].trim() : testData; matches = part2.match(/<code><em>(.*?)<\/em><\/code>/gs); expected2 = matches ? matches[matches.length - 1].match(/<em>(.*?)<\/em>/)[1].trim() : "0"; } return [title, testData, expected, testData2, expected2]; } catch (error) { handleErrors(error); return [null, null, null, null, null]; } }; const getInput = async (year, day, inputFilePath, dayIndexFilePath, puzzleInfo) => { const API_URL = process.env.AOC_API ?? "https://adventofcode.com"; if (existsSync(inputFilePath) && statSync(inputFilePath).size > 0) { console.log(kleur.yellow(`INPUT DATA FOR AOC ${year} DAY ${day} ALREADY FETCHED`)); } else { try { const res = await fetch(`${API_URL}/${year}/day/${day}/input`, { headers: { cookie: `session=${process.env.AOC_SESSION_KEY}`, ...USER_AGENT_HEADER } }); if (res.status !== 200) { throw new Error(String(res.status)); } const body = await res.text(); writeFileSync(inputFilePath, body.replace(/\n$/, "")); console.log(kleur.green(`INPUT DATA FOR AOC ${year} DAY ${day} SAVED!`)); } catch (error) { handleErrors(error); } } if (puzzleInfo != void 0) { const [_, testData1, expected1, testData2, expected2] = puzzleInfo; if (testData1 == null) { console.log(kleur.yellow(`TEST CASE DATA FOR AOC ${year} DAY ${day} NOT AVAILABLE`)); } else { if (existsSync(dayIndexFilePath)) { let dayIndexContent = readFileSync(dayIndexFilePath).toString(); if (dayIndexContent.indexOf("{testData") == -1 && dayIndexContent.indexOf("{expected") == -1) { console.log(kleur.yellow(`TEST CASES FOR AOC ${year} DAY ${day} ALREADY INSERTED`)); } else { let saveFile = false; let regex = /([ \t]*)\{testData\}/; let match = dayIndexContent.match(regex); let testCaseInserted = false; if (match) { const indent = match[1]; const testDataIndented = testData1.split("\n").filter((l) => l != "").map((line) => `${indent}${line}`).join("\n"); dayIndexContent = dayIndexContent.replace( regex, testDataIndented ); saveFile = true; testCaseInserted = true; } if (expected1 != null) { regex = /"\{expected\}"/; match = dayIndexContent.match(regex); if (match) { dayIndexContent = dayIndexContent.replace(regex, expected1); saveFile = true; testCaseInserted = true; } } if (!testCaseInserted) { console.log(kleur.yellow(`TEST CASE FOR AOC ${year} DAY ${day} PART 1 ALREADY INSERTED`)); } else { console.log(kleur.green(`TEST CASE FOR AOC ${year} DAY ${day} PART 1 HAS BEEN INSERTED!`)); } testCaseInserted = testData2 == null && expected2 == null; if (testData2 != null) { regex = /([ \t]*)\{testDataPending\}/; match = dayIndexContent.match(regex); if (match) { const indent = match[1]; const testDataIndented = testData2.split("\n").filter((l) => l != "").map((line) => `${indent}${line}`).join("\n"); dayIndexContent = dayIndexContent.replace( regex, testDataIndented ); dayIndexContent = dayIndexContent.replace("testsPending:", "tests:"); saveFile = true; testCaseInserted = true; } } if (expected2 != null) { regex = /"\{expectedPending\}"/; match = dayIndexContent.match(regex); if (match) { dayIndexContent = dayIndexContent.replace(regex, expected2); saveFile = true; testCaseInserted = true; } } if (!testCaseInserted) { console.log(kleur.yellow(`TEST CASE FOR AOC ${year} DAY ${day} PART 2 ALREADY INSERTED`)); } else if (testData2 != null || expected2 != null) { console.log(kleur.green(`TEST CASE FOR AOC ${year} DAY ${day} PART 2 HAS BEEN INSERTED!`)); } if (saveFile) { if (testCaseInserted) { dayIndexContent = dayIndexContent.replace("1 == 1 ? 0 : solve(rawInput, false, testName);", "solve(rawInput, false, testName);"); } writeFileSync(dayIndexFilePath, dayIndexContent); } } } } } }; const sendSolution = (year, day, part, solution) => { const API_URL = process.env.AOC_API ?? "https://adventofcode.com"; if (!canSubmit) { const now = Date.now(); const remainingMs = delayAmount - (now - delayStart); if (remainingMs <= 0) { canSubmit = true; } else { console.log( kleur.red(`You have to wait: ${msToReadable(remainingMs)}`) ); return Promise.resolve(2 /* ERROR */); } } return fetch(`${API_URL}/${year}/day/${day}/answer`, { headers: { cookie: `session=${process.env.AOC_SESSION_KEY}`, "content-type": "application/x-www-form-urlencoded", ...USER_AGENT_HEADER }, method: "POST", body: `level=${part}&answer=${encodeURIComponent(solution)}` }).then((res) => { if (res.status !== 200) { throw new Error(String(res.status)); } return res.text(); }).then((body) => { const $main = new JSDOM(body).window.document.querySelector("main"); let status = 2 /* ERROR */; const info = $main !== null ? $main.textContent.replace(/\[.*\]/, "").trim() : "Can't find the main element"; if (info.includes("That's the right answer")) { console.log(`Status`, kleur.green(`PART ${part} SOLVED!`)); return 0 /* SOLVED */; } else if (info.includes("That's not the right answer")) { console.log("Status:", kleur.red("WRONG ANSWER")); console.log(` ${info} `); status = 1 /* WRONG */; } else if (info.includes("You gave an answer too recently")) { console.log("Status:", kleur.yellow("TO SOON")); } else if (info.includes("You don't seem to be solving the right level")) { console.log( "Status:", kleur.yellow("ALREADY COMPLETED or LOCKED") ); } else { console.log("Status:", kleur.red("UNKNOWN RESPONSE\n")); console.log(` ${info} `); } const waitStr = info.match( /(one|two|three|four|five|six|seven|eight|nine|ten) (second|minute|hour|day)/ ); const waitNum = info.match(/\d+\s*(s|m|h|d)/g); if (waitStr !== null || waitNum !== null) { const waitTime = { s: 0, m: 0, h: 0, d: 0 }; if (waitStr !== null) { const [_, time, unit] = waitStr; waitTime[unit[0]] = strToNum(time); } else if (waitNum !== null) { waitNum.forEach((x) => { waitTime[x.slice(-1)] = Number(x.slice(0, -1)); }); } canSubmit = false; delayStart = Date.now(); delayAmount = (waitTime.d * 24 * 60 * 60 + waitTime.h * 60 * 60 + waitTime.m * 60 + waitTime.s) * 1e3; const delayStr = timeToReadable( waitTime.d, waitTime.h, waitTime.m, waitTime.s ); console.log(`Next request possible in: ${delayStr}`); } return status; }).catch(handleErrors); }; export { Status, getInput, getPuzzleInfo, sendSolution };