use crate::data_source::{Build, DataSource, Item, Stat}; #[cfg(test)] use crate::time::Duration; use crate::ChampInfo; use indexmap::IndexMap; use lazy_static::lazy_static; use serde_derive::Deserialize; use serde_json::{json, Value}; use std::sync::Mutex; pub struct KBDataSource; #[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 { position: String, 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, } lazy_static! { static ref TOKEN: Mutex> = Mutex::new(None); } impl KBDataSource { // It will be better to use Result... fn get_auth_token(&self, client: &ureq::Agent) -> Option { let mut 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 auth_position = match bundle.find("Authorization") { Some(position) => position, None => return None, }; bundle = (&bundle[(auth_position + 13)..]).to_string(); let q_position = match bundle.find('"') { Some(position) => position, None => return None, }; bundle = (&bundle[(q_position + 1)..]).to_string(); match bundle.find('"') { Some(position) => Some((&bundle[..position]).to_string()), None => None, } } fn get_champion_response(&self, client: &ureq::Agent) -> Option { if let Some(token) = TOKEN.lock().unwrap().as_ref() { return 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) => val, Err(_) => None, }, Err(_) => None, }; } None } fn get_positions(position: Option) -> Vec { let mut positions = Vec::new(); if let Some(pos) = position { 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()); } } // TODO: find better solution, activate all positions for retrieve older builds if positions.is_empty() { positions.push("TOP".to_owned()); positions.push("JUNGLE".to_owned()); positions.push("MID".to_owned()); positions.push("BOT".to_owned()); positions.push("SUPPORT".to_owned()); } positions } fn get_build(&self, build: &KBBuild) -> (String, Vec, Stat) { let mut starting_items: Vec = vec![]; let mut blocks = vec![]; if build.start_item0.item_id != 0 { starting_items.push(Item { id: build.start_item0.item_id.to_string(), count: 1, }) } if build.start_item1.item_id != 0 { starting_items.push(Item { id: build.start_item1.item_id.to_string(), count: 1, }) } if build.start_item2.item_id != 0 { starting_items.push(Item { id: build.start_item2.item_id.to_string(), count: 1, }) } if build.start_item3.item_id != 0 { starting_items.push(Item { id: build.start_item3.item_id.to_string(), count: 1, }) } if build.start_item4.item_id != 0 { starting_items.push(Item { id: build.start_item4.item_id.to_string(), count: 1, }) } if build.start_item5.item_id != 0 { starting_items.push(Item { id: build.start_item5.item_id.to_string(), count: 1, }) } blocks.push(json!(Build { type_: format!("Early game items | skillOrder : {}", build.skill_order), items: starting_items })); let mut final_items: Vec = vec![]; if build.item0.item_id != 0 { final_items.push(Item { id: build.item0.item_id.to_string(), count: 1, }) } if build.item1.item_id != 0 { final_items.push(Item { id: build.item1.item_id.to_string(), count: 1, }) } if build.item2.item_id != 0 { final_items.push(Item { id: build.item2.item_id.to_string(), count: 1, }) } if build.item3.item_id != 0 { final_items.push(Item { id: build.item3.item_id.to_string(), count: 1, }) } if build.item4.item_id != 0 { final_items.push(Item { id: build.item4.item_id.to_string(), count: 1, }) } if build.item5.item_id != 0 { final_items.push(Item { id: build.item5.item_id.to_string(), count: 1, }) } if build.item6.item_id != 0 { final_items.push(Item { id: build.item6.item_id.to_string(), count: 1, }) } blocks.push(json!(Build { type_: format!( "Item order by time finished | Summoner : {}", build.summoner.name ), items: final_items })); ( build.position.to_owned().to_uppercase(), blocks, Stat { win_rate: (build.wins / build.games) * 100., games: build.games as u32, kda: 0.0, }, ) } } impl DataSource for KBDataSource { fn init(&self, client: &ureq::Agent) { if let Some(t) = self.get_auth_token(client) { TOKEN.lock().unwrap().replace(t); } } fn get_alias(&self) -> &str { "KB" } fn get_timeout(&self) -> u64 { 300 } 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.id, KBDataSource::get_positions(champ.builds)); } (champions, patch) } fn get_champ_data_with_win_pourcentage( &self, champ: &ChampInfo, position: &[String], client: &ureq::Agent, ) -> Vec<(String, Vec, Stat)> { let mut champ_data = vec![]; if let Some(token) = TOKEN.lock().unwrap().as_ref() { let data: BuildResponse = match client .get(&format!( "https://api.koreanbuilds.net/builds?chmpname={}&patchid=-2&position=COMPOSITE", champ.id )) .set("Accept", "application/json") .set("Authorization", token.as_str()) .call() { Ok(resp) => match resp.into_json() { Ok(val) => val, Err(_) => { return vec![]; } }, Err(_) => { return vec![]; } }; for pos in position { let mut build: Option<&KBBuild> = None; for b in &data.builds2 { if b.position.to_uppercase() == *pos { build = Some(b); break; } } if let Some(b) = build { champ_data.push(self.get_build(&b)); } } } champ_data } } #[cfg(test)] mod tests { use super::*; #[test] fn test_get_auth_token() { let datasource = KBDataSource; 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; datasource.init(&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; datasource.init(&client); let champ = ChampInfo { id: String::from("Annie"), name: String::from("Annie"), key: String::from("1"), }; let result = datasource.get_champ_data_with_win_pourcentage( &champ, &vec!["MID".to_string()], &client, ); assert!(!result.is_empty()); assert!(!result[0].1.is_empty()); assert!(result[0].2.win_rate > 0.); } }