1use std::error::Error;
4use std::fs::{create_dir_all, read_to_string, File};
5use std::io::Write;
6use std::io::{stdin, stdout};
7use std::path::{Path, PathBuf};
8use std::time::Instant;
9
10use attohttpc::header::{COOKIE, USER_AGENT};
11
12use crate::utils::Line;
13
14const BASE_URL: &str = "https://adventofcode.com";
15const USER_AGENT_VALUE: &str = "github.com/remi-dupre/aoc by remi@dupre.io";
16
17fn input_path(year: u16, day: u8) -> PathBuf {
18 format!("input/{}/day{}.txt", year, day).into()
19}
20
21fn token_path() -> PathBuf {
22 dirs::config_dir()
23 .map(|mut cfg| {
24 cfg.push("aoc/token.txt");
25 cfg
26 })
27 .unwrap_or_else(|| ".token".into())
28}
29
30pub fn get_input(year: u16, day: u8) -> Result<String, Box<dyn Error>> {
31 let mut result = get_from_path_or_else(&input_path(year, day), || {
32 let start = Instant::now();
33 let url = format!("{}/{}/day/{}/input", BASE_URL, year, day);
34 let session_cookie = format!("session={}", get_conn_token()?);
35
36 let resp = attohttpc::get(&url)
37 .header(COOKIE, session_cookie)
38 .header(USER_AGENT, USER_AGENT_VALUE)
39 .send()?;
40
41 let elapsed = start.elapsed();
42
43 println!(
44 " - {}",
45 Line::new("downloaded input file").with_duration(elapsed)
46 );
47
48 resp.text()
49 })?;
50
51 if result.ends_with('\n') {
52 result.pop();
53 }
54
55 Ok(result)
56}
57
58fn get_conn_token() -> Result<String, std::io::Error> {
59 get_from_path_or_else(&token_path(), || {
60 let mut stdout = stdout();
61 write!(&mut stdout, "Write your connection token: ")?;
62 stdout.flush()?;
63
64 let mut output = String::new();
65 stdin().read_line(&mut output)?;
66 Ok(output.trim().to_string())
67 })
68}
69
70fn get_from_path_or_else<E: Error>(
71 path: &Path,
72 fallback: impl FnOnce() -> Result<String, E>,
73) -> Result<String, E> {
74 let from_path = read_to_string(path);
75
76 if let Ok(res) = from_path {
77 Ok(res)
78 } else {
79 let res = fallback()?;
80 create_dir_all(path.parent().expect("no parent directory"))
81 .and_then(|_| File::create(path))
82 .and_then(|mut file| file.write_all(res.as_bytes()))
83 .unwrap_or_else(|err| eprintln!("could not write {}: {}", path.display(), err));
84 Ok(res)
85 }
86}