Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Personal stats #18

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
98 changes: 91 additions & 7 deletions aoc-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),

Expand Down Expand Up @@ -384,6 +387,8 @@ impl AocClient {
))
.unwrap();

let all_stars = main.contains("calendar calendar-perfect");

// Remove stars that have not been collected
let calendar = cleaned_up
.lines()
Expand All @@ -394,13 +399,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)
})
Expand All @@ -421,6 +427,84 @@ impl AocClient {
Ok(())
}

fn get_personal_stats_html(&self) -> AocResult<String> {
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
return Err(AocError::NotLoggedIn);
}

let contents = response.error_for_status()?.text()?;

let main = Regex::new(r"(?i)(?s)<main>(?P<main>.*)</main>")
.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);

// 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"(?<Part1>-+Part \d+-+)([ ]+)(?<Part2>-+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_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)
.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()) {
println!("{}", line);
}

Ok(())
}

fn get_private_leaderboard(
&self,
leaderboard_id: LeaderboardId,
Expand Down
4 changes: 4 additions & 0 deletions src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,8 @@ pub enum Command {
/// Private leaderboard ID
leaderboard_id: LeaderboardId,
},

/// Show personal stats
#[command(visible_alias = "pe")]
PersonalStats,
}
2 changes: 2 additions & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -103,6 +104,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)
}
Expand Down