/ src / aoc / api.rs
api.rs
 1  use std::sync::LazyLock;
 2  
 3  use anyhow::anyhow;
 4  use regex::Regex;
 5  use reqwest::Client;
 6  
 7  use super::models::{AocWhoami, PrivateLeaderboard};
 8  
 9  static INVITE_CODE_REGEX: LazyLock<Regex> =
10      LazyLock::new(|| Regex::new(r"<code>((\d+)-[\da-z]+)</code>").unwrap());
11  
12  pub struct AocApiClient {
13      http: Client,
14  }
15  
16  impl AocApiClient {
17      pub fn new(session: &str) -> anyhow::Result<Self> {
18          Ok(Self {
19              http: Client::builder()
20                  .default_headers(
21                      [(
22                          "Cookie".try_into()?,
23                          format!("session={session}").try_into()?,
24                      )]
25                      .into_iter()
26                      .collect(),
27                  )
28                  .build()?,
29          })
30      }
31  
32      pub async fn whoami(&self) -> anyhow::Result<AocWhoami> {
33          let response = self
34              .http
35              .get("https://adventofcode.com/leaderboard/private")
36              .send()
37              .await?
38              .error_for_status()?
39              .text()
40              .await?;
41  
42          let cap = INVITE_CODE_REGEX
43              .captures(&response)
44              .ok_or_else(|| anyhow!("Failed to find invite code in response"))?;
45          let invite_code = cap[1].to_owned();
46          let user_id = cap[2].parse()?;
47  
48          Ok(AocWhoami {
49              invite_code,
50              user_id,
51          })
52      }
53  
54      pub async fn get_private_leaderboard(
55          &self,
56          year: i32,
57          user_id: u64,
58      ) -> reqwest::Result<PrivateLeaderboard> {
59          self.http
60              .get(format!(
61                  "https://adventofcode.com/{year}/leaderboard/private/view/{user_id}.json"
62              ))
63              .send()
64              .await?
65              .error_for_status()?
66              .json()
67              .await
68      }
69  }
70  
71  #[cfg(test)]
72  mod tests {
73      use super::*;
74  
75      #[test]
76      fn invite_code_regex() {
77          let c = INVITE_CODE_REGEX
78              .captures(
79                  "Others can join it using the code <code>123456-42ff1337</code>. Up to \
80                   <code>200</code> users can join, including yourself.",
81              )
82              .unwrap();
83          assert_eq!(&c[1], "123456-42ff1337");
84          assert_eq!(&c[2], "123456");
85      }
86  }