diff --git a/froxy-scraper/examples/get_friends.rs b/froxy-scraper/examples/get_friends.rs new file mode 100644 index 0000000..46e020f --- /dev/null +++ b/froxy-scraper/examples/get_friends.rs @@ -0,0 +1,27 @@ +use froxy_scrapper::{state::State, types::ClusterLocation}; + +#[tokio::main(flavor = "current_thread")] +async fn main() { + let session = std::env::var("FROXY_COOKIE") + .expect("provide `FROXY_COOKIE` with the session cookie from you friends42 instance"); + let state = State::new(session); + + let res = state.get("/friends/", None).await.unwrap(); + + let text = res.text().await.unwrap(); + + let location = froxy_scrapper::parsing::friend_page(&text); + + for l in &location.friends { + println!( + "{:^10} | {:^10} | {:^30} | {:^20}", + l.login, + l.position.as_ref().map(|s| s.as_str()).unwrap_or("<>"), + l.last_active.as_ref().map(|s| s.as_str()).unwrap_or("<>"), + l.image + .as_ref() + .map(|s| &s[s.len().saturating_sub(20)..]) + .unwrap_or("<>") + ); + } +} diff --git a/froxy-scraper/src/parsing.rs b/froxy-scraper/src/parsing.rs index d279d22..8b7f680 100644 --- a/froxy-scraper/src/parsing.rs +++ b/froxy-scraper/src/parsing.rs @@ -1,7 +1,8 @@ use scraper::Selector; +use smol_str::SmolStr; use url::Url; -use crate::types::{ClusterLocation, ClusterLocationData, Relation}; +use crate::types::{ClusterLocation, ClusterLocationData, Friend, Friends, Relation}; macro_rules! sel { ($name:ident, $value:literal) => { @@ -88,17 +89,17 @@ pub fn get_cluster_location( .has_class("pooled", scraper::CaseSensitivity::AsciiCaseInsensitive); let mut relation = Relation::None; - if is_me { - relation = Relation::Me; - } - if is_friend { - relation = Relation::Friend; + if is_pooled { + relation = Relation::Pooled; } if is_close_friend { relation = Relation::CloseFriend; } - if is_pooled { - relation = Relation::Pooled; + if is_friend { + relation = Relation::Friend; + } + if is_me { + relation = Relation::Me; } if is_focus { relation = Relation::Focus; @@ -119,3 +120,69 @@ pub fn get_cluster_location( out } + +sel!(FRIEND_CONT_SEL, "div.container > div > div.card.pl-fix"); +sel!(FRIEND_CARD_TITLE, "h5.card-title"); +sel!(FRIEND_LAST_ACTIVE, ".card-text"); + +pub fn friend_page(page: impl AsRef) -> Friends { + let page = page.as_ref(); + + let doc = scraper::Html::parse_document(page); + + let mut out = Friends { + friends: Vec::new(), + }; + + for f in doc.select(&FRIEND_CONT_SEL) { + let Some(login) = f + .select(&FRIEND_CARD_TITLE) + .next() + .map(|e| { + e.text() + .fold(String::new(), |mut acc: String, s: &str| -> String { + acc += s; + acc + }) + }) + .map(|s| SmolStr::new(s.trim())) + else { + continue; + }; + let img = f + .select(&IMAGE_SEL) + .next() + .and_then(|e| e.attr("src")) + .map(ToString::to_string); + + let pos_la = f + .select(&FRIEND_LAST_ACTIVE) + .next() + .map(|e| { + e.text() + .fold(String::new(), |mut acc: String, s: &str| -> String { + acc += s; + acc + }) + }) + .map(|s| SmolStr::new(s.trim())) + .and_then(|s| { + s.split_once(' ') + .map(|(pos, last_active)| { + (Some(SmolStr::new(pos)), Some(SmolStr::new(last_active))) + }) + .or(Some((Some(s), None))) + }); + out.friends.push(Friend { + login, + image: img, + position: pos_la + .as_ref() + .and_then(|(pos, _)| pos.clone()) + .filter(|s| s != "Absent"), + last_active: pos_la.as_ref().and_then(|(_, la)| la.clone()), + }) + } + + out +} diff --git a/froxy-scraper/src/types.rs b/froxy-scraper/src/types.rs index dd19326..692b79e 100644 --- a/froxy-scraper/src/types.rs +++ b/froxy-scraper/src/types.rs @@ -1,3 +1,5 @@ +use smol_str::SmolStr; + #[non_exhaustive] #[derive(Debug, Clone, Default, Eq, PartialEq)] pub enum ClusterLocationStatus { @@ -46,3 +48,16 @@ pub struct CluserInformation { pub cluster_name: smol_str::SmolStr, pub locations: Vec, } + +#[derive(Debug, Clone)] +pub struct Friend { + pub login: SmolStr, + pub image: Option, + pub position: Option, + pub last_active: Option, +} + +#[derive(Debug, Clone)] +pub struct Friends { + pub friends: Vec, +}