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 }