aoc-copilot
Version:
Advent of Code automatic runner for examples and inputs
328 lines • 14.8 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Incorrect = exports.cookieSteps = void 0;
exports.getInput = getInput;
exports.getLeaderboard = getLeaderboard;
exports.getPuzzle = getPuzzle;
exports.hms = hms;
exports.isNumChar = isNumChar;
exports.sleep = sleep;
exports.submitAnswer = submitAnswer;
exports.validateYearDay = validateYearDay;
const node_querystring_1 = require("node:querystring");
const promises_1 = require("node:readline/promises");
const cheerio_1 = require("cheerio");
const cache_1 = require("./cache");
const httpsPromisfied_1 = require("./httpsPromisfied");
Object.defineProperty(exports, "cookieSteps", { enumerable: true, get: function () { return httpsPromisfied_1.cookieSteps; } });
const stats = __importStar(require("./stats"));
class Incorrect extends Error {
name;
constructor(message) {
super(message);
this.name = 'Incorrect';
}
}
exports.Incorrect = Incorrect;
function isNumChar(ne) {
return String(ne).split('').every(c => '0123456789'.includes(c));
}
async function validateYearDay(year, day) {
const drop = new Date(`${day} Dec ${year} 00:00:00 EST`);
if (!isNumChar(year) ||
!isNumChar(day) ||
year < 2015 ||
day < 1 ||
day > 25) {
throw new Error(`Invalid year/day ("${year}"/"${day}")`);
}
else if (drop.valueOf() > Date.now()) {
console.log(`\nNeed to wait until puzzle drops on ${drop}`);
await countdown(drop);
}
}
async function getInput(year, day, forceRefresh = false) {
await validateYearDay(year, day);
let inputs = [];
try {
if (forceRefresh)
throw new Error('Force refresh');
const input = await (0, cache_1.read)(`${year}/inputs/${day}`);
inputs = input.split('\n');
while (inputs.at(-1) === '')
inputs.pop();
}
catch (err) {
const input = await (0, httpsPromisfied_1.request)('GET', `/${year}/day/${day}/input`, process.env.AOC_SESSION_COOKIE, process.env.CERTIFICATE);
if (input === 'Puzzle inputs differ by user. Please log in to get your puzzle input.\n')
throw new Error(httpsPromisfied_1.errExpiredSessionCookie);
inputs = input.split('\n');
while (inputs.at(-1) === '')
inputs.pop();
await (0, cache_1.write)(`${year}/inputs/${day}`, inputs.join('\n'));
}
return inputs;
}
async function getLeaderboard(year, id, refreshIfPossible = false) {
await validateYearDay(year, 1);
let isPossible = false;
if (refreshIfPossible) {
try {
const elapsed = Date.now() - parseInt(await (0, cache_1.read)(`lastLeaderboardRequest.json`));
if (elapsed >= 900000)
isPossible = true;
}
catch (err) {
isPossible = true;
}
}
let json = '';
try {
if (refreshIfPossible && isPossible)
throw new Error('Force refresh');
json = await (0, cache_1.read)(`${year}/${id}.json`);
}
catch (err) {
json = await (0, httpsPromisfied_1.request)('GET', `/${year}/leaderboard/private/view/${id}.json`, process.env.AOC_SESSION_COOKIE, process.env.CERTIFICATE);
await (0, cache_1.write)(`${year}/${id}.json`, json);
await (0, cache_1.write)(`lastLeaderboardRequest.json`, Date.now().toString());
}
return JSON.parse(json);
}
async function getPuzzle(year, day, forceRefresh = false) {
const no_http = process.env.NO_HTTP?.toLowerCase() ?? 'false';
await validateYearDay(year, day);
let puzzle = '';
try {
if (forceRefresh)
throw new Error('Force refresh');
puzzle = await (0, cache_1.read)(`${year}/puzzles/${day}.html`);
}
catch (err) {
puzzle = await (0, httpsPromisfied_1.request)('GET', `/${year}/day/${day}`, process.env.AOC_SESSION_COOKIE, process.env.CERTIFICATE);
const $ = (0, cheerio_1.load)(puzzle);
const login = $(`a[href="/${year}/auth/login"]`);
if (login.length > 0)
throw new Error(httpsPromisfied_1.errExpiredSessionCookie);
await (0, cache_1.write)(`${year}/puzzles/${day}.html`, puzzle);
await stats.startPart1(year, day);
}
// Get stylesheet for preview
const [sitev] = (puzzle.match(/(?<=style.css\?)\d+/g) ?? ['']);
const cachev = await (async () => {
try {
return await (0, cache_1.read)('/static/version');
}
catch {
return '';
}
})();
if (cachev != sitev && !no_http) { // Get CSS if it doesn't exist or is an older version
const css = await (0, httpsPromisfied_1.request)('GET', `/static/style.css?${sitev}`, process.env.AOC_SESSION_COOKIE, process.env.CERTIFICATE);
await (0, cache_1.write)(`static/style.css`, css);
await (0, cache_1.write)(`static/version`, sitev);
}
return puzzle;
}
function ms(wait) {
// Observed wait times:
// After 1st guess: one minute
// After 3rd guess: 5 minutes
// After 7th guess: 10 minutes
// After 11th guess: 15 minutes
let unit = '';
let time = 0;
wait.split(' ').reverse().forEach((e, i) => {
if (i % 2 === 0)
unit = e;
else {
const n = e === 'one' ? 1 : parseInt(e);
if (unit.includes('minute'))
time += n * 60 * 1000;
else if (unit.includes('hour'))
time += n * 60 * 60 * 1000;
else if (unit.includes('day'))
time += n * 24 * 60 * 60 * 1000;
}
});
return time;
}
function sleep(ms = 1000) {
return new Promise((resolve, reject) => {
setTimeout(resolve, ms);
});
}
function hms(ms) {
let hrs = Math.floor(ms / 1000 / 60 / 60);
ms -= hrs * 60 * 60 * 1000;
let mins = Math.floor(ms / 1000 / 60);
ms -= mins * 60 * 1000;
let secs = Math.floor(ms / 1000);
return `${hrs.toFixed(0).padStart(2, '0')}:${mins.toFixed(0).padStart(2, '0')}:${secs.toFixed(0).padStart(2, '0')}`;
}
async function countdown(until) {
let remaining = until.getTime() - Date.now();
if (remaining > 0) {
console.log();
while (remaining > 0) {
process.stdout.write(`\rWaiting \x1b[101m${hms(remaining)}\x1b[0m... `);
await sleep(remaining > 1000 ? 1000 : remaining);
remaining = until.getTime() - Date.now();
}
console.log('\rWaiting \x1b[102m00:00:00\x1b[0m... Done');
}
}
async function submitAnswer(year, day, part, answer, yes = false) {
await validateYearDay(year, day);
// Get answers file
const filename = `${year}/answers/${day}.json`;
let answers;
try {
answers = JSON.parse(await (0, cache_1.read)(filename));
}
catch (err) {
answers = [];
}
const partAnswers = answers.filter(a => a.part === part);
// Check against previously submitted answers
const correct = partAnswers.find(a => a.correct);
if (correct)
throw new Error(`Already submitted correct answer ${correct.answer} for ${year} day ${day} part ${part} on ${correct.timestamp}`);
const duplicate = partAnswers.find(a => a.answer === answer.toString());
if (duplicate) {
await stats.avoidedAttempt(year, day, part);
throw new Incorrect(`Already submitted ${duplicate.problem ?? 'incorrect'} answer ${duplicate.answer} for ${year} day ${day} part ${part} on ${duplicate.timestamp}`);
}
if (typeof answer === 'number' || typeof answer === 'bigint') {
const tooLows = partAnswers.filter(a => a.problem === 'too low').sort((a, b) => { const d = BigInt(b.answer) - BigInt(a.answer); return d < 0n ? -1 : d === 0n ? 0 : 1; });
if (tooLows.length > 0 && answer < BigInt(tooLows[0].answer)) {
await stats.avoidedAttempt(year, day, part);
throw new Incorrect(`${answer} is too low because it's less than ${tooLows[0].answer} which was too low for ${year} day ${day} part ${part} on ${tooLows[0].timestamp}`);
}
const tooHighs = partAnswers.filter(a => a.problem === 'too high').sort((a, b) => { const d = BigInt(a.answer) - BigInt(b.answer); return d < 0n ? -1 : d === 0n ? 0 : 1; });
if (tooHighs.length > 0 && answer > BigInt(tooHighs[0].answer)) {
await stats.avoidedAttempt(year, day, part);
throw new Incorrect(`${answer} is too high because it's greater than ${tooHighs[0].answer} which was too high for ${year} day ${day} part ${part} on ${tooHighs[0].timestamp}`);
}
}
// Prompt user to confirm submission
const prompt = `\nSubmit ${year} day ${day} part ${part} answer ${answer} (y/N)? `;
if (yes)
console.log(prompt, 'y');
else {
const rl = (0, promises_1.createInterface)({
input: process.stdin,
output: process.stdout,
});
const userInput = await rl.question(prompt);
rl.close();
if (userInput.toLowerCase() != 'y')
return { cancelled: true };
}
let lastSubmission;
try {
lastSubmission = JSON.parse(await (0, cache_1.read)('lastSubmission.json'));
if (!lastSubmission.correct) {
const lastTs = new Date(lastSubmission.timestamp);
const until = new Date(lastTs.getTime() + ms(lastSubmission.wait));
if (until.getTime() > Date.now()) {
console.log(`\nRate limiting on the adventofcode.com site requires waiting a variable amount of time after submitting an incorrect ` +
`answer. You submitted incorrect answer "${lastSubmission.answer}" for ${lastSubmission.year} day ${lastSubmission.day} part ` +
`${lastSubmission.part} on ${lastTs.toString()}, requiring you to wait ${lastSubmission.wait} until ${until.toString()} ` +
`before submitting another answer for any puzzle.`);
await countdown(until);
}
}
}
catch (error) {
lastSubmission = { year, day, part, answer: '', correct: false, timestamp: '', wait: '' };
}
// Submit the answer
const answerStr = typeof answer === 'string' ? answer : answer.toString(10);
const path = `/${year}/day/${day}/answer`;
const formData = (0, node_querystring_1.encode)({
level: part,
answer: answerStr
});
let response = await (0, httpsPromisfied_1.request)('POST', path, process.env.AOC_SESSION_COOKIE, process.env.CERTIFICATE, formData);
let timestamp = new Date().toJSON();
await (0, cache_1.write)(`${year}/lastPOSTResponse.html`, response);
let $ = (0, cheerio_1.load)(response);
// Check if answer was given too soon
const tooSoon = 'You gave an answer too recently';
if ($(`p:contains('${tooSoon}')`).length > 0) {
const msg = $(`p:contains('${tooSoon}')`).text();
console.log(msg);
// E.g. "You gave an answer too recently; you have to wait after submitting an answer before trying again. You have 14m 59s left to wait. [Return to Day 7]"
const match = msg.match(/\d+(?=d|h|m|s)/gm);
if (match) {
const [s, m = 0, h = 0, d = 0] = match.reverse().map(e => parseInt(e)); // Does it ever go beyond minutes? Who knows!
await countdown(new Date(Date.now() + (s + m * 60 + h * 60 * 60 + d * 24 * 60 * 60) * 1000));
// Resubmit
response = await (0, httpsPromisfied_1.request)('POST', path, process.env.AOC_SESSION_COOKIE, process.env.CERTIFICATE, formData);
timestamp = new Date().toJSON();
$ = (0, cheerio_1.load)(response);
}
else {
throw new Error(msg);
}
}
// Check for correct answer
const alreadySolved = "You don't seem to be solving the right level";
// Correct answer
if ($('span.day-success').length > 0) {
await (0, cache_1.write)('lastSubmission.json', JSON.stringify({ year, day, part, answer: answer.toString(), correct: true, timestamp, wait: '' }));
answers.push({ part, answer: answer.toString(), correct: true, timestamp });
await (0, cache_1.write)(filename, JSON.stringify(answers));
await getPuzzle(year, day, true);
const dayStats = await stats.finish(year, day, part);
return { cancelled: false, response, dayStats };
}
else if ($(`p:contains('${alreadySolved}')`).length > 0) {
// Solving wrong level - happens due to a sync issue e.g. when the player submits an answer through the website directly unbeknownst to AoCC
await getPuzzle(year, day, true);
await stats.solvedElsewhere(year, day, part);
throw new Error($(`p:contains('${alreadySolved}')`).text());
}
const { problem, wait } = /That's not the right answer(\.|; your answer is (?<problem>.*?)\.).*lease wait (?<wait>(one|\d+) minutes?)/g.exec($('article p').text())?.groups;
await (0, cache_1.write)('lastSubmission.json', JSON.stringify({ year, day, part, answer: answer.toString(), correct: false, timestamp, problem, wait }));
answers.push({ part, answer: answer.toString(), correct: false, timestamp, problem, wait });
await (0, cache_1.write)(filename, JSON.stringify(answers));
await stats.incorrectAttempt(year, day, part);
throw new Incorrect($('article p').text());
}
;
//# sourceMappingURL=site.js.map