split data source to trait and impl
This commit is contained in:
parent
4b9ba0aa8f
commit
8e8b498765
3 changed files with 232 additions and 175 deletions
90
src/cgg_data_source.rs
Normal file
90
src/cgg_data_source.rs
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
extern crate indexmap;
|
||||||
|
extern crate lazy_static;
|
||||||
|
extern crate log;
|
||||||
|
extern crate regex;
|
||||||
|
extern crate reqwest;
|
||||||
|
extern crate select;
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
extern crate simple_logger;
|
||||||
|
extern crate winreg;
|
||||||
|
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use regex::Regex;
|
||||||
|
use select::document::Document;
|
||||||
|
use select::predicate::{Class, Name};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use data_source::DataSource;
|
||||||
|
|
||||||
|
pub struct CGGDataSource;
|
||||||
|
impl DataSource for CGGDataSource {
|
||||||
|
fn get_champs_with_positions_and_patch(
|
||||||
|
client: &reqwest::Client,
|
||||||
|
) -> (IndexMap<String, Vec<String>>, String) {
|
||||||
|
let page = client
|
||||||
|
.get("https://champion.gg")
|
||||||
|
.send()
|
||||||
|
.unwrap()
|
||||||
|
.text()
|
||||||
|
.unwrap();
|
||||||
|
let document = Document::from(&*page);
|
||||||
|
|
||||||
|
let patch = document
|
||||||
|
.find(Class("analysis-holder"))
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.find(Name("strong"))
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.text();
|
||||||
|
|
||||||
|
let mut champions = IndexMap::new();
|
||||||
|
for node in document.find(Class("champ-height")) {
|
||||||
|
let id = node
|
||||||
|
.find(Class("home-champion"))
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.attr("class")
|
||||||
|
.unwrap()
|
||||||
|
.split(' ')
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.to_string();
|
||||||
|
let positions = node
|
||||||
|
.find(Name("a"))
|
||||||
|
.skip(1)
|
||||||
|
.map(|x| {
|
||||||
|
x.attr("href")
|
||||||
|
.unwrap()
|
||||||
|
.split('/')
|
||||||
|
.last()
|
||||||
|
.unwrap()
|
||||||
|
.to_string()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
champions.insert(id, positions);
|
||||||
|
}
|
||||||
|
|
||||||
|
(champions, patch)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_champ_data(id: &str, position: &str, client: &reqwest::Client) -> Option<Value> {
|
||||||
|
let mut req = client
|
||||||
|
.get(&format!(
|
||||||
|
"https://champion.gg/champion/{}/{}?league=",
|
||||||
|
id, position
|
||||||
|
))
|
||||||
|
.send()
|
||||||
|
.unwrap();
|
||||||
|
if req.status().is_success() {
|
||||||
|
lazy_static! {
|
||||||
|
static ref RE: Regex =
|
||||||
|
Regex::new(r"(?m)^\s+matchupData\.championData = (.*)$").unwrap();
|
||||||
|
}
|
||||||
|
serde_json::from_str(&RE.captures(&req.text().unwrap())?[1]).unwrap()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
135
src/data_source.rs
Normal file
135
src/data_source.rs
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
extern crate indexmap;
|
||||||
|
extern crate lazy_static;
|
||||||
|
extern crate log;
|
||||||
|
extern crate regex;
|
||||||
|
extern crate reqwest;
|
||||||
|
extern crate select;
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
extern crate simple_logger;
|
||||||
|
extern crate winreg;
|
||||||
|
|
||||||
|
use indexmap::IndexMap;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::fs;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
struct ItemSet {
|
||||||
|
title: String,
|
||||||
|
#[serde(rename = "type")]
|
||||||
|
type_: String,
|
||||||
|
map: String,
|
||||||
|
mode: String,
|
||||||
|
priority: bool,
|
||||||
|
sortrank: u32,
|
||||||
|
blocks: Vec<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
const CONSUMABLES: [u32; 9] = [2003, 2004, 2055, 2031, 2032, 2033, 2138, 2140, 2139];
|
||||||
|
const TRINKETS: [u32; 3] = [3340, 3364, 3363];
|
||||||
|
const ITEM_TYPES: &'static [(&str, [&str; 2]); 4] = &[
|
||||||
|
("Most Frequent Starters", ["firstItems", "mostGames"]),
|
||||||
|
(
|
||||||
|
"Highest Win % Starters",
|
||||||
|
["firstItems", "highestWinPercent"],
|
||||||
|
),
|
||||||
|
("Most Frequent Core Build", ["items", "mostGames"]),
|
||||||
|
("Highest Win % Core Build", ["items", "highestWinPercent"]),
|
||||||
|
];
|
||||||
|
|
||||||
|
pub trait DataSource {
|
||||||
|
fn get_champs_with_positions_and_patch(
|
||||||
|
client: &reqwest::Client,
|
||||||
|
) -> (IndexMap<String, Vec<String>>, String);
|
||||||
|
fn get_champ_data(id: &str, position: &str, client: &reqwest::Client) -> Option<Value>;
|
||||||
|
|
||||||
|
fn make_item_set(data: &Value, label: &str) -> Value {
|
||||||
|
json!({
|
||||||
|
"items": data["items"].as_array().unwrap().iter().map(|x| json!({"id": x["id"].as_str(), "count": 1})).collect::<Vec<Value>>(),
|
||||||
|
"type": format!("{} ({:.2}% - {} games)", label, data["winPercent"].as_f64().unwrap() * 100., data["games"].as_u64().unwrap())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn make_item_set_from_list(list: &Vec<u32>, label: &str, key: &str, data: &Value) -> Value {
|
||||||
|
let mut key_order = String::new();
|
||||||
|
if !data["skills"].get("skillInfo").is_none() {
|
||||||
|
key_order = data["skills"][key]["order"]
|
||||||
|
.as_array()
|
||||||
|
.unwrap()
|
||||||
|
.iter()
|
||||||
|
.map(|x| {
|
||||||
|
data["skills"]["skillInfo"].as_array().unwrap()
|
||||||
|
[x.as_str().unwrap().parse::<usize>().unwrap() - 1]["key"]
|
||||||
|
.as_str()
|
||||||
|
.unwrap()
|
||||||
|
})
|
||||||
|
.collect::<Vec<&str>>()
|
||||||
|
.join(".");
|
||||||
|
}
|
||||||
|
json!({
|
||||||
|
"items": list.iter().map(|x| json!({"id": x.to_string(), "count": 1})).collect::<Vec<Value>>(),
|
||||||
|
"type": format!("{} {}", label, key_order)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn write_item_set(
|
||||||
|
id: &str,
|
||||||
|
name: &str,
|
||||||
|
pos: &str,
|
||||||
|
ver: &str,
|
||||||
|
path: &PathBuf,
|
||||||
|
client: &reqwest::Client,
|
||||||
|
) {
|
||||||
|
info!("Retrieving data for {} at {}", name, pos);
|
||||||
|
let data = Self::get_champ_data(id, pos, client);
|
||||||
|
|
||||||
|
match data {
|
||||||
|
Some(data) => {
|
||||||
|
let mut item_set = ItemSet {
|
||||||
|
title: format!(
|
||||||
|
"CGG {} {} - {:.2}%",
|
||||||
|
pos,
|
||||||
|
ver,
|
||||||
|
data["stats"]["winRate"].as_f64().unwrap() * 100.
|
||||||
|
),
|
||||||
|
type_: "custom".to_string(),
|
||||||
|
map: "any".to_string(),
|
||||||
|
mode: "any".to_string(),
|
||||||
|
priority: false,
|
||||||
|
sortrank: 0,
|
||||||
|
blocks: vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
for (label, path) in ITEM_TYPES.iter() {
|
||||||
|
if !data[&path[0]].get(&path[1]).is_none() {
|
||||||
|
item_set
|
||||||
|
.blocks
|
||||||
|
.push(Self::make_item_set(&data[&path[0]][&path[1]], label));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item_set.blocks.push(Self::make_item_set_from_list(
|
||||||
|
&CONSUMABLES.to_vec(),
|
||||||
|
"Consumables | Frequent:",
|
||||||
|
"mostGames",
|
||||||
|
&data,
|
||||||
|
));
|
||||||
|
item_set.blocks.push(Self::make_item_set_from_list(
|
||||||
|
&TRINKETS.to_vec(),
|
||||||
|
"Trinkets | Wins:",
|
||||||
|
"highestWinPercent",
|
||||||
|
&data,
|
||||||
|
));
|
||||||
|
|
||||||
|
info!("Writing item set for {} at {}", name, pos);
|
||||||
|
fs::write(
|
||||||
|
path.join(format!("CGG_{}_{}.json", id, pos)),
|
||||||
|
serde_json::to_string_pretty(&item_set).unwrap(),
|
||||||
|
).unwrap();
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
error!("Can't get data for {} at {}", id, pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
182
src/main.rs
182
src/main.rs
|
@ -14,12 +14,13 @@ extern crate lazy_static;
|
||||||
extern crate indexmap;
|
extern crate indexmap;
|
||||||
extern crate winreg;
|
extern crate winreg;
|
||||||
|
|
||||||
|
mod cgg_data_source;
|
||||||
|
mod data_source;
|
||||||
|
|
||||||
|
use cgg_data_source::CGGDataSource;
|
||||||
|
use data_source::DataSource;
|
||||||
use indexmap::IndexMap;
|
use indexmap::IndexMap;
|
||||||
use regex::Regex;
|
|
||||||
use reqwest::header::{Headers, UserAgent};
|
use reqwest::header::{Headers, UserAgent};
|
||||||
use select::document::Document;
|
|
||||||
use select::predicate::{Class, Name};
|
|
||||||
use serde_json::Value;
|
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::{fs, thread, time};
|
use std::{fs, thread, time};
|
||||||
|
@ -41,32 +42,9 @@ struct ChampInfo {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
|
||||||
struct ItemSet {
|
|
||||||
title: String,
|
|
||||||
#[serde(rename = "type")]
|
|
||||||
type_: String,
|
|
||||||
map: String,
|
|
||||||
mode: String,
|
|
||||||
priority: bool,
|
|
||||||
sortrank: u32,
|
|
||||||
blocks: Vec<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
const USER_AGENT: &str =
|
const USER_AGENT: &str =
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0";
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0";
|
||||||
const LOL_CHAMPS_DIR: &str = ".\\champs";
|
const LOL_CHAMPS_DIR: &str = ".\\champs";
|
||||||
const CONSUMABLES: [u32; 9] = [2003, 2004, 2055, 2031, 2032, 2033, 2138, 2140, 2139];
|
|
||||||
const TRINKETS: [u32; 3] = [3340, 3364, 3363];
|
|
||||||
const ITEM_TYPES: &'static [(&str, [&str; 2]); 4] = &[
|
|
||||||
("Most Frequent Starters", ["firstItems", "mostGames"]),
|
|
||||||
(
|
|
||||||
"Highest Win % Starters",
|
|
||||||
["firstItems", "highestWinPercent"],
|
|
||||||
),
|
|
||||||
("Most Frequent Core Build", ["items", "mostGames"]),
|
|
||||||
("Highest Win % Core Build", ["items", "highestWinPercent"]),
|
|
||||||
];
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
simple_logger::init_with_level(log::Level::Info).unwrap();
|
simple_logger::init_with_level(log::Level::Info).unwrap();
|
||||||
|
@ -107,7 +85,7 @@ fn main() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
info!("LoL numbers of champs: {}", champion.data.len());
|
info!("LoL numbers of champs: {}", champion.data.len());
|
||||||
|
|
||||||
let (champs, patch) = get_champs_with_positions_and_patch(&client);
|
let (champs, patch) = CGGDataSource::get_champs_with_positions_and_patch(&client);
|
||||||
|
|
||||||
info!("CGG version: {}", patch);
|
info!("CGG version: {}", patch);
|
||||||
info!("CGG numbers of champs: {}", champs.len());
|
info!("CGG numbers of champs: {}", champs.len());
|
||||||
|
@ -117,7 +95,7 @@ fn main() {
|
||||||
let path = lol_champs_dir.join(&id).join("Recommended");
|
let path = lol_champs_dir.join(&id).join("Recommended");
|
||||||
fs::create_dir_all(&path).unwrap();
|
fs::create_dir_all(&path).unwrap();
|
||||||
for pos in positions {
|
for pos in positions {
|
||||||
write_item_set(
|
CGGDataSource::write_item_set(
|
||||||
&id,
|
&id,
|
||||||
&champion.data.get(id).unwrap().name,
|
&champion.data.get(id).unwrap().name,
|
||||||
&pos,
|
&pos,
|
||||||
|
@ -133,152 +111,6 @@ fn main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_champs_with_positions_and_patch(
|
|
||||||
client: &reqwest::Client,
|
|
||||||
) -> (IndexMap<String, Vec<String>>, String) {
|
|
||||||
let page = client
|
|
||||||
.get("https://champion.gg")
|
|
||||||
.send()
|
|
||||||
.unwrap()
|
|
||||||
.text()
|
|
||||||
.unwrap();
|
|
||||||
let document = Document::from(&*page);
|
|
||||||
|
|
||||||
let patch = document
|
|
||||||
.find(Class("analysis-holder"))
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.find(Name("strong"))
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.text();
|
|
||||||
|
|
||||||
let mut champions = IndexMap::new();
|
|
||||||
for node in document.find(Class("champ-height")) {
|
|
||||||
let id = node
|
|
||||||
.find(Class("home-champion"))
|
|
||||||
.next()
|
|
||||||
.unwrap()
|
|
||||||
.attr("class")
|
|
||||||
.unwrap()
|
|
||||||
.split(' ')
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.to_string();
|
|
||||||
let positions = node
|
|
||||||
.find(Name("a"))
|
|
||||||
.skip(1)
|
|
||||||
.map(|x| {
|
|
||||||
x.attr("href")
|
|
||||||
.unwrap()
|
|
||||||
.split('/')
|
|
||||||
.last()
|
|
||||||
.unwrap()
|
|
||||||
.to_string()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
champions.insert(id, positions);
|
|
||||||
}
|
|
||||||
|
|
||||||
(champions, patch)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_champ_data(id: &str, position: &str, client: &reqwest::Client) -> Option<Value> {
|
|
||||||
let mut req = client
|
|
||||||
.get(&format!(
|
|
||||||
"https://champion.gg/champion/{}/{}?league=",
|
|
||||||
id, position
|
|
||||||
))
|
|
||||||
.send()
|
|
||||||
.unwrap();
|
|
||||||
if req.status().is_success() {
|
|
||||||
lazy_static! {
|
|
||||||
static ref RE: Regex =
|
|
||||||
Regex::new(r"(?m)^\s+matchupData\.championData = (.*)$").unwrap();
|
|
||||||
}
|
|
||||||
serde_json::from_str(&RE.captures(&req.text().unwrap())?[1]).unwrap()
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_item_set(data: &Value, label: &str) -> Value {
|
|
||||||
json!({
|
|
||||||
"items": data["items"].as_array().unwrap().iter().map(|x| json!({"id": x["id"].as_str(), "count": 1})).collect::<Vec<Value>>(),
|
|
||||||
"type": format!("{} ({:.2}% - {} games)", label, data["winPercent"].as_f64().unwrap() * 100., data["games"].as_u64().unwrap())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_item_set_from_list(list: &Vec<u32>, label: &str, key: &str, data: &Value) -> Value {
|
|
||||||
json!({
|
|
||||||
"items": list.iter().map(|x| json!({"id": x.to_string(), "count": 1})).collect::<Vec<Value>>(),
|
|
||||||
"type": format!("{} {}", label, data["skills"][key]["order"].as_array().unwrap().iter()
|
|
||||||
.map(|x| data["skills"]["skillInfo"].as_array().unwrap()[x.as_str().unwrap().parse::<usize>().unwrap()-1]["key"].as_str().unwrap())
|
|
||||||
.collect::<Vec<&str>>().join("."))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_item_set(
|
|
||||||
id: &str,
|
|
||||||
name: &str,
|
|
||||||
pos: &str,
|
|
||||||
ver: &str,
|
|
||||||
path: &PathBuf,
|
|
||||||
client: &reqwest::Client,
|
|
||||||
) {
|
|
||||||
info!("Retrieving data for {} at {}", name, pos);
|
|
||||||
let data = get_champ_data(id, pos, client);
|
|
||||||
|
|
||||||
match data {
|
|
||||||
Some(data) => {
|
|
||||||
let mut item_set = ItemSet {
|
|
||||||
title: format!(
|
|
||||||
"CGG {} {} - {:.2}%",
|
|
||||||
pos,
|
|
||||||
ver,
|
|
||||||
data["stats"]["winRate"].as_f64().unwrap() * 100.
|
|
||||||
),
|
|
||||||
type_: "custom".to_string(),
|
|
||||||
map: "any".to_string(),
|
|
||||||
mode: "any".to_string(),
|
|
||||||
priority: false,
|
|
||||||
sortrank: 0,
|
|
||||||
blocks: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
for (label, path) in ITEM_TYPES.iter() {
|
|
||||||
if !data[&path[0]].get(&path[1]).is_none() {
|
|
||||||
item_set
|
|
||||||
.blocks
|
|
||||||
.push(make_item_set(&data[&path[0]][&path[1]], label));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item_set.blocks.push(make_item_set_from_list(
|
|
||||||
&CONSUMABLES.to_vec(),
|
|
||||||
"Consumables | Frequent:",
|
|
||||||
"mostGames",
|
|
||||||
&data,
|
|
||||||
));
|
|
||||||
item_set.blocks.push(make_item_set_from_list(
|
|
||||||
&TRINKETS.to_vec(),
|
|
||||||
"Trinkets | Wins:",
|
|
||||||
"highestWinPercent",
|
|
||||||
&data,
|
|
||||||
));
|
|
||||||
|
|
||||||
info!("Writing item set for {} at {}", name, pos);
|
|
||||||
fs::write(
|
|
||||||
path.join(format!("CGG_{}_{}.json", id, pos)),
|
|
||||||
serde_json::to_string_pretty(&item_set).unwrap(),
|
|
||||||
).unwrap();
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
error!("Can't get data for {} at {}", id, pos);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn lol_champ_dir() -> Result<PathBuf, Error> {
|
fn lol_champ_dir() -> Result<PathBuf, Error> {
|
||||||
let hklm = RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
|
let hklm = RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
|
||||||
let node = hklm.open_subkey(r"SOFTWARE\WOW6432Node\Riot Games\RADS");
|
let node = hklm.open_subkey(r"SOFTWARE\WOW6432Node\Riot Games\RADS");
|
||||||
|
|
Loading…
Add table
Reference in a new issue