aoc-automation
Version:
Advent of Code tool to automate the repetitive parts of AoC.
298 lines (297 loc) • 10.9 kB
JavaScript
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
};