From 3becde58ee285a0e8ac392010c9136218903d7f0 Mon Sep 17 00:00:00 2001 From: WillLillis Date: Sun, 19 Nov 2023 14:16:03 -0500 Subject: [PATCH 1/7] fix: Proper display of stars for 2022 Day 25 --- aoc-client/src/lib.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/aoc-client/src/lib.rs b/aoc-client/src/lib.rs index ee5a032..dd1f6ac 100644 --- a/aoc-client/src/lib.rs +++ b/aoc-client/src/lib.rs @@ -384,6 +384,12 @@ impl AocClient { )) .unwrap(); + let all_stars = if main.contains("calendar calendar-perfect") { + true + } else { + false + }; + // Remove stars that have not been collected let calendar = cleaned_up .lines() @@ -394,13 +400,14 @@ impl AocClient { .map(|c| c.as_str()) .unwrap_or(""); - let stars = if class.contains("calendar-verycomplete") { - "**" - } else if class.contains("calendar-complete") { - "*" - } else { - "" - }; + let stars = + if class.contains("calendar-verycomplete") || all_stars { + "**" + } else if class.contains("calendar-complete") { + "*" + } else { + "" + }; star_regex.replace(line, stars) }) From 39cf60639a7997d33c5e80db1b66b6933b66baa2 Mon Sep 17 00:00:00 2001 From: WillLillis Date: Tue, 21 Nov 2023 19:39:12 -0500 Subject: [PATCH 2/7] refactor: Simplify all_stars assignment in get_calendar_html --- aoc-client/src/lib.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/aoc-client/src/lib.rs b/aoc-client/src/lib.rs index dd1f6ac..87384c2 100644 --- a/aoc-client/src/lib.rs +++ b/aoc-client/src/lib.rs @@ -384,11 +384,7 @@ impl AocClient { )) .unwrap(); - let all_stars = if main.contains("calendar calendar-perfect") { - true - } else { - false - }; + let all_stars = main.contains("calendar calendar-perfect"); // Remove stars that have not been collected let calendar = cleaned_up From 7e8f2f15ef59677df9a5e36b5fda726be89af5e4 Mon Sep 17 00:00:00 2001 From: WillLillis Date: Wed, 22 Nov 2023 10:56:06 -0500 Subject: [PATCH 3/7] feat: Personal Stats (TODO: Colored text) --- aoc-client/src/lib.rs | 46 +++++++++++++++++++++++++++++++++++++++++++ src/args.rs | 4 ++++ src/main.rs | 1 + 3 files changed, 51 insertions(+) diff --git a/aoc-client/src/lib.rs b/aoc-client/src/lib.rs index ee5a032..d8ebfbe 100644 --- a/aoc-client/src/lib.rs +++ b/aoc-client/src/lib.rs @@ -421,6 +421,52 @@ impl AocClient { Ok(()) } + fn get_personal_stats_html(&self) -> AocResult { + // TODO: Redirect means we're not logged in + debug!("🦌 Fetching {} personal stats", self.year); + + let url = + format!("https://adventofcode.com/{}/leaderboard/self", self.year); + let response = http_client(&self.session_cookie, "text/html")? + .get(url) + .send()?; + + if response.status() == StatusCode::NOT_FOUND { + // A 402 reponse means the calendar for + // the requested year is not yet available + return Err(AocError::InvalidEventYear(self.year)); + } else if response.status() == StatusCode::FOUND { + // A 302 reponse is a redirect and it likely + // means we're not logged in + warn!( + "🍪 It looks like you are not logged in, try logging in again" + ); + return Err(AocError::AocResponseError); + } + + let contents = response.error_for_status()?.text()?; + + let main = Regex::new(r"(?i)(?s)
(?P
.*)
") + .unwrap() + .captures(&contents) + .ok_or(AocError::AocResponseError)? + .name("main") + .unwrap() + .as_str() + .to_string(); + + Ok(main) + } + + pub fn show_personal_stats(&self) -> AocResult<()> { + let stats_html = self.get_personal_stats_html()?; + + let stats_text = self.html2text(&stats_html); + println!("{}", stats_text); + + Ok(()) + } + fn get_private_leaderboard( &self, leaderboard_id: LeaderboardId, diff --git a/src/args.rs b/src/args.rs index fba86ac..51b98a3 100644 --- a/src/args.rs +++ b/src/args.rs @@ -108,4 +108,8 @@ pub enum Command { /// Private leaderboard ID leaderboard_id: LeaderboardId, }, + + /// Show personal stats + #[command(visible_alias = "pe")] + PersonalStats, } diff --git a/src/main.rs b/src/main.rs index ab9e7f0..49eb354 100644 --- a/src/main.rs +++ b/src/main.rs @@ -103,6 +103,7 @@ fn run(args: &Args, client: AocClient) -> AocResult<()> { } Ok(()) } + Some(Command::PersonalStats) => client.show_personal_stats(), Some(Command::Submit { part, answer }) => { client.submit_answer_and_show_outcome(part, answer) } From 63b4d28e09ea22a0033f9f18c0cade14c9bda51a Mon Sep 17 00:00:00 2001 From: WillLillis Date: Wed, 22 Nov 2023 11:21:11 -0500 Subject: [PATCH 4/7] feat: Colored text for personal stats feat: Colored text for personal stats --- aoc-client/src/lib.rs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/aoc-client/src/lib.rs b/aoc-client/src/lib.rs index d8ebfbe..94e8258 100644 --- a/aoc-client/src/lib.rs +++ b/aoc-client/src/lib.rs @@ -422,7 +422,6 @@ impl AocClient { } fn get_personal_stats_html(&self) -> AocResult { - // TODO: Redirect means we're not logged in debug!("🦌 Fetching {} personal stats", self.year); let url = @@ -460,9 +459,34 @@ impl AocClient { pub fn show_personal_stats(&self) -> AocResult<()> { let stats_html = self.get_personal_stats_html()?; - let stats_text = self.html2text(&stats_html); - println!("{}", stats_text); + + // print explanatory paragraph before recoloring the text + for line in stats_text.lines().take_while(|line| !line.is_empty()) { + println!("{}", line); + } + + let stats_text = stats_text + .replace( + "--------Part 1---------", + &"--------Part 1---------".color(SILVER).to_string(), + ) + .replace( + "--------Part 2---------", + &"--------Part 2---------".color(GOLD).to_string(), + ) + .replace("Time", &"Time".color(GOLD).to_string()) + .replace("Rank", &"Rank".color(GOLD).to_string()) + .replace("Score", &"Score".color(GOLD).to_string()) + .replacen("Time", &"Time".color(SILVER).to_string(), 1) + .replacen("Rank", &"Rank".color(SILVER).to_string(), 2) // "Rank" also in explanation + .replacen("Score", &"Score".color(SILVER).to_string(), 2); // "Score" also in + // explanation + + // just print out the day by day stats, expalanatory paragraph is recolored now + for line in stats_text.lines().skip_while(|line| !line.is_empty()) { + println!("{}", line); + } Ok(()) } From 075348fa352052ca88a50301e0a1b3e390bb1c18 Mon Sep 17 00:00:00 2001 From: WillLillis Date: Wed, 22 Nov 2023 10:56:06 -0500 Subject: [PATCH 5/7] feat: Personal Stats (TODO: Colored text) --- aoc-client/src/lib.rs | 46 +++++++++++++++++++++++++++++++++++++++++++ src/args.rs | 4 ++++ src/main.rs | 1 + 3 files changed, 51 insertions(+) diff --git a/aoc-client/src/lib.rs b/aoc-client/src/lib.rs index 87384c2..7f42b9f 100644 --- a/aoc-client/src/lib.rs +++ b/aoc-client/src/lib.rs @@ -424,6 +424,52 @@ impl AocClient { Ok(()) } + fn get_personal_stats_html(&self) -> AocResult { + // TODO: Redirect means we're not logged in + debug!("🦌 Fetching {} personal stats", self.year); + + let url = + format!("https://adventofcode.com/{}/leaderboard/self", self.year); + let response = http_client(&self.session_cookie, "text/html")? + .get(url) + .send()?; + + if response.status() == StatusCode::NOT_FOUND { + // A 402 reponse means the calendar for + // the requested year is not yet available + return Err(AocError::InvalidEventYear(self.year)); + } else if response.status() == StatusCode::FOUND { + // A 302 reponse is a redirect and it likely + // means we're not logged in + warn!( + "🍪 It looks like you are not logged in, try logging in again" + ); + return Err(AocError::AocResponseError); + } + + let contents = response.error_for_status()?.text()?; + + let main = Regex::new(r"(?i)(?s)
(?P
.*)
") + .unwrap() + .captures(&contents) + .ok_or(AocError::AocResponseError)? + .name("main") + .unwrap() + .as_str() + .to_string(); + + Ok(main) + } + + pub fn show_personal_stats(&self) -> AocResult<()> { + let stats_html = self.get_personal_stats_html()?; + + let stats_text = self.html2text(&stats_html); + println!("{}", stats_text); + + Ok(()) + } + fn get_private_leaderboard( &self, leaderboard_id: LeaderboardId, diff --git a/src/args.rs b/src/args.rs index fba86ac..51b98a3 100644 --- a/src/args.rs +++ b/src/args.rs @@ -108,4 +108,8 @@ pub enum Command { /// Private leaderboard ID leaderboard_id: LeaderboardId, }, + + /// Show personal stats + #[command(visible_alias = "pe")] + PersonalStats, } diff --git a/src/main.rs b/src/main.rs index ab9e7f0..49eb354 100644 --- a/src/main.rs +++ b/src/main.rs @@ -103,6 +103,7 @@ fn run(args: &Args, client: AocClient) -> AocResult<()> { } Ok(()) } + Some(Command::PersonalStats) => client.show_personal_stats(), Some(Command::Submit { part, answer }) => { client.submit_answer_and_show_outcome(part, answer) } From 7e9edfc7ff60e21d52f6672bb9e95937847bfc37 Mon Sep 17 00:00:00 2001 From: WillLillis Date: Wed, 22 Nov 2023 11:21:11 -0500 Subject: [PATCH 6/7] feat: Colored text for personal stats feat: Colored text for personal stats --- aoc-client/src/lib.rs | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/aoc-client/src/lib.rs b/aoc-client/src/lib.rs index 7f42b9f..de6d943 100644 --- a/aoc-client/src/lib.rs +++ b/aoc-client/src/lib.rs @@ -425,7 +425,6 @@ impl AocClient { } fn get_personal_stats_html(&self) -> AocResult { - // TODO: Redirect means we're not logged in debug!("🦌 Fetching {} personal stats", self.year); let url = @@ -463,9 +462,34 @@ impl AocClient { pub fn show_personal_stats(&self) -> AocResult<()> { let stats_html = self.get_personal_stats_html()?; - let stats_text = self.html2text(&stats_html); - println!("{}", stats_text); + + // print explanatory paragraph before recoloring the text + for line in stats_text.lines().take_while(|line| !line.is_empty()) { + println!("{}", line); + } + + let stats_text = stats_text + .replace( + "--------Part 1---------", + &"--------Part 1---------".color(SILVER).to_string(), + ) + .replace( + "--------Part 2---------", + &"--------Part 2---------".color(GOLD).to_string(), + ) + .replace("Time", &"Time".color(GOLD).to_string()) + .replace("Rank", &"Rank".color(GOLD).to_string()) + .replace("Score", &"Score".color(GOLD).to_string()) + .replacen("Time", &"Time".color(SILVER).to_string(), 1) + .replacen("Rank", &"Rank".color(SILVER).to_string(), 2) // "Rank" also in explanation + .replacen("Score", &"Score".color(SILVER).to_string(), 2); // "Score" also in + // explanation + + // just print out the day by day stats, expalanatory paragraph is recolored now + for line in stats_text.lines().skip_while(|line| !line.is_empty()) { + println!("{}", line); + } Ok(()) } From 92f9f27b07f416f8074acb23da7df63b7c0fac68 Mon Sep 17 00:00:00 2001 From: WillLillis Date: Fri, 24 Nov 2023 23:50:28 -0500 Subject: [PATCH 7/7] fix: Use regex in show_personal_stats() --- aoc-client/src/lib.rs | 41 ++++++++++++++++++++++++++--------------- src/main.rs | 1 + 2 files changed, 27 insertions(+), 15 deletions(-) diff --git a/aoc-client/src/lib.rs b/aoc-client/src/lib.rs index de6d943..4aff2f2 100644 --- a/aoc-client/src/lib.rs +++ b/aoc-client/src/lib.rs @@ -100,6 +100,9 @@ pub enum AocError { #[error("Invalid session cookie")] InvalidSessionCookie, + #[error("Not logged in")] + NotLoggedIn, + #[error("HTTP request error: {0}")] HttpRequestError(#[from] reqwest::Error), @@ -440,10 +443,7 @@ impl AocClient { } else if response.status() == StatusCode::FOUND { // A 302 reponse is a redirect and it likely // means we're not logged in - warn!( - "🍪 It looks like you are not logged in, try logging in again" - ); - return Err(AocError::AocResponseError); + return Err(AocError::NotLoggedIn); } let contents = response.error_for_status()?.text()?; @@ -467,24 +467,35 @@ impl AocClient { // print explanatory paragraph before recoloring the text for line in stats_text.lines().take_while(|line| !line.is_empty()) { println!("{}", line); + if line.eq("You haven't collected any stars... yet.") { + return Ok(()); + } } + let caps = + Regex::new(r"(?-+Part \d+-+)([ ]+)(?-+Part \d+-+)") + .unwrap() + .captures(&stats_text) + .ok_or(AocError::AocResponseError)?; + + let part_1_str = caps + .name("Part1") + .ok_or(AocError::AocResponseError)? + .as_str(); + let part_2_str = caps + .name("Part2") + .ok_or(AocError::AocResponseError)? + .as_str(); + let stats_text = stats_text - .replace( - "--------Part 1---------", - &"--------Part 1---------".color(SILVER).to_string(), - ) - .replace( - "--------Part 2---------", - &"--------Part 2---------".color(GOLD).to_string(), - ) + .replace(part_1_str, &part_1_str.color(SILVER).to_string()) + .replace(part_2_str, &part_2_str.color(GOLD).to_string()) .replace("Time", &"Time".color(GOLD).to_string()) .replace("Rank", &"Rank".color(GOLD).to_string()) .replace("Score", &"Score".color(GOLD).to_string()) .replacen("Time", &"Time".color(SILVER).to_string(), 1) - .replacen("Rank", &"Rank".color(SILVER).to_string(), 2) // "Rank" also in explanation - .replacen("Score", &"Score".color(SILVER).to_string(), 2); // "Score" also in - // explanation + .replacen("Rank", &"Rank".color(SILVER).to_string(), 2) + .replacen("Score", &"Score".color(SILVER).to_string(), 2); // just print out the day by day stats, expalanatory paragraph is recolored now for line in stats_text.lines().skip_while(|line| !line.is_empty()) { diff --git a/src/main.rs b/src/main.rs index 49eb354..3ffa9c3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,6 +27,7 @@ fn main() { AocError::SessionFileNotFound => NO_INPUT, AocError::SessionFileReadError { .. } => IO_ERROR, AocError::InvalidSessionCookie { .. } => DATA_ERROR, + AocError::NotLoggedIn => DATA_ERROR, AocError::HttpRequestError { .. } => FAILURE, AocError::AocResponseError => FAILURE, AocError::PrivateLeaderboardNotAvailable => FAILURE,