use indexmap::IndexMap; use lazy_static::lazy_static; use log::error; use serde_derive::Deserialize; use serde_json::{json, Value}; use std::sync::Mutex; use crate::data_source::{DataSource, Stat}; use crate::ChampInfo; use crate::Champion; #[derive(Deserialize, Debug)] struct BuildResponse { lol: Lol, } #[derive(Deserialize, Debug)] struct Lol { #[serde(rename = "championsReport")] champions_report: Vec, } #[derive(Deserialize, Debug)] struct ChampionReport { champion_id: u32, role: String, patch: String, stats: Stats, } #[derive(Deserialize, Debug)] struct Stats { starting_items: Build, core_builds: Build, big_item_builds: Build, skills: Build, most_common_starting_items: Build, most_common_core_builds: Build, most_common_big_item_builds: Build, most_common_skills: Build, games: u32, kills: u32, deaths: u32, wins: u32, assists: u32 } #[derive(Deserialize, Debug)] struct Build { build: Vec, win_rate: f64, games: u32, } lazy_static! { static ref CHAMPIONS_REPORT: Mutex> = Mutex::new(Vec::new()); } pub struct CGGDataSource; impl CGGDataSource { fn make_item_set(&self, build: &Build, label: &str) -> Value { json!({ "items": build.build.iter().map(|x| json!({"id": x.to_string(), "count": 1})).collect::>(), "type": format!("{} ({:.2}% - {} games)", label, build.win_rate * 100., build.games) }) } fn make_item_set_from_list(&self, build: &Build, label: &str, skills: &Build) -> Value { let key_order = skills .build .iter() .map(|x| x.to_string()) .collect::>() .join(""); self.make_item_set(build, [label, key_order.as_str()].join(" ").as_str()) } } fn extract_json(pattern: &str, page: &str) -> String { let json = page[page.find(pattern).unwrap() + pattern.len()..].to_owned(); json[..json.find("};").unwrap() + 1].replace("undefined", "null") } impl DataSource for CGGDataSource { fn get_alias(&self) -> &str { "CGG" } fn get_timeout(&self) -> u64 { 0 } fn get_champs_with_positions( &self, client: &ureq::Agent, _champion: &Champion, ) -> IndexMap> { let mut champions: IndexMap> = IndexMap::new(); match client.get("https://champion.gg").call() { Ok(req) => { let page = &req.into_string().unwrap(); let datas: BuildResponse = serde_json::from_str(&extract_json("window.__PRELOADED_STATE__ = ", page)) .unwrap(); for champ in &datas.lol.champions_report { let id = champ.champion_id; if champions.contains_key(&id) { let mut roles = champions.get(&id).unwrap().to_owned(); roles.push(champ.role.to_owned()); champions.insert(id, roles); } else { champions.insert(id, vec![champ.role.to_owned()]); } } for report in datas.lol.champions_report { CHAMPIONS_REPORT.lock().unwrap().push(report); } } Err(e) => error!("Error retrieving data: {}", e), } champions } fn get_champ_data_with_win_pourcentage( &self, champ: &ChampInfo, positions: &[String], _client: &ureq::Agent, ) -> Vec<(String, Vec, Stat)> { let mut data = vec![]; for position in positions { let mut some_champ: Option<&ChampionReport> = None; let reports = CHAMPIONS_REPORT.lock().unwrap(); for champion in reports.iter() { if champion.champion_id.to_string() == champ.key && champion.role == *position { some_champ = Some(champion); break; } } if let Some(champ) = some_champ { let blocks = vec![ self.make_item_set_from_list( &champ.stats.starting_items, "Highest % Win Starting Items | Skills: ", &champ.stats.skills, ), self.make_item_set(&champ.stats.core_builds, "Highest % Win Core Build Path:"), self.make_item_set(&champ.stats.big_item_builds, "Highest % Win Big Items:"), self.make_item_set_from_list( &champ.stats.most_common_starting_items, "Most Frequent Starting Items | Skills: ", &champ.stats.most_common_skills, ), self.make_item_set( &champ.stats.most_common_core_builds, "Most Frequent Build Path", ), self.make_item_set( &champ.stats.most_common_big_item_builds, "Most Frequent Big Items:", ), ]; data.push(( position.to_owned(), blocks, Stat { win_rate: (champ.stats.wins as f64 / champ.stats.games as f64) * 100f64, games: champ.stats.games, kda: (champ.stats.kills + champ.stats.assists) as f64 / champ.stats.deaths as f64, patch: champ.patch.to_owned(), }, )); } } data } }