extern crate chrono;
#[macro_use]
extern crate clap;
extern crate fern;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
extern crate ratelimit;
extern crate regex;
extern crate reqwest;
use std::time::Duration;
use std::error::Error;
use std::io::{BufRead, BufReader, Write};
use std::fs::{File, OpenOptions};
use std::path::Path;
use clap::{App, Arg};
use reqwest::StatusCode;
use regex::Regex;
lazy_static! {
static ref CLIENT: reqwest::Client = reqwest::Client::new();
static ref USERNAME_REGEX: Regex = Regex::new(r"^[0-9\p{L} _\\.]{3,16}$").unwrap();
}
fn setup_logger(verbosity: u64) -> Result<(), fern::InitError> {
fern::Dispatch::new()
.format(|out, message, record| {
out.finish(format_args!(
"{} [{}] [{}] {}",
chrono::Local::now().format("%e %b %Y %H:%M:%S%.3f"),
record.target(),
record.level(),
message
))
})
.level(match verbosity {
0 | 1 => log::LogLevelFilter::Info,
2 => log::LogLevelFilter::Debug,
_ => log::LogLevelFilter::Trace,
})
.level_for("reaper", match verbosity {
0 => log::LogLevelFilter::Info,
1 => log::LogLevelFilter::Debug,
_ => log::LogLevelFilter::Trace,
})
.chain(std::io::stdout())
.apply()?;
Ok(())
}
fn argparse<'a, 'b>() -> clap::App<'a, 'b> {
App::new("Reaper")
.version(crate_version!())
.author("Jordan Doyle <jordan@9t9t9.com>")
.about("Scans over a given list for available usernames on League of Legends")
.arg(Arg::with_name("SERVER")
.help("Sets the server to search for usernames on")
.required(true)
.index(1))
.arg(Arg::with_name("INPUT")
.help("Sets the input file to use")
.required(true)
.index(2))
.arg(Arg::with_name("API KEY")
.help("Sets the API key to use")
.required(true)
.index(3))
.arg(Arg::with_name("output")
.short("o")
.long("output")
.value_name("FILE")
.help("Sets an output file to write available usernames to")
.takes_value(true))
.arg(Arg::with_name("verbose")
.short("v")
.long("verbose")
.multiple(true)
.help("Increases logging verbosity each use up to 3 times"))
}
fn main() {
let args = argparse().get_matches();
setup_logger(args.occurrences_of("verbose")).expect("Failed to initialize logging.");
info!("Reaper booting up.");
let server = args.value_of("SERVER").unwrap();
let input = Path::new(args.value_of("INPUT").unwrap());
let api_key = args.value_of("API KEY").unwrap();
assert!(input.exists(), "Input file doesn't exist.");
let output = args.value_of("output");
let output_file = if output.is_some() {
match OpenOptions::new()
.create(true)
.append(true)
.open(output.unwrap())
{
Ok(file) => Some(file),
Err(e) => {
error!("Couldn't open handle to output file. {}", e);
panic!()
}
}
} else {
None
};
debug!("Finished parsing arguments");
let mut ratelimit = ratelimit::Builder::new()
.capacity(1)
.quantum(1)
.interval(Duration::new(1, 0))
.build();
for line in BufReader::new(File::open(input).unwrap()).lines() {
let username = &line.unwrap();
if !USERNAME_REGEX.is_match(username) {
error!(
"The username \"{}\" isn't a valid username, skipping.",
username
);
continue;
}
debug!("Checking if \"{}\" is available", username);
match send_request(server, api_key, username) {
Ok(_) => {
info!("{} is available!", username);
if let Some(ref mut file) = output_file.as_ref() {
writeln!(file, "{}", username).unwrap();
}
}
Err(e) => info!("{} is not available ({})", username, e),
}
ratelimit.wait();
}
}
fn send_request(server: &str, api_key: &str, username: &str) -> Result<(), Box<Error>> {
let resp = CLIENT
.get(&format!(
"https://{}.api.riotgames.com/lol/summoner/v4/summoners/by-name/{}?api_key={}",
server,
username,
api_key
))
.send()?;
debug!("{:?}", resp);
if !resp.status().is_success() {
if resp.status().eq(&StatusCode::NOT_FOUND) {
Ok(())
} else {
Err(Box::from("Bad response from Riot API"))
}
} else {
Err(Box::from("Username is taken"))
}
}