🏡 index : ~doyle/reaper.git

author Jordan Doyle <jordan@9t9t9.com> 2017-11-11 11:51:35.0 +00:00:00
committer Jordan Doyle <jordan@9t9t9.com> 2017-11-11 11:51:35.0 +00:00:00
commit
511f1312af2f01eaf24b92ed65629bccfe487144 [patch]
tree
3a595678130c71e3a0e1fb3186b37f884c072763
parent
082486211e02202dcd4a0bdfbaa05c6258d48023
download
511f1312af2f01eaf24b92ed65629bccfe487144.tar.gz

Complete rewrite using Rust



Diff

 .gitignore           |   5 +-
 .travis.yml          | 134 ++++++++++++++++++++++++++++++++++++++++-
 Cargo.toml           |  19 ++++++-
 README.md            |  39 +++++++++---
 appveyor.yml         |  93 ++++++++++++++++++++++++++++-
 ci/before_deploy.ps1 |  23 +++++++-
 ci/before_deploy.sh  |  33 ++++++++++-
 ci/install.sh        |  47 ++++++++++++++-
 ci/script.sh         |  24 +++++++-
 common.js            |  12 +----
 package.json         |  20 +------
 reaper.js            |  46 +--------------
 src/main.rs          | 175 ++++++++++++++++++++++++++++++++++++++++++++++++++++-
 13 files changed, 585 insertions(+), 85 deletions(-)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c21e6c7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
/target/
**/*.rs.bk
Cargo.lock
/target/
**/*.rs.bk
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4492818
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,134 @@
# Based on the "trust" template v0.1.1
# https://github.com/japaric/trust/tree/v0.1.1

dist: trusty
language: rust
services: docker
sudo: required

# TODO Rust builds on stable by default, this can be
# overridden on a case by case basis down below.

env:
  global:
    # TODO Update this to match the name of your project.
    - CRATE_NAME=reaper

matrix:
  # TODO These are all the build jobs. Adjust as necessary. Comment out what you
  # don't need
  include:
    # Android
    - env: TARGET=aarch64-linux-android DISABLE_TESTS=1
    - env: TARGET=arm-linux-androideabi DISABLE_TESTS=1
    - env: TARGET=armv7-linux-androideabi DISABLE_TESTS=1
    - env: TARGET=i686-linux-android DISABLE_TESTS=1
    - env: TARGET=x86_64-linux-android DISABLE_TESTS=1

    # iOS
    - env: TARGET=aarch64-apple-ios DISABLE_TESTS=1
      os: osx
    - env: TARGET=armv7-apple-ios DISABLE_TESTS=1
      os: osx
    - env: TARGET=armv7s-apple-ios DISABLE_TESTS=1
      os: osx
    - env: TARGET=i386-apple-ios DISABLE_TESTS=1
      os: osx
    - env: TARGET=x86_64-apple-ios DISABLE_TESTS=1
      os: osx

    # Linux
    - env: TARGET=aarch64-unknown-linux-gnu
    - env: TARGET=arm-unknown-linux-gnueabi
    - env: TARGET=armv7-unknown-linux-gnueabihf
    - env: TARGET=i686-unknown-linux-gnu
    - env: TARGET=i686-unknown-linux-musl
    - env: TARGET=mips-unknown-linux-gnu
    - env: TARGET=mips64-unknown-linux-gnuabi64
    - env: TARGET=mips64el-unknown-linux-gnuabi64
    - env: TARGET=mipsel-unknown-linux-gnu
    - env: TARGET=powerpc-unknown-linux-gnu
    - env: TARGET=powerpc64-unknown-linux-gnu
    - env: TARGET=powerpc64le-unknown-linux-gnu
    - env: TARGET=s390x-unknown-linux-gnu DISABLE_TESTS=1
    - env: TARGET=x86_64-unknown-linux-gnu
    - env: TARGET=x86_64-unknown-linux-musl

    # OSX
    - env: TARGET=i686-apple-darwin
      os: osx
    - env: TARGET=x86_64-apple-darwin
      os: osx

    # *BSD
    - env: TARGET=i686-unknown-freebsd DISABLE_TESTS=1
    - env: TARGET=x86_64-unknown-freebsd DISABLE_TESTS=1
    - env: TARGET=x86_64-unknown-netbsd DISABLE_TESTS=1

    # Windows
    - env: TARGET=x86_64-pc-windows-gnu

    # Bare metal
    # These targets don't support std and as such are likely not suitable for
    # most crates.
    # - env: TARGET=thumbv6m-none-eabi
    # - env: TARGET=thumbv7em-none-eabi
    # - env: TARGET=thumbv7em-none-eabihf
    # - env: TARGET=thumbv7m-none-eabi

    # Testing other channels
    - env: TARGET=x86_64-unknown-linux-gnu
      rust: nightly
    - env: TARGET=x86_64-apple-darwin
      os: osx
      rust: nightly

before_install:
  - set -e
  - rustup self update

install:
  - sh ci/install.sh
  - source ~/.cargo/env || true

script:
  - bash ci/script.sh

after_script: set +e

before_deploy:
  - sh ci/before_deploy.sh

deploy:
  # TODO update `api_key.secure`
  # - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new
  # - Encrypt it: `travis encrypt 0123456789012345678901234567890123456789
  # - Paste the output down here
  api_key:
    secure: "rG0L7YpKM2a2p3csThuONY8vEUjWVbw+NtADZOznPO3ZK1RLcIWxS6KV4D2kU5AWGREZmwrXlXdl9od38Dw92O7U2tb8GuAJcGz3FAGnQkDnvCpOGBx4uzNK2ARRS+euWzJd51pjFQMy9cJb7P6gpLnUf+oFu+JUQ5swTdqZXf9SkmQrboTn0XQetv9yQvtRBSDr8+UTucw2TJdU1+KjzH6dd76WCpHABum1Aveo0eFi/VkI0Vzw7rCSNBwrUiQHX0SNBY2Dm18+gkiQmdMFst9sO/YhF4FPw0ZcUweOzOF1ZWp0Bnnxi7pCEb5ELZKFi3COzAjI3jfJr9bHNmfsrZoXhleeRo+juh854oPi2+qwrWIWrL8jKwYGgFvNWKIj0/sfYvOr0DEZjbpD1siJe5E57CPtN0MAdZAvG3s4W60v75nAGChMWjIReKotfJkGSh4TmjqSyEdiJ5gLQjAGFdNYSwryea0MgdvpGa5qXyoExDuQoYern9HFgYBi8M3rfs/coOuA5LO/52FJC+1JqhNHIGCAarRgc10fcsxqcBHUxZ5mUYozHaJaV9jp7+WHqUriEhSiLOBEJpZjPwC92n4AKXe3yisbixfrQ40N4KzzUp0kamngcU7ZkPDT02aGGxwOuiU6tdcab9v2eGw9DmSedhDR8cDPd3jBuscvlAE="
  file_glob: true
  file: $CRATE_NAME-$TRAVIS_TAG-$TARGET.*
  on:
    # TODO Here you can pick which targets will generate binary releases
    # In this example, there are some targets that are tested using the stable
    # and nightly channels. This condition makes sure there is only one release
    # for such targets and that's generated using the stable channel
    condition: $TRAVIS_RUST_VERSION = stable
    tags: true
  provider: releases
  skip_cleanup: true

cache: cargo
before_cache:
  # Travis can't cache files that are not readable by "others"
  - chmod -R a+r $HOME/.cargo

branches:
  only:
    # release tags
    - /^v\d+\.\d+\.\d+.*$/
    - master

notifications:
  email:
    on_success: never
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..445b90d
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,19 @@
[package]
name = "reaper"
version = "2.0.0"
authors = ["Jordan Doyle <jordan@9t9t9.com>"]
repository = "https://github.com/w4/reaper.git"
homepage = "https://github.com/w4/reaper"
license = "MIT"
readme = "README.md"
description = "League of Legends username availablity checker."

[dependencies]
ratelimit = "0.4.2"
reqwest = "0.8.1"
clap = "2.27.1"
lazy_static = "0.2"
fern = "0.4.3"
log = "0.3.8"
chrono = "0.4"
regex = "0.2.2"
diff --git a/README.md b/README.md
index e363a55..4009ee4 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,42 @@
# reaper

[![License]https://poser.pugx.org/laravel/framework/license.svg]http://github.com/jordandoyle/reaper
[![License]https://img.shields.io/github/license/w4/reaper.svg?style=flat-square]http://github.com/jordandoyle/reaper

[League of Legends]http://leagueoflegends.com mass summoner name checker. Supply a region and a list and the script will check the list for available summoner names. Common uses are finding quick variations of your name or finding rare (or "OG") names for selling. An API Key is required to do use this script, they are available for free from [Riot Games]https://developer.riotgames.com/, the API key is set in **reaper.js**.
[League of Legends]http://leagueoflegends.com mass summoner name checker. Supply a region and a list
and the application will check the list for available summoner names. Common uses are finding quick
variations of your name or finding rare (or "OG") names for selling. An API Key is required to do use
this script, they are available for free from [Riot Games]https://developer.riotgames.com/.

You can find a list of the servers you can query from on [Riot's website]https://developer.riotgames.com/regional-endpoints.html.
You can find a list of the servers you can query from on
[Riot's website]https://developer.riotgames.com/regional-endpoints.html. Examples of inputs for
SERVER: `euw1`, `na1`, `pbe1`.

The syntax of reaper is very simple:
    Reaper 0.1.0
    Jordan Doyle <jordan@9t9t9.com>
    Scans over a given list for available usernames on League of Legends

    node reaper.js [server (na1/euw1/la1/etc)] [username file] (output file)
    USAGE:
        reaper [FLAGS] [OPTIONS] <SERVER> <INPUT> <API KEY>

    FLAGS:
        -h, --help       Prints help information
        -V, --version    Prints version information
        -v, --verbose    Increases logging verbosity each use up to 3 times

    OPTIONS:
        -o, --output <FILE>    Sets an output file to write available usernames to

    ARGS:
        <SERVER>     Sets the server to search for usernames on
        <INPUT>      Sets the input file to use
        <API KEY>    Sets the API key to use

For example:

    node reaper.js euw1 username_list.txt output.txt
    ./reaper euw1 username_list.txt my-api-key -o output.txt

Will check the list `username_list.txt` for available summoner names on Europe West using API key `my-api-key` and
outputs what it finds to output.txt

Will check the list username_list.txt for available summoner names on Europe West and output what it finds to output.txt
Builds are available under releases or you can build it from source by pulling down the code and running
`cargo build --release`.
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..a718c2a
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,93 @@
# Based on the "trust" template v0.1.1
# https://github.com/japaric/trust/tree/v0.1.1

environment:
  global:
  # TODO This is the Rust channel that build jobs will use by default but can be
  # overridden on a case by case basis down below
    RUST_VERSION: stable

    # TODO Update this to match the name of your project.
    CRATE_NAME: reaper

  # TODO These are all the build jobs. Adjust as necessary. Comment out what you
  # don't need
  matrix:
    # MinGW
    - TARGET: i686-pc-windows-gnu
    - TARGET: x86_64-pc-windows-gnu

    # MSVC
    - TARGET: i686-pc-windows-msvc
    - TARGET: x86_64-pc-windows-msvc

    # Testing other channels
    - TARGET: x86_64-pc-windows-gnu
      RUST_VERSION: nightly
    - TARGET: x86_64-pc-windows-msvc
      RUST_VERSION: nightly

install:
  - ps: >-
      If ($Env:TARGET -eq 'x86_64-pc-windows-gnu') {
        $Env:PATH += ';C:\msys64\mingw64\bin'
      } ElseIf ($Env:TARGET -eq 'i686-pc-windows-gnu') {
        $Env:PATH += ';C:\msys64\mingw32\bin'
      }
  - curl -sSf -o rustup-init.exe https://win.rustup.rs/
  - rustup-init.exe -y --default-host %TARGET% --default-toolchain %RUST_VERSION%
  - set PATH=%PATH%;C:\Users\appveyor\.cargo\bin
  - rustc -Vv
  - cargo -V

# TODO This is the "test phase", tweak it as you see fit
test_script:
  # we don't run the "test phase" when doing deploys
  - if [%APPVEYOR_REPO_TAG%]==[false] (
      cargo build --target %TARGET% &&
      cargo build --target %TARGET% --release &&
      cargo test --target %TARGET% &&
      cargo test --target %TARGET% --release &&
      cargo run --target %TARGET% &&
      cargo run --target %TARGET% --release
    )

before_deploy:
  # TODO Update this to build the artifacts that matter to you
  - cargo rustc --target %TARGET% --release --bin reaper -- -C lto
  - ps: ci\before_deploy.ps1

deploy:
  artifact: /.*\.zip/
  # TODO update `auth_token.secure`
  # - Create a `public_repo` GitHub token. Go to: https://github.com/settings/tokens/new
  # - Encrypt it. Go to https://ci.appveyor.com/tools/encrypt
  # - Paste the output down here
  auth_token:
    secure: ZVn4BWuEyloI1h+dDXJ4fjuJBHqMAouGCOrODmLdWcPeLHhy8ovJ9Rac15FrVBhp
  description: ''
  on:
    # TODO Here you can pick which targets will generate binary releases
    # In this example, there are some targets that are tested using the stable
    # and nightly channels. This condition makes sure there is only one release
    # for such targets and that's generated using the stable channel
    RUST_VERSION: stable
    appveyor_repo_tag: true
  provider: GitHub

cache:
  - C:\Users\appveyor\.cargo\registry
  - target

branches:
  only:
    # Release tags
    - /^v\d+\.\d+\.\d+.*$/
    - master

notifications:
  - provider: Email
    on_build_success: false

# Building is done in the test phase, so we disable Appveyor's build phase.
build: false
diff --git a/ci/before_deploy.ps1 b/ci/before_deploy.ps1
new file mode 100644
index 0000000..382a055
--- /dev/null
+++ b/ci/before_deploy.ps1
@@ -0,0 +1,23 @@
# This script takes care of packaging the build artifacts that will go in the
# release zipfile

$SRC_DIR = $PWD.Path
$STAGE = [System.Guid]::NewGuid().ToString()

Set-Location $ENV:Temp
New-Item -Type Directory -Name $STAGE
Set-Location $STAGE

$ZIP = "$SRC_DIR\$($Env:CRATE_NAME)-$($Env:APPVEYOR_REPO_TAG_NAME)-$($Env:TARGET).zip"

# TODO Update this to package the right artifacts
Copy-Item "$SRC_DIR\target\$($Env:TARGET)\release\reaper.exe" '.\'

7z a "$ZIP" *

Push-AppveyorArtifact "$ZIP"

Remove-Item *.* -Force
Set-Location ..
Remove-Item $STAGE
Set-Location $SRC_DIR
diff --git a/ci/before_deploy.sh b/ci/before_deploy.sh
new file mode 100644
index 0000000..94555ac
--- /dev/null
+++ b/ci/before_deploy.sh
@@ -0,0 +1,33 @@
# This script takes care of building your crate and packaging it for release

set -ex

main() {
    local src=$(pwd) \
          stage=

    case $TRAVIS_OS_NAME in
        linux)
            stage=$(mktemp -d)
            ;;
        osx)
            stage=$(mktemp -d -t tmp)
            ;;
    esac

    test -f Cargo.lock || cargo generate-lockfile

    # TODO Update this to build the artifacts that matter to you
    cross rustc --bin reaper --target $TARGET --release -- -C lto

    # TODO Update this to package the right artifacts
    cp target/$TARGET/release/reaper $stage/

    cd $stage
    tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *
    cd $src

    rm -rf $stage
}

main
diff --git a/ci/install.sh b/ci/install.sh
new file mode 100644
index 0000000..80e18e4
--- /dev/null
+++ b/ci/install.sh
@@ -0,0 +1,47 @@
set -ex

main() {
    local target=
    if [ $TRAVIS_OS_NAME = linux ]; then
        target=x86_64-unknown-linux-musl
        sort=sort
    else
        target=x86_64-apple-darwin
        sort=gsort  # for `sort --sort-version`, from brew's coreutils.
    fi

    # Builds for iOS are done on OSX, but require the specific target to be
    # installed.
    case $TARGET in
        aarch64-apple-ios)
            rustup target install aarch64-apple-ios
            ;;
        armv7-apple-ios)
            rustup target install armv7-apple-ios
            ;;
        armv7s-apple-ios)
            rustup target install armv7s-apple-ios
            ;;
        i386-apple-ios)
            rustup target install i386-apple-ios
            ;;
        x86_64-apple-ios)
            rustup target install x86_64-apple-ios
            ;;
    esac

    # This fetches latest stable release
    local tag=$(git ls-remote --tags --refs --exit-code https://github.com/japaric/cross \
                       | cut -d/ -f3 \
                       | grep -E '^v[0.1.0-9.]+$' \
                       | $sort --version-sort \
                       | tail -n1)
    curl -LSfs https://japaric.github.io/trust/install.sh | \
        sh -s -- \
           --force \
           --git japaric/cross \
           --tag $tag \
           --target $target
}

main
diff --git a/ci/script.sh b/ci/script.sh
new file mode 100644
index 0000000..ddd7f93
--- /dev/null
+++ b/ci/script.sh
@@ -0,0 +1,24 @@
# This script takes care of testing your crate

set -ex

# TODO This is the "test phase", tweak it as you see fit
main() {
    cross build --target $TARGET
    cross build --target $TARGET --release

    if [ ! -z $DISABLE_TESTS ]; then
        return
    fi

    cross test --target $TARGET
    cross test --target $TARGET --release

    cross run --target $TARGET
    cross run --target $TARGET --release
}

# we don't run the "test phase" when doing deploys
if [ -z $TRAVIS_TAG ]; then
    main
fi
diff --git a/common.js b/common.js
deleted file mode 100644
index 756bdad..0000000
--- a/common.js
+++ /dev/null
@@ -1,12 +0,0 @@
const colors = require('colors');

console.error = (msg) => console.log("[".white + "!".red + "] ".white + msg.white);
console.info = (msg) => console.log("[".white + "-".green + "] ".white + msg.white);
console.warn = (msg) => console.log("[".white + "~".blue + "] ".white + msg.white);

exports.shuffle = (array) => {
    for (let i = array.length - 1; i > 0; i--) {
        const j = Math.floor(Math.random() * (i + 1));
        [array[i], array[j]] = [array[j], array[i]];
    }
};
diff --git a/package.json b/package.json
deleted file mode 100644
index 8594b15..0000000
--- a/package.json
+++ /dev/null
@@ -1,20 +0,0 @@
{
	"name": "reaper",
	"description": "League of Legends available name scraper",
	"version": "1.0.2",
	"author": "Jordan Doyle <jordan@doyle.wf>",
	"main": "reaper.js",
	"repository": {
		"type": "git",
		"url": "git://github.com/JordanDoyle/reaper.git"
	},
	"bugs": {
		"url": "https://github.com/JordanDoyle/reaper/issues"
	},
	"dependencies": {
		"chunk": "~0.0.2",
		"colors": "~1.1.2",
		"limiter": "~1.0.5"
	},
	"license": "MIT"
}
\ No newline at end of file
diff --git a/reaper.js b/reaper.js
deleted file mode 100644
index ecb2e2b..0000000
--- a/reaper.js
+++ /dev/null
@@ -1,46 +0,0 @@
"use strict";

const common = require('./common');
const chunk = require('chunk');
const fs = require('fs');
const https = require('https');
const RateLimiter = require('limiter').RateLimiter;

const args = process.argv.slice(2);

if(args.length < 2 || args.length > 3) {
    console.error("Invalid syntax. Valid syntax: " + process.argv[0] + " " + process.argv[1] + " [server] [username input] (username output)");
}

const apiKey = "<api key here>";

const server = args[0];
const input = args[1];
const output = args[2];

const limiter = new RateLimiter(1, 'second');

const names = fs.readFileSync(input).toString().split('\n');

// shuffle the list of names passed in
common.shuffle(names);

for (let name of names) {
    name = name.replace(/[-]/g, ' ').replace(/[\r\n'.]/g, '').trim();

    if(name.length < 3)
        continue;

    limiter.removeTokens(1, () => {
        // make a secure request to the specified server
        https.get(`https://${server}.api.riotgames.com/lol/summoner/v3/summoners/by-name/${name}?api_key=${apiKey}`, (res) => {
            // riot returns a 404 if none of the names are registered
            if(res.statusCode === 404) {
                console.info(name.replace(/[-]/g, ' ').replace(/[\r\n'.]/g, '') + " is available!");

                if(output !== undefined)
                    fs.appendFile(output, name + '\n', () => null);
            }
        });
    });
}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..1e80071
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,175 @@
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.");

    // all our inputs below are required fields so we can unwrap them without any worries
    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");

    // output is an optional field so we check if it has a value and if it does we build a
    // new File instance and create a new Option with it
    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) // number of tokens the bucket will hold
        .quantum(1) // add one token per interval
        .interval(Duration::new(1, 0)) // add quantum tokens every 1 second
        .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/v3/summoners/by-name/{}?api_key={}",
            server,
            username,
            api_key
        ))
        .send()?;

    debug!("{:?}", resp);

    if !resp.status().is_success() {
        if resp.status().eq(&StatusCode::NotFound) {
            Ok(())
        } else {
            Err(Box::from("Bad response from Riot API"))
        }
    } else {
        Err(Box::from("Username is taken"))
    }
}