CGGItemSets/src/main.rs

292 lines
8.3 KiB
Rust
Raw Normal View History

2018-06-09 09:07:41 +02:00
#[macro_use]
extern crate serde_derive;
#[macro_use]
extern crate log;
2018-06-04 22:51:18 +02:00
extern crate reqwest;
extern crate serde;
2018-06-09 09:07:41 +02:00
#[macro_use]
extern crate serde_json;
2018-06-08 22:41:01 +02:00
extern crate regex;
2018-06-09 09:07:41 +02:00
extern crate select;
extern crate simple_logger;
#[macro_use]
extern crate lazy_static;
2018-06-09 12:35:47 +02:00
extern crate indexmap;
2018-06-09 08:31:15 +02:00
extern crate winreg;
2018-06-04 22:51:18 +02:00
2018-06-09 12:35:47 +02:00
use indexmap::IndexMap;
2018-06-09 09:07:41 +02:00
use regex::Regex;
2018-06-04 22:51:18 +02:00
use reqwest::header::{Headers, UserAgent};
use select::document::Document;
use select::predicate::{Class, Name};
2018-06-08 22:41:01 +02:00
use serde_json::Value;
2018-06-09 09:07:41 +02:00
use std::path::PathBuf;
use std::{fs, io, thread, time};
2018-06-09 00:06:30 +02:00
use time::Duration;
2018-06-09 08:31:15 +02:00
use winreg::RegKey;
2018-06-04 22:51:18 +02:00
#[derive(Deserialize)]
struct Realm {
2018-06-09 09:07:41 +02:00
v: String,
2018-06-04 22:51:18 +02:00
}
#[derive(Deserialize)]
struct Champion {
2018-06-09 12:35:47 +02:00
data: IndexMap<String, ChampInfo>,
2018-06-04 22:51:18 +02:00
}
#[derive(Deserialize)]
struct ChampInfo {
2018-06-09 11:46:02 +02:00
name: String,
}
#[derive(Serialize, Deserialize)]
struct ItemSet {
title: String,
#[serde(rename = "type")]
type_: String,
map: String,
mode: String,
priority: bool,
2018-06-08 22:41:01 +02:00
sortrank: u32,
2018-06-09 09:07:41 +02:00
blocks: Vec<Value>,
2018-06-04 22:51:18 +02:00
}
2018-06-09 09:07:41 +02:00
const USER_AGENT: &str =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0";
const LOL_CHAMPS_DIR: &str = ".\\champs";
2018-06-09 00:06:30 +02:00
const CONSUMABLES: [u32; 9] = [2003, 2004, 2055, 2031, 2032, 2033, 2138, 2140, 2139];
const TRINKETS: [u32; 3] = [3340, 3364, 3363];
2018-06-09 08:31:15 +02:00
const ITEM_TYPES: &'static [(&str, [&str; 2]); 4] = &[
("Most Frequent Starters", ["firstItems", "mostGames"]),
2018-06-09 09:07:41 +02:00
(
"Highest Win % Starters",
["firstItems", "highestWinPercent"],
),
2018-06-09 08:31:15 +02:00
("Most Frequent Core Build", ["items", "mostGames"]),
2018-06-09 10:10:07 +02:00
("Highest Win % Core Build", ["items", "highestWinPercent"]),
2018-06-09 08:31:15 +02:00
];
2018-06-08 22:41:01 +02:00
2018-06-04 22:51:18 +02:00
fn main() {
simple_logger::init_with_level(log::Level::Info).unwrap();
info!("CGG Item Sets");
2018-06-09 08:31:15 +02:00
let lol_champs_dir = match lol_champ_dir() {
Ok(x) => x,
2018-06-09 09:07:41 +02:00
Err(_e) => PathBuf::from(LOL_CHAMPS_DIR),
2018-06-09 08:31:15 +02:00
};
2018-06-10 12:42:02 +02:00
info!("LoL Champs Folder: {}", lol_champs_dir.to_str().unwrap());
2018-06-09 08:31:15 +02:00
2018-06-04 22:51:18 +02:00
let mut headers = Headers::new();
headers.set(UserAgent::new(USER_AGENT));
2018-06-09 00:06:30 +02:00
let client = reqwest::Client::builder()
.default_headers(headers)
.timeout(Duration::from_secs(10))
2018-06-09 09:07:41 +02:00
.build()
.unwrap();
2018-06-04 22:51:18 +02:00
2018-06-09 09:07:41 +02:00
let realm: Realm = client
.get("https://ddragon.leagueoflegends.com/realms/euw.json")
.send()
.unwrap()
.json()
.unwrap();
2018-06-04 22:51:18 +02:00
info!("LoL version: {}", realm.v);
2018-06-09 09:07:41 +02:00
let champion: Champion = client
.get(&format!(
"https://ddragon.leagueoflegends.com/cdn/{}/data/en_US/champion.json",
realm.v
))
.send()
.unwrap()
.json()
.unwrap();
2018-06-04 22:51:18 +02:00
info!("LoL numbers of champs: {}", champion.data.len());
let (champs, patch) = get_champs_with_positions_and_patch(&client);
info!("CGG version: {}", patch);
info!("CGG numbers of champs: {}", champs.len());
2018-06-08 22:41:01 +02:00
for (id, positions) in &champs {
if champion.data.contains_key(id) {
2018-06-09 08:31:15 +02:00
let path = lol_champs_dir.join(&id).join("Recommended");
fs::create_dir_all(&path).unwrap();
for pos in positions {
2018-06-09 11:46:02 +02:00
write_item_set(
&id,
&champion.data.get(id).unwrap().name,
&pos,
&patch,
&path,
&client,
);
2018-06-09 00:06:30 +02:00
thread::sleep(Duration::from_millis(300));
}
} else {
2018-06-09 08:59:32 +02:00
error!("{} not found in LoL champs", &id);
}
}
}
2018-06-09 09:07:41 +02:00
fn get_champs_with_positions_and_patch(
client: &reqwest::Client,
2018-06-09 12:35:47 +02:00
) -> (IndexMap<String, Vec<String>>, String) {
2018-06-09 09:07:41 +02:00
let page = client
.get("https://champion.gg")
.send()
.unwrap()
.text()
.unwrap();
let document = Document::from(&*page);
2018-06-09 09:07:41 +02:00
let patch = document
.find(Class("analysis-holder"))
.next()
.unwrap()
.find(Name("strong"))
.next()
.unwrap()
.text();
2018-06-09 12:35:47 +02:00
let mut champions = IndexMap::new();
for node in document.find(Class("champ-height")) {
2018-06-09 09:07:41 +02:00
let id = node.find(Class("home-champion"))
.next()
.unwrap()
.attr("class")
.unwrap()
.split(' ')
.last()
.unwrap()
.to_string();
let positions = node.find(Name("a"))
2018-06-09 09:07:41 +02:00
.skip(1)
.map(|x| {
x.attr("href")
.unwrap()
.split('/')
.last()
.unwrap()
.to_string()
})
.collect();
champions.insert(id, positions);
}
(champions, patch)
2018-06-04 22:51:18 +02:00
}
2018-06-09 00:06:30 +02:00
fn get_champ_data(id: &str, position: &str, client: &reqwest::Client) -> Option<Value> {
2018-06-09 09:07:41 +02:00
let mut req = client
.get(&format!(
"https://champion.gg/champion/{}/{}?league=",
id, position
))
.send()
.unwrap();
2018-06-09 11:10:49 +02:00
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()
2018-06-09 00:06:30 +02:00
} else {
None
}
2018-06-08 22:41:01 +02:00
}
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())
})
}
2018-06-09 08:31:15 +02:00
fn make_item_set_from_list(list: &Vec<u32>, label: &str, key: &str, data: &Value) -> Value {
2018-06-09 00:06:30 +02:00
json!({
"items": list.iter().map(|x| json!({"id": x.to_string(), "count": 1})).collect::<Vec<Value>>(),
2018-06-09 08:31:15 +02:00
"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("."))
2018-06-09 00:06:30 +02:00
})
}
2018-06-09 11:46:02 +02:00
fn write_item_set(
id: &str,
name: &str,
pos: &str,
ver: &str,
path: &PathBuf,
client: &reqwest::Client,
) {
info!("Retrieving data for {} at {}", name, pos);
2018-06-08 22:41:01 +02:00
let data = get_champ_data(id, pos, client);
2018-06-09 00:06:30 +02:00
match data {
Some(data) => {
let mut item_set = ItemSet {
2018-06-09 09:07:41 +02:00
title: format!(
"CGG {} {} - {:.2}%",
pos,
ver,
data["stats"]["winRate"].as_f64().unwrap() * 100.
),
2018-06-09 00:06:30 +02:00
type_: "custom".to_string(),
map: "any".to_string(),
mode: "any".to_string(),
priority: false,
sortrank: 0,
2018-06-09 09:07:41 +02:00
blocks: vec![],
2018-06-09 00:06:30 +02:00
};
for (label, path) in ITEM_TYPES.iter() {
if !data[&path[0]].get(&path[1]).is_none() {
2018-06-09 09:07:41 +02:00
item_set
.blocks
.push(make_item_set(&data[&path[0]][&path[1]], label));
2018-06-09 00:06:30 +02:00
}
}
2018-06-09 09:07:41 +02:00
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,
));
2018-06-08 22:41:01 +02:00
2018-06-09 11:46:02 +02:00
info!("Writing item set for {} at {}", name, pos);
2018-06-09 09:07:41 +02:00
fs::write(
path.join(format!("CGG_{}_{}.json", id, pos)),
serde_json::to_string_pretty(&item_set).unwrap(),
).unwrap();
}
2018-06-09 00:06:30 +02:00
None => {
error!("Can't get data for {} at {}", id, pos);
}
}
2018-06-08 22:41:01 +02:00
}
2018-06-09 08:31:15 +02:00
fn lol_champ_dir() -> Result<PathBuf, io::Error> {
let hklm = RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE);
2018-06-10 12:42:02 +02:00
let node = hklm.open_subkey(r"SOFTWARE\WOW6432Node\Riot Games\RADS");
let path: PathBuf;
if node.is_ok() {
let val: String = node?.get_value("LocalRootFolder")?;
path = PathBuf::from(val).parent().unwrap().to_path_buf();
} else {
let node = hklm.open_subkey(r"SOFTWARE\WOW6432Node\Riot Games, Inc\League of Legends");
let val: String = node?.get_value("Location")?;
path = PathBuf::from(val);
}
Ok(path.join("Config").join("Champions"))
2018-06-09 08:31:15 +02:00
}