use crate::error::ProfileError;
use crate::url;
use anyhow::{anyhow, Result};
use regex::Regex;
use scraper::selectable::Selectable;
use serde::Deserialize;
use std::collections::HashMap;
pub async fn get_inventory_by_url(url_name_id: &str) -> Result<HashMap<String, u32>, ProfileError> {
let url = url::convert_to_url(url_name_id);
let content = match ureq::get(format!("{}/inventory", url).as_str()).call() {
Ok(r) => r.into_string()?,
Err(_) => {
return Err(ProfileError::FetchError(format!(
"Failed to parse, url: {}",
url
)))
}
};
let document = scraper::Html::parse_document(&content);
let selector = scraper::Selector::parse("a.games_list_tab")?;
let stats = document.select(&selector);
let mut links: HashMap<String, u32> = HashMap::new();
for profile in stats {
let count: String = profile
.select(&scraper::Selector::parse("span.games_list_tab_number")?)
.next()
.map(|num| num.text().collect::<String>())
.unwrap_or("0".to_string())
.chars()
.filter(|char| char.is_ascii_digit())
.collect();
let name = profile
.select(&scraper::Selector::parse("span.games_list_tab_name")?)
.next()
.map(|name| name.text().collect::<String>())
.unwrap_or("Game Name Not Found".to_owned());
let number = count.parse().unwrap_or(0);
if number > 0 {
links.insert(name, number);
}
}
Ok(links)
}
pub async fn get_activities_by_url(
url_name_id: &str,
) -> Result<HashMap<String, u32>, ProfileError> {
let url = url::convert_to_url(url_name_id);
let content = match ureq::get(&url).call() {
Ok(r) => r.into_string()?,
Err(_) => {
return Err(ProfileError::FetchError(format!(
"Failed to parse, url: {}",
url
)))
}
};
let document = scraper::Html::parse_document(&content);
let html_product_selector = scraper::Selector::parse("div.profile_count_link")?;
let html_products = document.select(&html_product_selector);
let mut links: HashMap<String, u32> = HashMap::new();
for profile in html_products {
let count: String = profile
.select(&scraper::Selector::parse("span.profile_count_link_total")?)
.next()
.map(|text| text.text().collect::<String>())
.unwrap_or_default()
.chars()
.filter(|char| char.is_ascii_digit())
.collect();
let name = profile
.select(&scraper::Selector::parse("span.count_link_label")?)
.next()
.map(|number| number.text().collect::<String>())
.unwrap_or_default();
let number = count.parse().unwrap_or(0);
if number > 0 {
links.insert(name, number);
}
}
Ok(links)
}
pub async fn get_name_by_url(url_name_id: &str) -> Result<SteamProfile, ProfileError> {
let url = url::convert_to_url(url_name_id);
let content = match ureq::get(&url).call() {
Ok(r) => r.into_string()?,
Err(_) => {
return Err(ProfileError::FetchError(format!(
"Failed to parse, url: {}",
url
)))
}
};
let document = scraper::Html::parse_document(&content);
let selector = scraper::Selector::parse("div#responsive_page_template_content")?;
let pp: String = document
.select(&scraper::Selector::parse("div.playerAvatarAutoSizeInner")?)
.next()
.ok_or(ProfileError::Anyhow(anyhow!("Scrapper Failed")))?
.child_elements()
.last()
.ok_or(ProfileError::Anyhow(anyhow!("Scrapper Failed")))?
.attr("src")
.unwrap_or("none")
.to_string();
let lvl: String = document
.select(&scraper::Selector::parse("span.friendPlayerLevelNum")?)
.next()
.ok_or(ProfileError::Anyhow(anyhow!("Scrapper Failed")))?
.inner_html()
.to_string();
let mut stats: String = document
.select(&selector)
.next()
.ok_or(ProfileError::Anyhow(anyhow!("Scrapper Failed")))?
.child_elements()
.next()
.ok_or(ProfileError::Anyhow(anyhow!("Scrapper Failed")))?
.text()
.collect();
stats = stats.replace(['\\', '\n', '\t'], "" ).replace("g_rgProfileData = ","" ).replace(";const g_bViewingOwnProfile = 0;$J( function() {window.Responsive_ReparentItemsInResponsiveMode && Responsive_ReparentItemsInResponsiveMode( '.responsive_groupfriends_element', $J('#responsive_groupfriends_element_ctn') );SetupAnimateOnHoverImages();});","" );
let re = Regex::new(r#""summary":"(.*?)\s*"}"#)?;
let test = re.captures(&stats).ok_or(anyhow!("Nothing is Captured"))?;
let stf: SteamProfile = serde_json::from_str(
&stats
.replace(&test[1], &test[1].replace('"', r#"\""#))
.replace(
'}',
format!(r#","imgurl":"{}","lvl":"{}"}}"#, pp, lvl).as_str(),
),
)?;
Ok(stf)
}
#[derive(Debug, Deserialize, Default)]
pub struct Profile {
url: String,
name: String,
steamid: String,
description: String,
profilepic: String,
level: u32,
stats: HashMap<String, u32>,
inventory: HashMap<String, u32>,
}
#[derive(Debug, Deserialize, Default)]
pub struct SteamProfile {
url: String,
steamid: String,
personaname: String,
summary: String,
imgurl: String,
lvl: String,
}
impl Profile {
pub async fn get_full_profile(url_name_id: &str) -> Self {
let results = tokio::join!(
get_name_by_url(url_name_id),
get_activities_by_url(url_name_id),
get_inventory_by_url(url_name_id),
);
let prof = results.0.unwrap_or_default();
Self {
url: prof.url,
name: prof.personaname,
steamid: prof.steamid,
description: prof.summary,
profilepic: prof.imgurl,
level: prof.lvl.parse().unwrap_or_default(),
stats: results.1.unwrap_or_default(),
inventory: results.2.unwrap_or_default(),
}
}
#[cfg(feature = "print")]
pub fn print_profile(self) {
use owo_colors::OwoColorize;
use tabled::{col, row, Table};
let table1 = Table::new(self.stats)
.with(tabled::settings::Style::modern())
.to_owned();
let table2 = Table::new(self.inventory)
.with(tabled::settings::Style::modern())
.to_owned();
let mut table3 = row![col!["Statistics", table1], col!["Inventory", table2]];
println!(
"\nName: {}, \nLevel: {}, \nSteamID: {}, \nUrl: {}, \nProfile Picture: {}, \nDescription: {}, \n{}",
self.name.red(),
self.level.purple(),
self.steamid.yellow(),
self.url.green(),
self.profilepic.cyan(),
self.description.blue(),
table3.with(tabled::settings::Style::modern()).green()
)
}
}