346 lines
9.5 KiB
Rust
346 lines
9.5 KiB
Rust
use crate::ChampInfo;
|
|
use crate::Champion as ChampionLoL;
|
|
use crate::data_source::{Data, DataSource, Item, Stat};
|
|
use indexmap::IndexMap;
|
|
use log::error;
|
|
use serde_derive::Deserialize;
|
|
|
|
pub struct KBDataSource {
|
|
client: ureq::Agent,
|
|
token: Option<String>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct ChampionResponse {
|
|
//patches: Vec<Patch>,
|
|
champions: Vec<Champion>,
|
|
}
|
|
|
|
#[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<Position>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct Position {
|
|
bot: u16,
|
|
jungle: u16,
|
|
mid: u16,
|
|
support: u16,
|
|
top: u16,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct BuildResponse {
|
|
builds3: Vec<KBBuild>,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct KBBuild {
|
|
position: PositionResponse,
|
|
#[serde(rename = "itemSets")]
|
|
item_sets: Vec<ItemSets>,
|
|
#[serde(rename = "strItemSets")]
|
|
str_item_sets: Vec<StrItemSets>,
|
|
#[serde(rename = "skillSets")]
|
|
skill_sets: Vec<SkillSet>,
|
|
wins: u32,
|
|
games: u32,
|
|
kda: f32,
|
|
summoner: Summoner,
|
|
patch: Patch,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct PositionResponse {
|
|
#[serde(rename = "positionName")]
|
|
position_name: String,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct SkillSet {
|
|
#[serde(rename = "skillOrder")]
|
|
skill_order: String,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct ItemSets {
|
|
item0: KBItem,
|
|
item1: KBItem,
|
|
item2: KBItem,
|
|
item3: KBItem,
|
|
item4: KBItem,
|
|
item5: KBItem,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct StrItemSets {
|
|
#[serde(rename = "strItem0")]
|
|
str_item0: KBItem,
|
|
#[serde(rename = "strItem1")]
|
|
str_item1: KBItem,
|
|
#[serde(rename = "strItem2")]
|
|
str_item2: KBItem,
|
|
#[serde(rename = "strItem3")]
|
|
str_item3: KBItem,
|
|
#[serde(rename = "strItem4")]
|
|
str_item4: KBItem,
|
|
#[serde(rename = "strItem5")]
|
|
str_item5: KBItem,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct KBItem {
|
|
#[serde(rename = "itemId")]
|
|
item_id: u32,
|
|
//name: String,
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
struct Summoner {
|
|
name: String,
|
|
}
|
|
|
|
impl KBDataSource {
|
|
pub fn new(client: &ureq::Agent) -> Self {
|
|
Self {
|
|
client: client.clone(),
|
|
token: Self::fetch_auth_token(client),
|
|
}
|
|
}
|
|
|
|
fn fetch_auth_token(client: &ureq::Agent) -> Option<String> {
|
|
let resp = client.get("https://koreanbuilds.net/bundle.js").call();
|
|
if let Ok(mut resp) = resp {
|
|
if let Ok(bundle) = resp.body_mut().read_to_string() {
|
|
if let Some(token) = Self::extract_token(&bundle) {
|
|
return Some(token);
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn extract_token(bundle: &str) -> Option<String> {
|
|
let auth_marker = "Authorization";
|
|
let start = bundle.find(auth_marker)? + auth_marker.len();
|
|
let after_marker = bundle[start..].find('"')? + start + 1;
|
|
let end = bundle[after_marker..].find('"')? + after_marker;
|
|
Some(bundle[after_marker..end].to_owned())
|
|
}
|
|
|
|
fn get_champion_response(&self) -> Option<ChampionResponse> {
|
|
if let Some(token) = &self.token {
|
|
return match self
|
|
.client
|
|
.get("https://api.koreanbuilds.net/champions?patchid=-1")
|
|
.header("Accept", "application/json")
|
|
.header("Authorization", token.as_str())
|
|
.call()
|
|
{
|
|
Ok(mut resp) => resp.body_mut().read_json().unwrap_or_default(),
|
|
Err(_) => None,
|
|
};
|
|
}
|
|
None
|
|
}
|
|
|
|
fn get_positions(position: Option<Position>) -> Vec<String> {
|
|
let positions = vec!["TOP", "JUNGLE", "MID", "BOT", "SUPPORT"];
|
|
if let Some(pos) = position {
|
|
positions
|
|
.into_iter()
|
|
.zip([pos.top, pos.jungle, pos.mid, pos.bot, pos.support])
|
|
.filter_map(|(name, count)| {
|
|
if count > 0 {
|
|
Some(name.to_owned())
|
|
} else {
|
|
None
|
|
}
|
|
})
|
|
.collect()
|
|
} else {
|
|
positions.into_iter().map(|s| s.to_owned()).collect()
|
|
}
|
|
}
|
|
|
|
fn get_build(&self, build: &KBBuild) -> Data {
|
|
let mut starting_items: Vec<Item> = vec![];
|
|
let mut blocks = vec![];
|
|
for i in 0..6 {
|
|
let item_id = match i {
|
|
0 => build.str_item_sets[0].str_item0.item_id,
|
|
1 => build.str_item_sets[0].str_item1.item_id,
|
|
2 => build.str_item_sets[0].str_item2.item_id,
|
|
3 => build.str_item_sets[0].str_item3.item_id,
|
|
4 => build.str_item_sets[0].str_item4.item_id,
|
|
5 => build.str_item_sets[0].str_item5.item_id,
|
|
_ => 0,
|
|
};
|
|
if item_id != 0 {
|
|
starting_items.push(Item {
|
|
id: item_id.to_string(),
|
|
count: 1,
|
|
});
|
|
}
|
|
}
|
|
blocks.push(self.make_item_set(
|
|
starting_items.iter().map(|item| item.id.clone()).collect(),
|
|
format!(
|
|
"Early game items | skillOrder : {}",
|
|
build.skill_sets[0].skill_order
|
|
),
|
|
));
|
|
let mut final_items: Vec<Item> = vec![];
|
|
for item in [
|
|
&build.item_sets[0].item0,
|
|
&build.item_sets[0].item1,
|
|
&build.item_sets[0].item2,
|
|
&build.item_sets[0].item3,
|
|
&build.item_sets[0].item4,
|
|
&build.item_sets[0].item5,
|
|
] {
|
|
if item.item_id != 0 {
|
|
final_items.push(Item {
|
|
id: item.item_id.to_string(),
|
|
count: 1,
|
|
});
|
|
}
|
|
}
|
|
blocks.push(self.make_item_set(
|
|
final_items.iter().map(|item| item.id.clone()).collect(),
|
|
format!(
|
|
"Item order by time finished | Summoner : {}",
|
|
build.summoner.name
|
|
),
|
|
));
|
|
|
|
Data {
|
|
position: build.position.position_name.to_uppercase(),
|
|
items: blocks,
|
|
stat: 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_champs_with_positions(&self, _champion: &ChampionLoL) -> IndexMap<u32, Vec<String>> {
|
|
let mut champions = IndexMap::new();
|
|
let data: ChampionResponse = match self.get_champion_response() {
|
|
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],
|
|
) -> Vec<Data> {
|
|
let mut champ_data = vec![];
|
|
if let Some(token) = &self.token {
|
|
let url = format!(
|
|
"https://api.koreanbuilds.net/builds?chmpname={}&patchid=-2&position=COMPOSITE",
|
|
champ.name.replace(" ", "%20")
|
|
);
|
|
let data: BuildResponse = match self
|
|
.client
|
|
.get(&url)
|
|
.header("Accept", "application/json")
|
|
.header("Authorization", token.as_str())
|
|
.call()
|
|
{
|
|
Ok(mut resp) => match resp.body_mut().read_json() {
|
|
Ok(val) => val,
|
|
Err(x) => {
|
|
error!("Cant json: {x}");
|
|
return vec![];
|
|
}
|
|
},
|
|
Err(x) => {
|
|
error!("Call failed for URL: {url}, error: {x}");
|
|
return vec![];
|
|
}
|
|
};
|
|
|
|
for pos in position {
|
|
for b in &data.builds3 {
|
|
if b.position.position_name.to_uppercase() == *pos {
|
|
champ_data.push(self.get_build(b));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
champ_data
|
|
}
|
|
}
|
|
#[cfg(test)]
|
|
mod tests {
|
|
|
|
use super::*;
|
|
use crate::create_http_client;
|
|
use std::sync::LazyLock;
|
|
|
|
static DATASOURCE: LazyLock<KBDataSource> =
|
|
LazyLock::new(|| KBDataSource::new(&create_http_client()));
|
|
|
|
#[test]
|
|
fn test_get_auth_token() {
|
|
match &DATASOURCE.token {
|
|
Some(token) => assert!(token.len() > 0),
|
|
None => assert!(false),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_champs_with_positions_and_patch() {
|
|
let champion = ChampionLoL {
|
|
data: IndexMap::new(),
|
|
};
|
|
let champs_with_positions = DATASOURCE.get_champs_with_positions(&champion);
|
|
assert!(champs_with_positions.len() > 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_get_champ_data_with_win_pourcentage() {
|
|
let champ = ChampInfo {
|
|
id: String::from("Annie"),
|
|
name: String::from("Annie"),
|
|
key: 1,
|
|
};
|
|
let result =
|
|
DATASOURCE.get_champ_data_with_win_pourcentage(&champ, &vec!["MID".to_owned()]);
|
|
assert!(!result.is_empty());
|
|
assert!(!result[0].items.is_empty());
|
|
assert!(result[0].stat.win_rate > 0.);
|
|
}
|
|
}
|