use crate::data_source::{Build, DataSource, Item, Stat}; use crate::ChampInfo; use crate::Champion as ChampionLoL; use crate::USER_AGENT_VALUE; use indexmap::IndexMap; use lazy_static::lazy_static; use serde_derive::Deserialize; use serde_json::{json, Value}; use std::time::Duration; pub struct KBDataSource { token: Option, } #[derive(Deserialize)] struct ChampionResponse { //patches: Vec, champions: Vec, } #[derive(Deserialize)] struct Patch { //enabled: bool, #[serde(rename = "patchVersion")] patch_version: String, //patchid: u32, //start: String, } #[derive(Deserialize)] struct Champion { id: u32, //name: String, //#[serde(rename = "className")] //classname: String, builds: Option, } #[derive(Deserialize)] struct Position { bot: u16, jungle: u16, mid: u16, support: u16, top: u16, } #[derive(Deserialize)] struct BuildResponse { builds2: Vec, } #[derive(Deserialize)] 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: u32, games: u32, kda: f32, summoner: Summoner, patch: Patch, } #[derive(Deserialize)] struct KBItem { #[serde(rename = "itemId")] item_id: u32, //name: String, } #[derive(Deserialize)] struct Summoner { name: String, } // It will be better to use Result... fn get_auth_token() -> Option { let client = ureq::AgentBuilder::new() .user_agent(USER_AGENT_VALUE) .timeout(Duration::from_secs(10)) .build(); 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(); bundle .find('"') .map(|position| (&bundle[..position]).to_string()) } impl KBDataSource { pub fn new() -> &'static KBDataSource { lazy_static! { static ref DATASOURCE: KBDataSource = KBDataSource { token: get_auth_token(), }; } &DATASOURCE } fn get_champion_response(&self, client: &ureq::Agent) -> Option { if let Some(token) = &self.token { 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 as f32 / build.games as f32) * 100f32, games: build.games, kda: build.kda, patch: build.patch.patch_version.to_owned(), }, ) } } impl DataSource for KBDataSource { fn get_alias(&self) -> &str { "KB" } fn get_timeout(&self) -> u64 { 300 } fn get_champs_with_positions( &self, client: &ureq::Agent, _champion: &ChampionLoL, ) -> IndexMap> { let mut champions = IndexMap::new(); let data: ChampionResponse = match self.get_champion_response(client) { Some(val) => val, None => { return champions; } }; for champ in data.champions { champions.insert(champ.id, KBDataSource::get_positions(champ.builds)); } champions } 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) = &self.token { let data: BuildResponse = match client .get(&format!( "https://api.koreanbuilds.net/builds?chmpname={}&patchid=-2&position=COMPOSITE", champ.name )) .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 { for b in &data.builds2 { if b.position.to_uppercase() == *pos { champ_data.push(self.get_build(b)); break; } } } } champ_data } } #[cfg(test)] mod tests { use super::*; #[test] fn test_get_auth_token() { match get_auth_token() { Some(token) => assert!(token.len() > 0), None => assert!(false), }; } #[test] fn test_get_champs_with_positions_and_patch() { let client = ureq::AgentBuilder::new() .user_agent(USER_AGENT_VALUE) .timeout(Duration::from_secs(10)) .build(); let datasource = KBDataSource::new(); let champion = ChampionLoL { data: IndexMap::new(), }; let champs_with_positions = datasource.get_champs_with_positions(&client, &champion); assert!(champs_with_positions.len() > 0); } #[test] fn test_get_champ_data_with_win_pourcentage() { let client = ureq::AgentBuilder::new() .user_agent(USER_AGENT_VALUE) .timeout(Duration::from_secs(10)) .build(); let datasource = KBDataSource::new(); 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.); } }