Skip to content

Commit f8a2c70

Browse files
committed
Everybody.Codes: implement web API to get data and answers
1 parent 38d5a6a commit f8a2c70

2 files changed

Lines changed: 44 additions & 20 deletions

File tree

everybody_codes/runner.py

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,40 @@
1-
#!/bin/python
1+
#!/usr/bin/python
22

33
import os
44
import pathlib
55

66
import click
7-
import ecd
7+
import requests
88
from lib import running
99

10+
from Crypto.Cipher import AES
11+
from Crypto.Util.Padding import unpad
12+
1013

1114
class Runner(running.Runner):
1215

1316
def solutions_path(self) -> pathlib.Path:
1417
"""Return the solution file."""
1518
return pathlib.Path(f"{self.year}/solutions.txt")
1619

20+
def write_solutions(self, year: int, day: int) -> str:
21+
"""Write the solutions to file."""
22+
cookie = (pathlib.Path(os.getenv("XDG_DATA_HOME")) / "cookies/ec").read_text().strip()
23+
session = requests.Session()
24+
session.cookies.set("everybody-codes", cookie)
25+
26+
year = year.removeprefix("event").removeprefix("story").removeprefix("0")
27+
data = session.get(f"https://api.everybody.codes/event/{year}/quest/{day}").json()
28+
want = [f"answer{i}" for i in range(1, 4)]
29+
if not set(want).issubset(set(data)):
30+
return None
31+
solutions = [line for line in self.solutions_path().read_text().splitlines() if not line.split(maxsplit=1)[0].startswith(f"{day:02}")]
32+
answers = []
33+
for part in range(1, 4):
34+
answers.append(f"{day:02}.{part} {data[f"answer{part}"]}")
35+
self.solutions_path().write_text("\n".join(solutions + answers) + "\n")
36+
return answers
37+
1738
def input_path(self, part: int) -> pathlib.Path:
1839
"""Return the input file."""
1940
p = pathlib.Path(f"{self.year}/inputs/{self.day:02}.{part}.txt")
@@ -28,11 +49,22 @@ def module_name(self) -> str:
2849

2950
def download_input(self, year: int, day: int, part: int) -> str | None:
3051
"""Download the input."""
31-
event = int(year.replace("event", ""))
32-
data = ecd.get_inputs(quest=day, event=event)
33-
if str(part) not in data:
34-
return None
35-
return data[str(part)]
52+
# Hard coded per account, see https://api.everybody.codes/user/me
53+
seed = 49
54+
year = year.removeprefix("event").removeprefix("story").removeprefix("0")
55+
56+
cookie = (pathlib.Path(os.getenv("XDG_DATA_HOME")) / "cookies/ec").read_text().strip()
57+
58+
session = requests.Session()
59+
session.cookies.set("everybody-codes", cookie)
60+
61+
data = session.get(f"https://everybody.codes/assets/{year}/{day}/input/{seed}.json").json()
62+
metadata = session.get(f"https://api.everybody.codes/event/{year}/quest/{day}").json()
63+
64+
aes_key = metadata[f"key{part}"].encode()
65+
cipher = AES.new(aes_key, AES.MODE_CBC, iv=aes_key[:AES.block_size])
66+
plaintext = cipher.decrypt(bytes.fromhex(data[str(part)]))
67+
return unpad(plaintext, AES.block_size).decode()
3668

3769

3870
@click.command()

pylib/running.py

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ def download_input(self, year: int, day: int, part: int) -> str:
5353
"""Download the input."""
5454
raise NotImplemented
5555

56+
def write_solutions(self, year: int, day: int) -> str:
57+
"""Write the solutions to file."""
58+
raise NotImplemented
59+
5660
def submit_solution(self, year: int, day: int, solutions: list[int | str]) -> str:
5761
"""Submit the solution."""
5862
raise NotImplemented
@@ -61,19 +65,7 @@ def get_solutions(self, day: int) -> list[int] | None:
6165
solutions_path = self.solutions_path()
6266
want_raw = [line for line in solutions_path.read_text().splitlines() if line.startswith(f"{day:02}.")]
6367
if not want_raw:
64-
session = requests.Session()
65-
cookie_file = pathlib.Path(os.getenv("XDG_DATA_HOME")) / "everyone.codes.cookie"
66-
cookie = cookie_file.read_text().strip()
67-
session.cookies.set("everybody-codes", cookie)
68-
data = session.get(f"https://everybody.codes/api/event/2024/quest/{day}").json()
69-
want = [f"answer{i}" for i in range(1, 4)]
70-
if not set(want).issubset(set(data)):
71-
return None
72-
new_line = "\t".join([f"{day:02}"] + [data[i] for i in want])
73-
solutions = [line for line in solutions_path.read_text().splitlines() if line.split("\t")[0] != f"{day:02}"]
74-
solutions.append(new_line)
75-
solutions_path.write_text("\n".join(solutions) + "\n")
76-
want_raw = new_line
68+
want_raw = self.write_solutions(self.year, day)
7769
if not want_raw:
7870
return None
7971
return [line.split(maxsplit=1)[1] for line in want_raw]

0 commit comments

Comments
 (0)