use fantoccini::{Client, ClientBuilder, Locator}; use log::info; use log::warn; use serde::{Deserialize, Serialize}; macro_rules! skip_fail { ($res:expr) => { match $res { Ok(val) => val, Err(e) => { warn!("An error: {}; skipped.", e); continue; } } }; } macro_rules! skip_fail_opt { ($res:expr) => { match $res { Some(val) => val, None => { warn!("Unexpected empty value; skipped."); continue; } } }; } /// Holds data about stackoverflow answers #[derive(Debug, Serialize, Deserialize, Clone)] pub struct Answer { upvotes: u32, author: String, pub content: String, } /// Get all answers from a stackoverflow domain. No error handling is done so get ready to either /// check your input or "note: run with `RUST_BACKTRACE=1` environment variable to display a /// backtrace" pub async fn get_answers( c: &Client, url: &str, i: usize, links_size: usize, ) -> anyhow::Result> { // first, go to the Wikipedia page for Foobar c.goto(url).await?; let answer_loc = c.find_all(Locator::Css(".answer")).await?; let mut out_answers = vec![]; for (j, answer) in answer_loc.iter().enumerate() { info!("Getting answer {} on link {} of {}", j, i, links_size); let text = skip_fail!(answer.text().await); let score = skip_fail!( skip_fail_opt!(text.clone().split('\n').collect::>().get(0)) .parse::() ); let content = text .split("Share\nImprove this answer") .collect::>()[0] .to_string() .replace("\\n", "\n"); out_answers.push(Answer { upvotes: score, content: content, author: "unimplemented".to_string(), }); } Ok(out_answers) } pub async fn get_top_links(c: &Client, pages: u16) -> anyhow::Result> { let mut answers = vec![]; for page in 1..=pages { skip_fail!( c.goto( format!( "https://stackoverflow.com/questions?tab=votes&page={}", page ) .as_str(), ) .await ); let finds = c.find_all(Locator::Css(".s-link")).await?; for find in finds { if skip_fail_opt!(skip_fail!(find.attr("href").await)).contains("/questions/") { answers.push(find.attr("href").await.unwrap().unwrap()); } } } Ok(answers) }