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(-)
@@ -0,0 +1,5 @@
/target/
**/*.rs.bk
Cargo.lock
/target/
**/*.rs.bk
@@ -0,0 +1,134 @@
dist: trusty
language: rust
services: docker
sudo: required
env:
global:
- CRATE_NAME=reaper
matrix:
include:
- 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
- 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
- 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
- env: TARGET=i686-apple-darwin
os: osx
- env: TARGET=x86_64-apple-darwin
os: osx
- 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
- env: TARGET=x86_64-pc-windows-gnu
- 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:
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:
condition: $TRAVIS_RUST_VERSION = stable
tags: true
provider: releases
skip_cleanup: true
cache: cargo
before_cache:
- chmod -R a+r $HOME/.cargo
branches:
only:
- /^v\d+\.\d+\.\d+.*$/
- master
notifications:
email:
on_success: never
@@ -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"
@@ -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`.
@@ -0,0 +1,93 @@
environment:
global:
RUST_VERSION: stable
CRATE_NAME: reaper
matrix:
- TARGET: i686-pc-windows-gnu
- TARGET: x86_64-pc-windows-gnu
- TARGET: i686-pc-windows-msvc
- TARGET: x86_64-pc-windows-msvc
- 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
test_script:
- 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:
- cargo rustc --target %TARGET% --release --bin reaper -- -C lto
- ps: ci\before_deploy.ps1
deploy:
artifact: /.*\.zip/
auth_token:
secure: ZVn4BWuEyloI1h+dDXJ4fjuJBHqMAouGCOrODmLdWcPeLHhy8ovJ9Rac15FrVBhp
description: ''
on:
RUST_VERSION: stable
appveyor_repo_tag: true
provider: GitHub
cache:
- C:\Users\appveyor\.cargo\registry
- target
branches:
only:
- /^v\d+\.\d+\.\d+.*$/
- master
notifications:
- provider: Email
on_build_success: false
build: false
@@ -0,0 +1,23 @@
$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"
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
@@ -0,0 +1,33 @@
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
cross rustc --bin reaper --target $TARGET --release -- -C lto
cp target/$TARGET/release/reaper $stage/
cd $stage
tar czf $src/$CRATE_NAME-$TRAVIS_TAG-$TARGET.tar.gz *
cd $src
rm -rf $stage
}
main
@@ -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 fi
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
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
@@ -0,0 +1,24 @@
set -ex
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
}
if [ -z $TRAVIS_TAG ]; then
main
fi
@@ -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]];
}
};
@@ -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
@@ -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');
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, () => {
https.get(`https://${server}.api.riotgames.com/lol/summoner/v3/summoners/by-name/${name}?api_key=${apiKey}`, (res) => {
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
@@ -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.");
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/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"))
}
}