2021-03-14 14:25:37 +01:00
|
|
|
use crate::data_source::{Build, DataSource, Item, Stat};
|
2021-03-10 12:22:00 +01:00
|
|
|
#[cfg(test)]
|
|
|
|
use crate::time::Duration;
|
2021-03-14 11:21:49 +01:00
|
|
|
use crate::ChampInfo;
|
2019-09-30 17:56:03 +02:00
|
|
|
use indexmap::IndexMap;
|
|
|
|
use serde_derive::Deserialize;
|
|
|
|
use serde_json::{json, Value};
|
|
|
|
|
|
|
|
pub struct KBDataSource {
|
|
|
|
token: Option<String>,
|
|
|
|
internal_classname_mapping: IndexMap<String, String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
struct ChampionResponse {
|
|
|
|
patches: Vec<Patch>,
|
|
|
|
champions: Vec<Champion>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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<Position>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
struct Position {
|
|
|
|
bot: u16,
|
|
|
|
jungle: u16,
|
|
|
|
mid: u16,
|
|
|
|
support: u16,
|
|
|
|
top: u16,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
struct BuildResponse {
|
|
|
|
builds2: Vec<KBBuild>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Deserialize, Debug)]
|
|
|
|
struct KBBuild {
|
2021-03-15 23:00:20 +01:00
|
|
|
position: String,
|
2019-09-30 17:56:03 +02:00
|
|
|
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 {
|
2021-03-10 12:22:00 +01:00
|
|
|
pub fn new(client: &ureq::Agent) -> KBDataSource {
|
2019-09-30 17:56:03 +02:00
|
|
|
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...
|
2021-03-14 18:10:51 +01:00
|
|
|
fn get_auth_token(&self, client: &ureq::Agent) -> Option<String> {
|
2021-03-14 16:43:29 +01:00
|
|
|
let mut bundle = match client.get("https://koreanbuilds.net/bundle.js").call() {
|
2021-03-10 12:22:00 +01:00
|
|
|
Ok(resp) => match resp.into_string() {
|
2019-09-30 17:56:03 +02:00
|
|
|
Ok(val) => val,
|
|
|
|
Err(_) => return None,
|
|
|
|
},
|
|
|
|
Err(_) => return None,
|
|
|
|
};
|
2021-03-14 16:43:29 +01:00
|
|
|
let auth_position = match bundle.find("Authorization") {
|
|
|
|
Some(position) => position,
|
|
|
|
None => return None,
|
2019-09-30 17:56:03 +02:00
|
|
|
};
|
2021-03-14 16:43:29 +01:00
|
|
|
bundle = (&bundle[(auth_position + 13)..]).to_string();
|
2021-03-14 17:17:29 +01:00
|
|
|
let q_position = match bundle.find('"') {
|
2021-03-14 16:43:29 +01:00
|
|
|
Some(position) => position,
|
2019-09-30 17:56:03 +02:00
|
|
|
None => return None,
|
|
|
|
};
|
2021-03-14 16:43:29 +01:00
|
|
|
bundle = (&bundle[(q_position + 1)..]).to_string();
|
2021-03-14 17:17:29 +01:00
|
|
|
match bundle.find('"') {
|
2021-03-14 16:43:29 +01:00
|
|
|
Some(position) => Some((&bundle[..position]).to_string()),
|
2019-09-30 17:56:03 +02:00
|
|
|
None => None,
|
2021-03-14 17:17:29 +01:00
|
|
|
}
|
2021-03-14 18:10:51 +01:00
|
|
|
}
|
2019-09-30 17:56:03 +02:00
|
|
|
|
2021-03-10 12:22:00 +01:00
|
|
|
fn get_classname_mapping(&self, client: &ureq::Agent) -> IndexMap<String, String> {
|
2019-09-30 17:56:03 +02:00
|
|
|
let mut mapping = IndexMap::new();
|
2021-03-11 00:00:04 +01:00
|
|
|
if let Some(data) = self.get_champion_response(client) {
|
|
|
|
for champ in data.champions {
|
|
|
|
mapping.insert(champ.classname, champ.name);
|
2019-09-30 17:56:03 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
mapping
|
|
|
|
}
|
|
|
|
|
2021-03-10 12:22:00 +01:00
|
|
|
fn get_champion_response(&self, client: &ureq::Agent) -> Option<ChampionResponse> {
|
2019-09-30 17:56:03 +02:00
|
|
|
let token = match self.token.clone() {
|
|
|
|
Some(t) => t,
|
2021-03-11 00:00:04 +01:00
|
|
|
None => String::new(),
|
2019-09-30 17:56:03 +02:00
|
|
|
};
|
|
|
|
match client
|
|
|
|
.get("https://api.koreanbuilds.net/champions?patchid=-1")
|
2021-03-10 12:22:00 +01:00
|
|
|
.set("Accept", "application/json")
|
|
|
|
.set("Authorization", token.as_str())
|
|
|
|
.call()
|
2019-09-30 17:56:03 +02:00
|
|
|
{
|
2021-03-10 12:22:00 +01:00
|
|
|
Ok(resp) => match resp.into_json() {
|
2021-03-11 00:00:04 +01:00
|
|
|
Ok(val) => val,
|
|
|
|
Err(_) => None,
|
2019-09-30 17:56:03 +02:00
|
|
|
},
|
2021-03-11 00:00:04 +01:00
|
|
|
Err(_) => None,
|
|
|
|
}
|
2019-09-30 17:56:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
fn get_positions(position: Option<Position>) -> Vec<String> {
|
|
|
|
let mut positions = Vec::new();
|
2021-03-11 00:00:04 +01:00
|
|
|
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());
|
2019-09-30 17:56:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
positions
|
|
|
|
}
|
2021-03-15 23:00:20 +01:00
|
|
|
|
|
|
|
fn get_build(&self, build: &KBBuild) -> (String, Vec<Value>, Stat) {
|
|
|
|
let mut starting_items: Vec<Item> = 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<Item> = 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,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2019-09-30 17:56:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
impl DataSource for KBDataSource {
|
|
|
|
fn get_alias(&self) -> &str {
|
|
|
|
"KB"
|
|
|
|
}
|
|
|
|
|
2021-03-14 09:49:18 +01:00
|
|
|
fn get_timeout(&self) -> u64 {
|
|
|
|
300
|
|
|
|
}
|
|
|
|
|
2019-09-30 17:56:03 +02:00
|
|
|
fn get_champs_with_positions_and_patch(
|
|
|
|
&self,
|
2021-03-10 12:22:00 +01:00
|
|
|
client: &ureq::Agent,
|
2019-09-30 17:56:03 +02:00
|
|
|
) -> (IndexMap<String, Vec<String>>, 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,
|
2021-03-13 23:02:26 +01:00
|
|
|
champ: &ChampInfo,
|
2021-03-15 23:11:40 +01:00
|
|
|
position: &[String],
|
2021-03-10 12:22:00 +01:00
|
|
|
client: &ureq::Agent,
|
2021-03-15 23:00:20 +01:00
|
|
|
) -> Vec<(String, Vec<Value>, Stat)> {
|
|
|
|
let mut champ_data = vec![];
|
|
|
|
if let Some(token) = self.token.clone() {
|
|
|
|
if let Some(map_id) = self.internal_classname_mapping.get(&champ.id) {
|
|
|
|
let data: BuildResponse = match client
|
|
|
|
.get(&format!(
|
|
|
|
"https://api.koreanbuilds.net/builds?chmpname={}&patchid=-2&position=COMPOSITE",
|
|
|
|
map_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 {
|
2021-03-15 23:11:40 +01:00
|
|
|
if b.position.to_uppercase() == *pos {
|
2021-03-15 23:00:20 +01:00
|
|
|
build = Some(b);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(b) = build {
|
|
|
|
champ_data.push(self.get_build(&b));
|
|
|
|
}
|
2019-09-30 17:56:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
2021-03-15 23:00:20 +01:00
|
|
|
champ_data
|
2019-09-30 17:56:03 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_auth_token() {
|
|
|
|
let datasource = KBDataSource {
|
|
|
|
token: None,
|
|
|
|
internal_classname_mapping: IndexMap::new(),
|
|
|
|
};
|
2021-03-10 12:22:00 +01:00
|
|
|
let client = ureq::AgentBuilder::new()
|
2019-09-30 17:56:03 +02:00
|
|
|
.timeout(Duration::from_secs(10))
|
2021-03-10 12:22:00 +01:00
|
|
|
.build();
|
2019-09-30 17:56:03 +02:00
|
|
|
match datasource.get_auth_token(&client) {
|
|
|
|
Some(token) => assert!(token.len() > 0),
|
|
|
|
None => assert!(false),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_get_champs_with_positions_and_patch() {
|
2021-03-10 12:22:00 +01:00
|
|
|
let client = ureq::AgentBuilder::new()
|
2019-09-30 17:56:03 +02:00
|
|
|
.timeout(Duration::from_secs(10))
|
2021-03-10 12:22:00 +01:00
|
|
|
.build();
|
2019-09-30 17:56:03 +02:00
|
|
|
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() {
|
2021-03-10 12:22:00 +01:00
|
|
|
let client = ureq::AgentBuilder::new()
|
2019-09-30 17:56:03 +02:00
|
|
|
.timeout(Duration::from_secs(10))
|
2021-03-10 12:22:00 +01:00
|
|
|
.build();
|
2019-09-30 17:56:03 +02:00
|
|
|
let datasource = KBDataSource::new(&client);
|
2021-03-14 11:21:49 +01:00
|
|
|
let champ = ChampInfo {
|
|
|
|
id: String::from("Aatrox"),
|
|
|
|
name: String::from("Aatrox"),
|
|
|
|
key: String::from("1"),
|
|
|
|
};
|
2021-03-15 23:00:20 +01:00
|
|
|
let result = datasource.get_champ_data_with_win_pourcentage(
|
|
|
|
&champ,
|
|
|
|
&vec!["TOP".to_string()],
|
|
|
|
&client,
|
|
|
|
);
|
|
|
|
assert!(!result.is_empty());
|
|
|
|
assert!(!result[0].1.is_empty());
|
|
|
|
assert!(result[0].2.win_rate > 0.);
|
2019-09-30 17:56:03 +02:00
|
|
|
}
|
|
|
|
}
|