advent_of_utils_cli/
input.rs

1use crate::error::AocError;
2use crate::types::AocDatabase;
3use reqwest::blocking::Client;
4use std::{fmt::Display, sync::Arc, time::Instant};
5
6use crate::error::InputError;
7
8const USER_AGENT: &str =
9    "Advent of Utils by Itron-al-Lenn found on github.com/Itron-al-Lenn/Advent-of-Utils";
10const AOC_BASE_URL: &str = "https://adventofcode.com";
11
12#[derive(Debug, Clone)]
13pub struct SessionToken(String);
14
15impl SessionToken {
16    pub fn new() -> Result<Self, InputError> {
17        match std::env::var("AOC_SESSION") {
18            Ok(token) => Ok(Self(token)),
19            Err(e) => Err(InputError::VarError {
20                key: "AOC_SESSION".to_string(),
21                reason: "Faield fetching the session token".to_string(),
22                source: Some(e),
23            }),
24        }
25    }
26}
27
28impl Display for SessionToken {
29    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
30        write!(f, "{}", self.0)
31    }
32}
33
34impl From<String> for SessionToken {
35    fn from(value: String) -> Self {
36        Self(value)
37    }
38}
39
40fn create_client() -> Result<Client, InputError> {
41    let cookie = reqwest::cookie::Jar::default();
42    cookie.add_cookie_str(
43        &format!("session={}", SessionToken::new()?),
44        &AOC_BASE_URL.parse::<reqwest::Url>().unwrap(),
45    );
46
47    Ok(Client::builder()
48        .cookie_provider(Arc::new(cookie))
49        .user_agent(USER_AGENT)
50        .build()
51        .expect("Failed to create HTTP client"))
52}
53
54fn fetch_input(year: i32, day: u8) -> Result<String, InputError> {
55    let url = format!("{}/{}/day/{}/input", AOC_BASE_URL, year, day);
56
57    create_client()?
58        .get(&url)
59        .send()
60        .map_err(|e| InputError::FetchFailed {
61            year,
62            day,
63            reason: "Network request failed".to_string(),
64            source: Some(e),
65        })?
66        .error_for_status()
67        .map_err(|e| InputError::FetchFailed {
68            year,
69            day,
70            reason: "Server returned error status".to_string(),
71            source: Some(e),
72        })?
73        .text()
74        .map_err(|e| InputError::FetchFailed {
75            year,
76            day,
77            reason: "Failed to read response text".to_string(),
78            source: Some(e),
79        })
80}
81
82pub fn get_input(
83    year: i32,
84    day: u8,
85    db: &AocDatabase,
86    test: bool,
87) -> Result<(String, Instant), AocError> {
88    if db.has_input(year, day, test)? {
89        let time = Instant::now();
90        let input = db.get_input(year, day, test)?;
91        Ok((input, time))
92    } else if !test {
93        println!("Fetching online...");
94        let input = fetch_input(year, day)?;
95        let time = Instant::now();
96        db.set_input(year, day, test, input.clone())?;
97        Ok((input, time))
98    } else {
99        Err(AocError::Input(InputError::NoTestInput { day }))
100    }
101}