([^.]*\.)/)?.[1] || s; +} + +function cleanQuestion(s) { + return s.match(/(?<=
Your puzzle answer was ).*?(?=<\/code>)/g) || [];
+}
+
+async function downloadContent(url, session, postPayload) {
+ const headers = { Cookie: `session=${session}` };
+ const options = { headers };
+ if (postPayload) {
+ Object.assign(options, {
+ body: postPayload,
+ method: 'POST',
+ });
+ Object.assign(options.headers, {
+ 'content-type': 'application/x-www-form-urlencoded',
+ });
+ }
+ const response = await fetch(url, options);
+ if (response.status >= 400) {
+ throw new Error(
+ [
+ `Failed to download from ${url} (${response.status})`,
+ `Description: ${cleanError(await response.text())}`,
+ ].join('\n'),
+ );
+ }
+ return await response.text();
+}
+
+async function getDayAnswer(year, day, session) {
+ const url = `https://adventofcode.com/${+year}/day/${+day}`;
+ return cleanQuestion(await downloadContent(url, session));
+}
+
+async function getDayInput(year, day, session) {
+ const url = `https://adventofcode.com/${+year}/day/${+day}/input`;
+ return await downloadContent(url, session);
+}
+
+async function submitDayAnswer(year, day, session, level, answer) {
+ const url = `https://adventofcode.com/${+year}/day/${+day}/answer`;
+ const postPayload = `level=${level}&answer=${encodeURIComponent(answer)}`;
+ return cleanAnswer(await downloadContent(url, session, postPayload));
+}
+
+async function respond(fn) {
+ try {
+ const body = await fn();
+ return new Response(
+ typeof body === 'string' ? body : JSON.stringify(body),
+ {
+ status: 200,
+ headers: { 'Access-Control-Allow-Origin': '*' },
+ },
+ );
+ } catch (e) {
+ return new Response(e.toString(), {
+ status: 500,
+ headers: { 'Access-Control-Allow-Origin': '*' },
+ });
+ }
+}
+
+export default {
+ async fetch(req) {
+ let match;
+ const session = new URL(req.url).searchParams.get('session');
+ match = new URLPattern({ pathname: '/input/:year/:day' }).exec(req.url);
+ if (req.method === 'GET' && match) {
+ const { year, day } = match.pathname.groups;
+ return respond(() => getDayInput(year, day, session));
+ }
+ match = new URLPattern({ pathname: '/question/:year/:day' }).exec(req.url);
+ if (req.method === 'GET' && match) {
+ const { year, day } = match.pathname.groups;
+ return respond(() => getDayAnswer(year, day, session));
+ }
+ match = new URLPattern({ pathname: '/answer/:year/:day' }).exec(req.url);
+ if (req.method === 'POST' && match) {
+ const { year, day } = match.pathname.groups;
+ const formData = await req.formData();
+ const level = formData.get('level');
+ const answer = formData.get('answer');
+ return respond(() => submitDayAnswer(year, day, session, level, answer));
+ }
+ return new Response('Not found', { status: 404 });
+ },
+};
diff --git a/src/server/wrangler.toml b/src/server/wrangler.toml
new file mode 100644
index 00000000..fe05d40e
--- /dev/null
+++ b/src/server/wrangler.toml
@@ -0,0 +1,8 @@
+#:schema node_modules/wrangler/config-schema.json
+name = "aoc"
+main = "worker.js"
+compatibility_date = "2024-10-22"
+compatibility_flags = ["nodejs_compat"]
+
+[observability]
+enabled = true
diff --git a/src/utils/urls.js b/src/utils/urls.js
index 28d51dcf..71bc9181 100644
--- a/src/utils/urls.js
+++ b/src/utils/urls.js
@@ -23,4 +23,5 @@ export const imports = {
};
// export const aocSolverServer = 'https://www.wix.com/_serverless/adventofcode';
+// export const aocSolverServer = 'https://aoc.shahar-talmi.workers.dev';
export const aocSolverServer = 'https://aoc.deno.dev';