1#[cfg(feature = "local_cache")]
39mod cache;
40pub mod error;
41
42pub use Part::*;
43
44#[cfg(feature = "local_cache")]
45use crate::cache::cache_wrapper;
46
47use crate::error::{Error, Result};
48use std::{
49 fmt::Display,
50 fs::File,
51 io::{Read, Write},
52 path::Path,
53};
54use ureq::{get, post};
55
56pub enum Part {
60 Part(i32),
61 Part1,
62 Part2,
63}
64
65impl From<Part> for i32 {
66 fn from(value: Part) -> Self {
67 match value {
68 Part::Part(x) => x,
69 Part::Part1 => 1,
70 Part::Part2 => 2,
71 }
72 }
73}
74
75pub fn get_input(session: &str, year: impl Into<i32>, day: impl Into<i32>) -> Result<String> {
77 let url = format!(
78 "https://adventofcode.com/{}/day/{}/input",
79 year.into(),
80 day.into()
81 );
82 let cookies = format!("session={}", session);
83 let resp = get(&url)
84 .set("User-Agent", "rust/aoc_driver")
85 .set("Cookie", &cookies)
86 .call()
87 .map_err(|e| Error::UReq(Some(Box::new(e))))?;
88
89 let mut body = resp.into_string()?;
90
91 if body.ends_with('\n') {
93 body.pop();
94 }
95
96 Ok(body)
97}
98
99pub fn get_input_or_file(
107 session: &str,
108 year: impl Into<i32>,
109 day: impl Into<i32>,
110 path: impl AsRef<Path>,
111) -> Result<String> {
112 let path = path.as_ref();
113 match File::open(path) {
114 Ok(mut f) => {
115 let mut input = String::new();
116 f.read_to_string(&mut input)?;
117 Ok(input)
118 }
119 Err(_) => {
120 let input = get_input(session, year, day)?;
121 let mut output_file = File::create(path)?;
122 output_file.write_all(input.as_bytes())?;
123 Ok(input)
124 }
125 }
126}
127
128pub fn post_answer<SolOutput>(
138 session: &str,
139 year: i32,
140 day: i32,
141 part: i32,
142 #[cfg_attr(not(feature = "local_cache"), allow(unused))] cache_path: Option<impl AsRef<Path>>,
143 answer: SolOutput,
144) -> Result<()>
145where
146 SolOutput: Display,
147{
148 let post_fn = |answer: &str| {
149 let url = format!("https://adventofcode.com/{}/day/{}/answer", year, day);
150 let cookies = format!("session={}", session);
151 let form_level = format!("{}", part);
152 let form = [("level", form_level.as_str()), ("answer", answer)];
153
154 let resp = post(&url)
155 .set("User-Agent", "rust/aoc_driver")
156 .set("Cookie", &cookies)
157 .send_form(&form)
158 .map_err(|e| Error::UReq(Some(Box::new(e))))?;
159
160 let body = resp.into_string().expect("response was not a string");
161
162 let timeout_msg = "You gave an answer too recently; you have to wait after submitting an answer before trying again. You have ";
163 if let Some(index) = body.find(timeout_msg) {
164 let start = index + timeout_msg.len();
165 let end = body.find(" left to wait.").unwrap();
166 let timeout = String::from(&body[start..end]);
167 return Err(Error::RateLimit(timeout));
168 }
169
170 let correct = body.contains("That's the right answer!")
171 | body.contains("Did you already complete it?");
172 match correct {
173 true => Ok(()),
174 false => Err(Error::Incorrect),
175 }
176 };
177
178 let answer = answer.to_string();
179
180 #[cfg(feature = "local_cache")]
181 return cache_wrapper(cache_path, part, &answer, post_fn);
182
183 #[cfg(not(feature = "local_cache"))]
184 return post_fn(&answer);
185}
186
187pub fn calculate_and_post<SolOutput, SolFn>(
199 session: &str,
200 year: impl Into<i32>,
201 day: impl Into<i32>,
202 part: impl Into<i32>,
203 input_path: Option<impl AsRef<Path>>,
204 cache_path: Option<impl AsRef<Path>>,
205 solution: SolFn,
206) -> Result<()>
207where
208 SolOutput: Display,
209 SolFn: FnOnce(&str) -> SolOutput,
210{
211 let year = year.into();
212 let day = day.into();
213 let part = part.into();
214
215 let input = match input_path {
216 Some(path) => get_input_or_file(session, year, day, path),
217 None => get_input(session, year, day),
218 }?;
219 let answer = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| solution(&input)))
220 .map_err(|err| Error::Panic(Some(err)))?;
221 post_answer(session, year, day, part, cache_path, answer)?;
222 Ok(())
223}
224
225#[macro_export]
229macro_rules! aoc_magic {
230 ($session:expr, $year:literal : $day:literal : $part:literal, $sol:expr) => {{
231 let mut input_path = std::path::PathBuf::from_iter(["inputs", &$year.to_string()]);
232 std::fs::create_dir_all(&input_path).unwrap();
233
234 let file_name = format!("{}.txt", $day);
235 input_path.push(file_name);
236
237 let mut cache_path = std::path::PathBuf::from_iter(["cache", &$year.to_string()]);
238 std::fs::create_dir_all(&cache_path).unwrap();
239
240 let file_name = format!("{}.json", $day);
241 cache_path.push(file_name);
242
243 aoc_driver::calculate_and_post(
244 $session,
245 $year,
246 $day,
247 $part,
248 Some(&input_path),
249 Some(&cache_path),
250 $sol,
251 )
252 }};
253}