use crate::data_source::{Build, DataSource, Item}; #[cfg(test)] use crate::time::Duration; use indexmap::IndexMap; use regex::Regex; use serde_derive::Deserialize; use serde_json::{json, Value}; pub struct KBDataSource { token: Option, internal_classname_mapping: IndexMap, } #[derive(Deserialize, Debug)] struct ChampionResponse { patches: Vec, champions: Vec, } #[derive(Deserialize, Debug)] struct Patch { enabled: bool, #[serde(rename = "patchVersion")] patch_version: String, patchid: u32, start: String, } #[derive(Deserialize, Debug)] struct Champion { id: u32, name: String, #[serde(rename = "className")] classname: String, builds: Option, } #[derive(Deserialize, Debug)] struct Position { bot: u16, jungle: u16, mid: u16, support: u16, top: u16, } #[derive(Deserialize, Debug)] struct BuildResponse { builds2: Vec, } #[derive(Deserialize, Debug)] struct KBBuild { item0: KBItem, item1: KBItem, item2: KBItem, item3: KBItem, item4: KBItem, item5: KBItem, item6: KBItem, #[serde(rename = "startItem0")] start_item0: KBItem, #[serde(rename = "startItem1")] start_item1: KBItem, #[serde(rename = "startItem2")] start_item2: KBItem, #[serde(rename = "startItem3")] start_item3: KBItem, #[serde(rename = "startItem4")] start_item4: KBItem, #[serde(rename = "startItem5")] start_item5: KBItem, #[serde(rename = "skillOrder")] skill_order: String, wins: f64, games: f64, summoner: Summoner, } #[derive(Deserialize, Debug)] struct KBItem { #[serde(rename = "itemId")] item_id: u32, name: String, } #[derive(Deserialize, Debug)] struct Summoner { name: String, } impl KBDataSource { pub fn new(client: &ureq::Agent) -> KBDataSource { let mut datasource = KBDataSource { token: None, internal_classname_mapping: IndexMap::new(), }; datasource.token = datasource.get_auth_token(client); datasource.internal_classname_mapping = datasource.get_classname_mapping(client); datasource } // It will be better to use Result... fn get_auth_token(&self, client: &ureq::Agent) -> Option { let bundle = match client.get("https://koreanbuilds.net/bundle.js").call() { Ok(resp) => match resp.into_string() { Ok(val) => val, Err(_) => return None, }, Err(_) => return None, }; let regex = match Regex::new(r##"Authorization:\s*"(\w+)""##) { Ok(reg) => reg, Err(_) => return None, }; let result = match regex.captures(&bundle) { Some(res) => res, None => return None, }; return match result.get(1) { Some(token) => Some(token.as_str().to_string()), None => None, }; } fn get_classname_mapping(&self, client: &ureq::Agent) -> IndexMap { let mut mapping = IndexMap::new(); match self.get_champion_response(client) { Some(data) => { for champ in data.champions { mapping.insert(champ.classname, champ.name); } } None => { /* Nothing to do */ } }; mapping } fn get_champion_response(&self, client: &ureq::Agent) -> Option { let token = match self.token.clone() { Some(t) => t, None => return None, }; match client .get("https://api.koreanbuilds.net/champions?patchid=-1") .set("Accept", "application/json") .set("Authorization", token.as_str()) .call() { Ok(resp) => match resp.into_json() { Ok(val) => return val, Err(_) => return None, }, Err(_) => return None, }; } fn get_positions(position: Option) -> Vec { let mut positions = Vec::new(); match position { Some(pos) => { if pos.top > 0 { positions.push("TOP".to_owned()); } if pos.jungle > 0 { positions.push("JUNGLE".to_owned()); } if pos.mid > 0 { positions.push("MID".to_owned()); } if pos.bot > 0 { positions.push("BOT".to_owned()); } if pos.support > 0 { positions.push("SUPPORT".to_owned()); } } None => { /* Nothing to do */ } } positions } } impl DataSource for KBDataSource { fn get_alias(&self) -> &str { "KB" } fn get_champs_with_positions_and_patch( &self, client: &ureq::Agent, ) -> (IndexMap>, String) { let mut champions = IndexMap::new(); let data: ChampionResponse = match self.get_champion_response(client) { Some(val) => val, None => { return (champions, String::new()); } }; let patch = match data.patches.get(0) { Some(p) => p.patch_version.clone(), None => return (champions, String::new()), }; for champ in data.champions { champions.insert(champ.classname, KBDataSource::get_positions(champ.builds)); } (champions, patch) } fn get_champ_data_with_win_pourcentage( &self, id: &str, position: &str, client: &ureq::Agent, ) -> Option<(Vec, f64)> { let token = match self.token.clone() { Some(t) => t, None => return None, }; let map_id = match self.internal_classname_mapping.get(id) { Some(m_id) => m_id, None => return None, }; let data: BuildResponse = match client .get(&format!( "https://api.koreanbuilds.net/builds?chmpname={}&patchid=-2&position={}", map_id, position )) .set("Accept", "application/json") .set("Authorization", token.as_str()) .call() { Ok(resp) => match resp.into_json() { Ok(val) => val, Err(_) => { return None; } }, Err(_) => { return None; } }; let mut blocks = vec![]; let winrate = (data.builds2[0].wins / data.builds2[0].games) * 100.; let mut starting_items: Vec = vec![]; if data.builds2[0].start_item0.item_id != 0 { starting_items.push(Item { id: data.builds2[0].start_item0.item_id.to_string(), count: 1, }) } if data.builds2[0].start_item1.item_id != 0 { starting_items.push(Item { id: data.builds2[0].start_item1.item_id.to_string(), count: 1, }) } if data.builds2[0].start_item2.item_id != 0 { starting_items.push(Item { id: data.builds2[0].start_item2.item_id.to_string(), count: 1, }) } if data.builds2[0].start_item3.item_id != 0 { starting_items.push(Item { id: data.builds2[0].start_item3.item_id.to_string(), count: 1, }) } if data.builds2[0].start_item4.item_id != 0 { starting_items.push(Item { id: data.builds2[0].start_item4.item_id.to_string(), count: 1, }) } if data.builds2[0].start_item5.item_id != 0 { starting_items.push(Item { id: data.builds2[0].start_item5.item_id.to_string(), count: 1, }) } blocks.push(json!(Build { type_: format!( "Early game items | skillOrder : {}", data.builds2[0].skill_order ), items: starting_items })); let mut final_items: Vec = vec![]; if data.builds2[0].item0.item_id != 0 { final_items.push(Item { id: data.builds2[0].item0.item_id.to_string(), count: 1, }) } if data.builds2[0].item1.item_id != 0 { final_items.push(Item { id: data.builds2[0].item1.item_id.to_string(), count: 1, }) } if data.builds2[0].item2.item_id != 0 { final_items.push(Item { id: data.builds2[0].item2.item_id.to_string(), count: 1, }) } if data.builds2[0].item3.item_id != 0 { final_items.push(Item { id: data.builds2[0].item3.item_id.to_string(), count: 1, }) } if data.builds2[0].item4.item_id != 0 { final_items.push(Item { id: data.builds2[0].item4.item_id.to_string(), count: 1, }) } if data.builds2[0].item5.item_id != 0 { final_items.push(Item { id: data.builds2[0].item5.item_id.to_string(), count: 1, }) } if data.builds2[0].item6.item_id != 0 { final_items.push(Item { id: data.builds2[0].item6.item_id.to_string(), count: 1, }) } blocks.push(json!(Build { type_: format!( "Item order by time finished | Summoner : {}", data.builds2[0].summoner.name ), items: final_items })); Some((blocks, winrate)) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_get_auth_token() { let datasource = KBDataSource { token: None, internal_classname_mapping: IndexMap::new(), }; let client = ureq::AgentBuilder::new() .timeout(Duration::from_secs(10)) .build(); match datasource.get_auth_token(&client) { Some(token) => assert!(token.len() > 0), None => assert!(false), }; } #[test] fn test_get_champs_with_positions_and_patch() { let client = ureq::AgentBuilder::new() .timeout(Duration::from_secs(10)) .build(); let datasource = KBDataSource::new(&client); let champs_with_positions_and_patch = datasource.get_champs_with_positions_and_patch(&client); assert!(champs_with_positions_and_patch.0.len() > 0); } #[test] fn test_get_champ_data_with_win_pourcentage() { let client = ureq::AgentBuilder::new() .timeout(Duration::from_secs(10)) .build(); let datasource = KBDataSource::new(&client); let result = datasource.get_champ_data_with_win_pourcentage("Aatrox", "TOP", &client); assert!(result.is_some()); match result { Some(value) => { assert!(value.0.len() > 0); assert!(value.1 > 0.); } None => assert!(false), } } }