UNPKG

aocjs

Version:

Advent of Code API client.

244 lines (237 loc) 7.95 kB
'use strict'; const defu = require('defu'); function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; } const defu__default = /*#__PURE__*/_interopDefaultCompat(defu); var __defProp$1 = Object.defineProperty; var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField$1 = (obj, key, value) => { __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class ClientError extends Error { constructor(message) { super(message); this.name = "AOCClientError"; } } class AuthenticationError extends ClientError { constructor(message = "Authentication failed") { super(message); this.name = "AuthenticationError"; } } class NetworkError extends ClientError { constructor(message, status, statusText) { super(message); __publicField$1(this, "status"); __publicField$1(this, "statusText"); this.name = "NetworkError"; this.status = status; this.statusText = statusText; } } class LeaderboardError extends ClientError { constructor(message) { super(message); this.name = "LeaderboardError"; } } class SubmissionError extends ClientError { constructor(message) { super(message); this.name = "SubmissionError"; } } var __defProp = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; class Client { constructor(options) { __publicField(this, "session"); __publicField(this, "user-agent", "aocjs (https://npmjs.com/package/aocjs)"); __publicField(this, "logger"); this.session = options.session; if (options["user-agent"]) this["user-agent"] = options["user-agent"]; this.logger = options.logger || console.error; } /** * Internal fetcher with improved error handling */ async fetcher(path, _options) { if (!this.session) { throw new AuthenticationError("Session cookie is required"); } const options = defu__default(_options, { headers: { Cookie: `session=${this.session}`, "User-Agent": this["user-agent"] } }); try { const request = await fetch(`https://adventofcode.com/${path}`, options); if (!request.ok) { const errorMessage = `Network request failed: ${request.status} ${request.statusText}`; this.logger(errorMessage); throw new NetworkError(errorMessage, request.status, request.statusText); } return request; } catch (error) { if (error instanceof NetworkError) throw error; this.logger( `Fetch error: ${error instanceof Error ? error.message : String(error)}` ); throw new NetworkError( "Network request failed", void 0, error instanceof Error ? error.message : void 0 ); } } /** * Get a puzzle's input. * * @param year Advent of Code year. * @param day Advent of Code year's puzzle day. */ async getInput(year, day) { try { return await (await this.fetcher(`${year}/day/${day}/input`)).text(); } catch (error) { this.logger( `Failed to get input for year ${year}, day ${day}: ${error instanceof Error ? error.message : String(error)}` ); throw error; } } /** * Gets your specified private leaderboard. * @param year Advent of Code year. * @param id Your leaderboard id. * @param [sorted=false] If true, returns a sorted array of leaderboard members by stars. */ async getLeaderboard(year, id, sorted) { try { const request = await this.fetcher( `${year}/leaderboard/private/view/${id}.json` ); if (request.status === 302) { throw new LeaderboardError( `Cannot access leaderboard: Year ${year}, ID ${id}. Does the leaderboard exist or do you have access?` ); } const data = await request.json(); if (sorted) { return Object.values(data.members).sort( (a, b) => b.stars - a.stars || b.local_score - a.local_score || b.global_score - a.global_score ); } return data; } catch (error) { this.logger( `Leaderboard retrieval failed: ${error instanceof Error ? error.message : String(error)}` ); throw error; } } /** * Get a puzzle's problem. * * @param year Advent of Code year. * @param day Advent of Code year's puzzle day. * @param raw If true, returns the raw HTML of the problem. */ async getProblem(year, day, raw) { try { const request = await this.fetcher(`${year}/day/${day}`); const html = await request.text(); if (raw) { return html; } return this.getMainElementHtml(html); } catch (error) { this.logger( `Failed to get problem for year ${year}, day ${day}: ${error instanceof Error ? error.message : String(error)}` ); throw error; } } /** * Get the main element HTML from a response. * * @param html Response HTML. */ getMainElementHtml(html) { const match = /<main\b[^>]*>(.*)<\/main>/s.exec(html); if (!match) { throw new ClientError("Could not find main element in response"); } return match[1]; } /** * Submits a solution to the server. * * @param year Advent of Code year. * @param day Advent of Code year's puzzle day. * @param part Part of the puzzle to submit. * @param solution Solution to the puzzle. */ async submit(year, day, part, solution) { try { const request = await this.fetcher(`${year}/day/${day}/answer`, { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ level: String(part), answer: String(solution) }) }); const response = this.getMainElementHtml(await request.text()); if (response.includes("That's the right answer!")) { return true; } if (response.includes("That's not the right answer")) { return false; } if (response.includes("You don't seem to be solving the right level.")) { const problem = await this.getProblem(year, day); const problemSplitByPart = problem.split("</article>"); let relevantProblemPart = problemSplitByPart[part]; if (!relevantProblemPart) { throw new SubmissionError("Could not find correct answer in page"); } relevantProblemPart = relevantProblemPart.split("<article")[0]; const match = /Your puzzle answer was <code>([^<]+)<\/code>/.exec( relevantProblemPart ); if (!match) { throw new SubmissionError("Could not find correct answer in page"); } const correctAnswer = match[1]; return String(correctAnswer) === solution; } if (response.includes("To play, please identify yourself")) { throw new AuthenticationError("Session cookie is invalid or not set"); } this.logger(`Unexpected submission response: ${JSON.stringify(response)}`); throw new SubmissionError("Could not parse submission response"); } catch (error) { this.logger( `Submission failed for year ${year}, day ${day}, part ${part}: ${error instanceof Error ? error.message : String(error)}` ); throw error; } } } exports.AuthenticationError = AuthenticationError; exports.Client = Client; exports.ClientError = ClientError; exports.LeaderboardError = LeaderboardError; exports.NetworkError = NetworkError; exports.SubmissionError = SubmissionError;