#[macro_use] extern crate serde_derive; #[macro_use] extern crate log; extern crate reqwest; extern crate serde; #[macro_use] extern crate serde_json; extern crate regex; extern crate select; extern crate simple_logger; #[macro_use] extern crate lazy_static; extern crate winreg; use regex::Regex; use reqwest::header::{Headers, UserAgent}; use select::document::Document; use select::predicate::{Class, Name}; use serde_json::Value; use std::collections::BTreeMap; use std::path::PathBuf; use std::{fs, io, thread, time}; use time::Duration; use winreg::RegKey; #[derive(Deserialize)] struct Realm { v: String, } #[derive(Deserialize)] struct Champion { data: BTreeMap, } #[derive(Deserialize)] struct ChampInfo { /*id: String*/ } #[derive(Serialize, Deserialize)] struct ItemSet { title: String, #[serde(rename = "type")] type_: String, map: String, mode: String, priority: bool, sortrank: u32, blocks: Vec, } 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"; 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", ["firstItems", "highestWinPercent"], ), ]; fn main() { simple_logger::init_with_level(log::Level::Info).unwrap(); info!("CGG Item Sets"); let lol_champs_dir = match lol_champ_dir() { Ok(x) => x, Err(_e) => PathBuf::from(LOL_CHAMPS_DIR), }; let mut headers = Headers::new(); headers.set(UserAgent::new(USER_AGENT)); let client = reqwest::Client::builder() .default_headers(headers) .timeout(Duration::from_secs(10)) .build() .unwrap(); let realm: Realm = client .get("https://ddragon.leagueoflegends.com/realms/euw.json") .send() .unwrap() .json() .unwrap(); info!("LoL version: {}", realm.v); let champion: Champion = client .get(&format!( "https://ddragon.leagueoflegends.com/cdn/{}/data/en_US/champion.json", realm.v )) .send() .unwrap() .json() .unwrap(); 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()); for (id, positions) in &champs { if champion.data.contains_key(id) { let path = lol_champs_dir.join(&id).join("Recommended"); fs::create_dir_all(&path).unwrap(); for pos in positions { write_item_set(&id, &pos, &patch, &path, &client); thread::sleep(Duration::from_millis(300)); } } else { error!("{} not found in LoL champs", &id); } } } fn get_champs_with_positions_and_patch( client: &reqwest::Client, ) -> (BTreeMap>, 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 = BTreeMap::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 { let mut req = client .get(&format!( "https://champion.gg/champion/{}/{}?league=", id, position )) .send() .unwrap(); if req.status() == reqwest::StatusCode::Ok { serde_json::from_str(&find_champ_data(&req.text().unwrap())).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::>(), "type": format!("{} ({:.2}% - {} games)", label, data["winPercent"].as_f64().unwrap() * 100., data["games"].as_u64().unwrap()) }) } fn make_item_set_from_list(list: &Vec, label: &str, key: &str, data: &Value) -> Value { json!({ "items": list.iter().map(|x| json!({"id": x.to_string(), "count": 1})).collect::>(), "type": format!("{} {}", label, data["skills"][key]["order"].as_array().unwrap().iter() .map(|x| data["skills"]["skillInfo"].as_array().unwrap()[x.as_str().unwrap().parse::().unwrap()-1]["key"].as_str().unwrap()) .collect::>().join(".")) }) } fn write_item_set(id: &str, pos: &str, ver: &str, path: &PathBuf, client: &reqwest::Client) { info!("Retrieving data for {} at {}", id, 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 {}", id, 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 find_champ_data(text: &str) -> String { lazy_static! { static ref RE: Regex = Regex::new(r"(?m)^\s+matchupData\.championData = (.*)$").unwrap(); } RE.captures(text).unwrap()[1].to_string() } fn lol_champ_dir() -> Result { let hklm = RegKey::predef(winreg::enums::HKEY_LOCAL_MACHINE); let node = hklm.open_subkey(r"SOFTWARE\WOW6432Node\Riot Games\RADS")?; let dir: String = node.get_value("LocalRootFolder")?; Ok(PathBuf::from(&dir) .parent() .unwrap() .join("Config") .join("Champions")) }