text:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/time.h>
#include <stdarg.h> // for variable arguments functions
#include <fcntl.h>
#include <stdlib.h>

// At this point we have already definitions needed for  ocall interface, so:
#define DO_NOT_REDEFINE_FOR_OCALL
#include "Enclave_t.h"

// For open64 need to define this
#define O_TMPFILE (__O_TMPFILE | O_DIRECTORY)

long int sysconf(int name){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

int open64(const char *filename, int flags, ...){
    mode_t mode = 0; // file permission bitmask

    // Get the mode_t from arguments
	if ((flags & O_CREAT) || (flags & O_TMPFILE) == O_TMPFILE) {
		va_list valist;
		va_start(valist, flags);
		mode = va_arg(valist, mode_t);
		va_end(valist);
	}

    int ret;
    sgx_status_t status = ocall_open64(&ret, filename, flags, mode);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

off_t lseek64(int fd, off_t offset, int whence){
    off_t ret;
    sgx_status_t status = ocall_lseek64(&ret, fd, offset, whence);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

int gettimeofday(struct timeval *tv, struct timezone *tz){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

unsigned int sleep(unsigned int seconds){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

void *dlopen(const char *filename, int flag){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
}

char *dlerror(void){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

void *dlsym(void *handle, const char *symbol){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);

}

int dlclose(void *handle){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

int utimes(const char *filename, const struct timeval times[2]){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

struct tm *localtime(const time_t *timep){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

pid_t getpid(void){
    int ret;
    sgx_status_t status = ocall_getpid(&ret);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

int fsync(int fd){
    int ret;
    sgx_status_t status = ocall_fsync(&ret, fd);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

time_t time(time_t *t){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

int close(int fd){
    int ret;
    sgx_status_t status = ocall_close(&ret, fd);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

int access(const char *pathname, int mode){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

char *getcwd(char *buf, size_t size){
    char* ret;
    sgx_status_t status = ocall_getcwd(&ret, buf, size);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

int sgx_lstat(const char *path, struct stat *buf){
    int ret;
    sgx_status_t status = ocall_lstat(&ret, path, buf, sizeof(struct stat));
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

int sgx_stat(const char *path, struct stat *buf){
    int ret;
    sgx_status_t status = ocall_stat(&ret, path, buf, sizeof(struct stat));
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

int sgx_fstat(int fd, struct stat *buf){
    int ret;
    sgx_status_t status = ocall_fstat(&ret, fd, buf, sizeof(struct stat));
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

int sgx_ftruncate(int fd, off_t length){
    int ret;
    sgx_status_t status = ocall_ftruncate(&ret, fd, length);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

int fcntl(int fd, int cmd, ... /* arg */ ){
    // Read one argument
    va_list valist;
	va_start(valist, cmd);
	void* arg = va_arg(valist, void*);
	va_end(valist);

    int ret;
    sgx_status_t status = ocall_fcntl(&ret, fd, cmd, arg, sizeof(void*));
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

ssize_t read(int fd, void *buf, size_t count){
    int ret;
    sgx_status_t status = ocall_read(&ret, fd, buf, count);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return (ssize_t)ret;
}

ssize_t write(int fd, const void *buf, size_t count){
    int ret;
    sgx_status_t status = ocall_write(&ret, fd, buf, count);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return (ssize_t)ret;
}

int fchmod(int fd, mode_t mode){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

int unlink(const char *pathname){
    int ret;
    sgx_status_t status = ocall_unlink(&ret, pathname);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

int mkdir(const char *pathname, mode_t mode) {
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

int rmdir(const char *pathname){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

int fchown(int fd, uid_t owner, gid_t group){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

uid_t geteuid(void){
    int ret;
    sgx_status_t status = ocall_getuid(&ret);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return (uid_t)ret;
}

char* getenv(const char *name){
    char* ret = NULL;
    sgx_status_t status = ocall_getenv(&ret, name);
    if (status != SGX_SUCCESS) {
        char error_msg[256];
        snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: when calling ocall_", __func__);
        ocall_print_error(error_msg);
    }
    return ret;
}

void *mmap64(void *addr, size_t len, int prot, int flags, int fildes, off_t off){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
}

int munmap(void *addr, size_t length){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}

void *mremap(void *old_address, size_t old_size, size_t new_size, int flags, ... /* void *new_address */){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
}

ssize_t readlink(const char *path, char *buf, size_t bufsiz){
    char error_msg[256];
    snprintf(error_msg, sizeof(error_msg), "%s%s", "Error: no ocall implementation for ", __func__);
    ocall_print_error(error_msg);
    return 0;
}


text:
// This is a real implementation of ocalls
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>

int ocall_lstat(const char *path, struct stat* buf){
    //printf("Entering %s\n", __func__);
    return lstat(path, buf);
}

int ocall_stat(const char *path, struct stat* buf){
    //printf("Entering %s\n", __func__);
    return stat(path, buf);
}

int ocall_fstat(int fd, struct stat* buf){
    //printf("Entering %s\n", __func__);
    return fstat(fd, buf);
}

int ocall_ftruncate(int fd, off_t length){
    //printf("Entering %s\n", __func__);
    return ftruncate(fd, length);
}

char* ocall_getcwd(char *buf, size_t size){
    //printf("Entering %s\n", __func__);
    return getcwd(buf, size);
}

int ocall_getpid(void){
    //printf("Entering %s\n", __func__);
    return getpid();
}

int ocall_open64(const char *filename, int flags, mode_t mode){
    //printf("Entering %s\n", __func__);
    return open(filename, flags, mode); // redirect it to open() instead of open64()
}

off_t ocall_lseek64(int fd, off_t offset, int whence){
    //printf("Entering %s\n", __func__);
    return lseek(fd, offset, whence); // redirect it to lseek() instead of lseek64()
}

int ocall_read(int fd, void *buf, size_t count){
    //printf("Entering %s\n", __func__);
    return read(fd, buf, count);
}

int ocall_write(int fd, const void *buf, size_t count){
    //printf("Entering %s\n", __func__);
    return write(fd, buf, count);
}

int ocall_fcntl(int fd, int cmd, void* arg, size_t size){
    //printf("Entering %s\n", __func__);
    return fcntl(fd, cmd, arg);
}

int ocall_close(int fd){
    //printf("Entering %s\n", __func__);
    return close(fd);
}

int ocall_unlink(const char *pathname){
    //printf("Entering %s\n", __func__);
    return unlink(pathname);
}

int ocall_getuid(void){
    //printf("Entering %s\n", __func__);
    return getuid();
}

char* ocall_getenv(const char *name){
    //printf("Entering %s\n", __func__);
    return getenv(name);
}

int ocall_fsync(int fd){
    //printf("Entering %s\n", __func__);
    return fsync(fd);
}

repo_language: C

mime_type: text/x-c++


text:
#include <iostream>
#include <string>
#include "sgx_urts.h"
#include "Enclave_u.h" // Headers for untrusted part (autogenerated by edger8r)

using namespace std;

# define MAX_PATH FILENAME_MAX
# define ENCLAVE_FILENAME "enclave.signed.so"

// ocalls for printing string (C++ ocalls)
void ocall_print_error(const char *str){
    cerr << str << endl;
}

void ocall_print_string(const char *str){
    cout << str;
}

void ocall_println_string(const char *str){
    cout << str << endl;
}

// Application entry
int main(int argc, char *argv[]){
    if ( argc != 2 ){
        cout << "Usage: " << argv[0] << " <database>" << endl;
        return -1;
    }
    const char* dbname = argv[1];

    sgx_enclave_id_t eid = 0;
    char token_path[MAX_PATH] = {'\0'};
    sgx_launch_token_t token = {0};
    sgx_status_t ret = SGX_ERROR_UNEXPECTED; // status flag for enclave calls
    int updated = 0;

    // Initialize the enclave
    ret = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, &token, &updated, &eid, NULL);
    if (ret != SGX_SUCCESS) {
        cerr << "Error: creating enclave" << endl;
        return -1;
    }
    cout << "Info: SQLite SGX enclave successfully created." << endl;

    // Open SQLite database
    ret = ecall_opendb(eid, dbname);
    if (ret != SGX_SUCCESS) {
        cerr << "Error: Making an ecall_open()" << endl;
        return -1;
    }

    cout << "Enter SQL statement to execute or 'quit' to exit: " << endl;
    string input;
    cout << "> ";
    while(getline(cin, input)) {
        if (input == "quit"){
            break;
        }
        const char* sql = input.c_str();
        ret =  ecall_execute_sql(eid, sql);
        if (ret != SGX_SUCCESS) {
            cerr << "Error: Making an ecall_execute_sql()" << endl;
            return -1;
        }
        cout << "> ";
    }

    // Closing SQLite database inside enclave
    ret =  ecall_closedb(eid);
    if (ret != SGX_SUCCESS) {
        cerr << "Error: Making an ecall_closedb()" << endl;
        return -1;
    }

    // Destroy the enclave
    sgx_destroy_enclave(eid);
    if (ret != SGX_SUCCESS) {
        cerr << "Error: destroying enclave" << endl;
        return -1;
    }

    cout << "Info: SQLite SGX enclave successfully returned." << endl;
    return 0;
}


text:
Improve categorize script
=> Can we have category based on torrent hash from an API???

Improve code base
=> classifier + categorize helper

repo_name: FlyersWeb/dhtbay

repo_language: JavaScript

mime_type: text/plain


text:
module.exports = {
  apps : [{
    name: "loadDHT",
    script: "./loadDHT.js",
    env: {
      NODE_ENV: "development",
    },
    env_production: {
      NODE_ENV: "production",
    }
  }, {
    name: "loadTorrent",
    script: "./loadTorrent.js",
    args: "--watch",
    env: {
      NODE_ENV: "development",
    },
    env_production: {
      NODE_ENV: "production",
    }
  }, {
    name: "categorize",
    script: "./categorize.js",
    env: {
      NODE_ENV: "development",
    },
    env_production: {
      NODE_ENV: "production",
    }
  }, {
    name: "crawlDHT",
    script: "./crawlDHT.js",
    env: {
      NODE_ENV: "development",
    },
    env_production: {
      NODE_ENV: "production",
    }
  }]
}


text:
"use strict";

const config = require('./config/database');

const Promise = require("bluebird");

const mongoose = require('mongoose');
mongoose.Promise = Promise;
mongoose.connect(config.db.uri);

const path = require('path');

const CronJob = require("cron").CronJob;

const natural = require('natural');

const Torrent = require('./models/Torrent.js');
const Classifier = require('./models/Classifier.js');

const bunyan = require("bunyan");
const logger = bunyan.createLogger({name: "classifier"});

function precision(a) {
   let e = 1;
   while (Math.round(a * e) / e !== a) e *= 10;
   return Math.round(Math.log(e) / Math.LN10);
};

let   category = "Unknown";
const filter = { 'category' : /Unknown/ };

const job = new CronJob("30 * * * * *", function() { // run each 30 seconds
  Classifier.findOne({})
  .then(dbClassifier => {
    if(!dbClassifier) return Promise.reject("Unavailable classifier!");
    return Promise.resolve(natural.BayesClassifier.restore(JSON.parse(dbClassifier.raw)));
  })
  .then(classifier => {
    const cursor = Torrent.find(filter).sort({'imported': -1}).limit(100).cursor();
    return cursor.eachAsync(torrent => {
      logger.info(`Treating ${torrent._id} categorization`)
      if(!torrent.files) return Promise.reject(`Torrent ${torrent._id} has no files!`);
      const exts = torrent.files
        .map(file => path.extname(file).toLowerCase())
        .filter(ext => ext.length > 0) // no empty
        .filter(ext => ext.length < config.limitExt) // with min length

      if(!exts.length) return Promise.reject(`No extensions for torrent ${torrent._id}`);
      const classifications = classifier.getClassifications(exts);
      if(classifications.length && (classifications[0].value * Math.pow(10,8) > 1)) {
        const valA = classifications[0].value,
              valB = classifications[1].value;
        // Detect incertitude to limit classification
        const cprecision = precision(valA);
        const valAprecision = valA*Math.pow(10,cprecision),
              valBprecision = valB*Math.pow(10,cprecision);
        if( ((valAprecision-valBprecision)/valAprecision) > 0.4 )  {
          category=classifications[0].label;
        }
      }
      torrent.category = category;
      return torrent.save()
    })
  })
  .catch((err) => Promise.reject(logger.error(err)))
});
job.start();


text:
'use strict';

const config = require('./config/database');

const Promise = require('bluebird');

const mongoose = require('mongoose');
mongoose.Promise = Promise;
mongoose.connect(config.db.uri);

const fs = require('fs');
Promise.promisifyAll(fs);

const path = require('path');

const minimist = require('minimist');

const rt = require('read-torrent');

const Torrent = require('./models/Torrent.js');

const bunyan = require('bunyan');
const logger = bunyan.createLogger({
  name: 'torrentLoader'
});

const chokidar = require('chokidar');
const glob = require('glob');

const TORRENT_PATH = `${__dirname}/torrent`;
const TORRENT_GLOB = `${TORRENT_PATH}/*.torrent`;

function loadTorrent(fsfile) {
  logger.info(`File ${fsfile} treatment in progress...`);
  return new Promise((resolve, reject) => {
      rt(fsfile, (err, ftorrent) => {
        if (err) {
          reject(err)
        }
        resolve(ftorrent);
      })
    })
    .then(ftorrent => [ftorrent, Torrent.findById(ftorrent.infoHash).exec()])
    .spread((ftorrent, res) =>
      (res) ? Promise.reject('TEXISTS') : Promise.resolve(ftorrent))
    .then(ftorrent => {
      return [
        ftorrent,
        new Torrent({
          '_id': ftorrent.infoHash,
          'title': ftorrent.name,
          'details': ftorrent.announce,
          'size': ftorrent.length,
          'files': ftorrent.files.map(f => f.path),
          'imported': new Date()
        }).save()
      ]
    })
    .spread((ftorrent, res) => Promise.resolve(
      logger.info(`File ${ftorrent.infoHash} added`)))
    .catch(err => (err === 'TEXISTS') ? Promise.resolve(
      logger.info(`File ${fsfile} already loaded`)) : Promise.reject(err))
    .then(() => fs.unlinkAsync(fsfile))
    .catch(err => Promise.reject(logger.error(err)))
}

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

if (argv.f || argv.force) {
  return glob(TORRENT_GLOB, {
      silent: true,
      absolute: false
    }, (err, fsfiles) =>
    Promise.all(fsfiles.map(fsfile => loadTorrent(fsfile))));
} else if (argv.w || argv.watch) {
  const watcher = chokidar.watch(TORRENT_GLOB);
  watcher.on('add', (fsfile) => loadTorrent(fsfile));
} else {
  process.stdout.write(
    `Usage :
    + Watch mode : loadTorrent --watch
    + Force load : loadTorrent --force`
  );
}


text:
version: '2'
services:
  dhtbay:
    build: .
    image: dhtbay
    container_name: dhtbay
    restart: always
    links:
      - aria2
      - mongo
      - redis
    ports:
      - "6881:6881/udp"
    volumes:
      - ./torrent:/var/www/torrent
  aria2:
    image: flyersweb/aria2
    container_name: dhtaria2
    restart: always
    volumes:
      - ./torrent:/data
      - ./config/aria2:/etc/aria2
    ports:
      - "6900:6800"
  mongo:
    image: mongo:3.0
    container_name: mongo
    restart: always
    volumes:
      - ./data:/data/db
  redis:
    image: redis:3
    container_name: redis
    restart: always


text:
#!/bin/bash

docker exec -i mongo  mongoexport --db bitcannon --collection torrents --type=csv --fields _id,title,category,details,size,files,imported,lastmod --out /export.csv
docker cp mongo:/export.csv .


text:
"use strict";

const config = require('./config/database');

const DHT = require('bittorrent-dht');

const redis = require("redis");
const client = redis.createClient(config.redis.port, config.redis.host, config.redis.options);

const bunyan = require("bunyan");
const logger = bunyan.createLogger({name: "crawler"});

const dht = new DHT();

dht.listen(6881, () => {
  logger.info('now listening');
  logger.info(dht.address());
});

dht.on('ready', () => {
  logger.info('now ready');
});

dht.on('announce', (peer, infoHash) => {
  logger.info(`announce : ${peer.host}:${peer.port} : ${infoHash.toString('hex')}`);
  dht.lookup(infoHash);
  client.publish("DHTS", infoHash.toString('hex'));
});

dht.on('peer', (peer, infoHash, from) => {
  logger.debug(`peer : ${peer.host}:${peer.port} : ${infoHash.toString('hex')}`);
});

dht.on('error', (err) => {
  logger.error(err);
  dht.destroy();
});


text:
"use strict";

const config = require('./config/database');

const Promise = require("bluebird");

const mongoose = require('mongoose');
mongoose.Promise = Promise;
mongoose.connect(config.db.uri);

const path = require('path');

const CronJob = require("cron").CronJob;

const Torrent = require('./models/Torrent.js');
const Classifier = require('./models/Classifier.js');

const natural = require("natural");
let   classifier = new natural.BayesClassifier();

const bunyan = require("bunyan");
const logger = bunyan.createLogger({name: "trainer"});

const filter = { $nor: [ { 'category' : /Unknown/ }, { 'category' : /Other/ } ] };

const job = new CronJob("* 30 * * * *", function() { // run each 30 min
  const cursor = Torrent.find(filter).sort({'imported': -1}).cursor();
  cursor.eachAsync(torrent => {
    logger.info(`Adding ${torrent._id} training`);
    if(!torrent.files) return Promise.reject(`Torrent ${torrent._id} has no files!`);
    const exts = torrent.files
      .map(file => path.extname(file).toLowerCase())
      .filter(ext => ext.length > 0) // no empty
      .filter(ext => ext.length < config.limitExt) // with min length
    return Promise.resolve(classifier.addDocument(exts, torrent.category));
  })
  .then(() => Promise.resolve(classifier.train()))
  .then(() => Classifier.findOneAndUpdate( {}, { $set : { 'raw' : JSON.stringify(classifier) } }, { upsert : true }))
  .catch(err => Promise.reject(logger.error(err)))
});
job.start();


text:
"use strict";

const config = require('./config/database');

const Promise = require("bluebird");

const mongoose = require('mongoose');
mongoose.Promise = Promise;
mongoose.connect(config.db.uri);

const path = require('path');

const Torrent = require('./models/Torrent.js');

const bunyan = require("bunyan");
const logger = bunyan.createLogger({name: "categorize"});

const CronJob = require("cron").CronJob;

const filter = { 'category' : /Unknown/ };

const dynamicToIgnore = () => {
  const pad = (n, width, z) => {
    z = z || '0';
    n = n + '';
    return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
  }
  const result = [];
  // need to refactor this shit
  for(var i=2; i<100; i++) {
    result.push('.s'+pad(i,2));
    result.push('.r'+pad(i,2));
    result.push('.z'+pad(i,2));
    result.push('.'+pad(i,2));
  }
  for(var i=2; i<1000; i++) {
    result.push('.s'+pad(i,3));
    result.push('.r'+pad(i,3));
    result.push('.z'+pad(i,3));
    result.push('.'+pad(i,3));
  }
  return result;
}

function findCategoryBasedOnExtensions(exts) {
  return Object.keys(config.extToCateg)
    .map(categ => { // browse category extensions
      if ( config.extToCateg[categ].some(c => exts.includes(c)) )
      return categ;
    })
    .find((c) => c !== undefined); // find the first category
}

const job = new CronJob("30 * * * * *", function() { // run each 30 seconds
  const cursor = Torrent.find(filter).sort({'imported': -1}).limit(100).cursor();
  cursor.eachAsync(torrent => {
    logger.info(`Treating ${torrent._id} categorization`);
    if(!torrent.files) {
      logger.info(`Torrent ${torrent._id} has no files!`);
      return Promise.resolve(torrent);
    }
    const exts = torrent.files
      .map(file => path.extname(file).toLowerCase())
      .filter(ext => ext.length > 0) // no empty
      .filter(ext => !config.extToIgnore.includes(ext)) // no ignored
      .filter(ext => !dynamicToIgnore().includes(ext)) // no special ignored
      .filter(ext => ext.length < config.limitExt) // with min length
      .slice() // shallow copy
      .sort() // sort
      .reduce((p, c) => {
        if(p[0] !== c) return p.concat(c);
        return p;
      }, []) // deduplicate

    if(exts.length > 5) {
      return Promise.resolve(`Torrent ${torrent._id} has no too many extensions!`);
    }

    const category = findCategoryBasedOnExtensions(exts);
    torrent.category = category || "Unknown";
    return torrent.save();
  })
  .then(() => Promise.resolve(logger.info(`All torrents treated`)))
  .catch(err => Promise.reject(logger.error(err)))
});
job.start();


text:
FROM keymetrics/pm2:6-jessie

RUN groupadd -r app && useradd -r -g app app

ADD . /var/www
WORKDIR /var/www

RUN npm install --no-optional
CMD [ "pm2-runtime", "start", "ecosystem.config.js" ]


text:
'use strict';

const config = require('./config/database');

const Promise = require('bluebird');

const Aria2 = require('aria2');
const fs = require('fs');

const magnet = require('magnet-uri');

const redis = require('redis');
Promise.promisifyAll(redis.RedisClient.prototype);
const client1 = redis.createClient(config.redis.port, config.redis.host,
  config.redis.options);
const client2 = redis.createClient(config.redis.port, config.redis.host,
  config.redis.options);

const bunyan = require('bunyan');
const logger = bunyan.createLogger({ name: 'loader' });

const MAGNET_TEMPLATE =
  magnet.encode({ xt: 'urn:btih:{DHTHASH}', tr: config.trackers })

const aria2 = new Aria2(config.aria2);

const aria2Options =
{
  'bt-metadata-only': 'true',
  'bt-save-metadata': 'true',
  'follow-torrent': 'false',
  'seed-time': 0
}

client1.on('subscribe', (channel, count) => {
  logger.info(`Subscribed : ${channel}`);
})

client1.on('message', (channel, message) => {
  let magnetLink = MAGNET_TEMPLATE;
  return Promise.resolve(message)
    .then(hash => {
      return new Promise((resolve, reject) => {
        return client2.get(hash, (err, reply) => {
          if (err) reject(err);
          if (reply) reject(`${hash} already present`);
          return client2.setex(hash, 60, "OK", () => {
            resolve(hash)
          })
        })
      })
    })
    .then(hash => {
      return new Promise((resolve, reject) => {
        if (!hash)
          reject('No torrent in queue');
        const filename =
          `${__dirname}/torrent/${hash.toString().toUpperCase()}.torrent`;
        magnetLink = MAGNET_TEMPLATE.replace(
          '{DHTHASH}', hash.toString().toUpperCase());
        if (fs.existsSync(filename)) {
          reject(`File ${filename} already exists`);
        }
        resolve(magnetLink)
      });
    })
    .then(() => aria2.open())
    .then(() => aria2.getVersion())
    .then(() => aria2.addUri([magnetLink], aria2Options))
    .then(res => Promise.resolve(
      logger.info(`Added : ${magnetLink} => ${res}`)))
    .then(() => aria2.close())
    .catch((err) => logger.error(err));
});

client1.subscribe('DHTS');


text:
# dht-bay
A DHT crawler, torrent indexer and search portal all in nodeJS

DEPENDENCIES
------------

This project works fine with :
- node 6.3.1
- npm 3.10.3
- redis-server 2.8.17
- mongod 2.4.10
- aria2 1.33.1

INSTALL
-------

#### Good to know

You should open your 6881/udp port to allow the crawler to have access to DHT network.

#### Use docker

Or you can just use the docker project version and run it using :

```
docker-compose up -d
```

It will automatically launch redis, mongo and aria2 then start crawling and categorizing for you. You'll have your DHT Crawler up and running. Crawling may take some time so be patient.

ARCHITECTURE
------------

This diagram presents an overview of the project architecture and how each piece communicate with each other.

![DHTBay Architecture](./documentation/architecture.svg)

CONTENT
-------

The project is composed of 4 modules as presented. Each module is independant and can be used separately without problem.

+  **crawlDHT.js** is responsible for crawling hashs from the DHT network. It will push hashes on a redis list called *DHTS*. It also provides a routing table backup system saving it each 10 minutes in a mongo collection called table.
+  **loadDHT.js** is responsible of loading hashes from the redis list *DHTS* and to download torrent metadat for indexation. It rely intensely on *aria2* tool and tray to download it from torcache, torrage and through DHT.
+  **loadTorrent.js** is responsible of saving metadatas into our mongo instance in collection torrents. This will be our basis data.
+  **categorize.js** will try to categorize crawled torrent depending on file extensions. Because module only takes a limited number of extensions in account you can use classifier too.
+  **classifier.js** a bayesian classifier that will classify torrent that couldn't be classed by previous one. In order to work you need to train the classifier.
+  **trainer.js** the bayesian classifier trainer, based on categorize script classification it helps unknown torrent classification.

You could use the bayesian classifier when you've already had a bunch of torrent indexed. The more samples you'll have the more accurate it will be.

Please fork it, and use it everywhere you can.

IMPROVEMENTS
------------

+ Improve categorization to support more extensions. Use a [categorized API](https://github.com/FlyersWeb/file-extension-api).

Have fun.

@flyersweb


text:
var mongoose = require('mongoose');

var TorrentSchema = mongoose.Schema({
  _id: { type: String, index: true },
  title: { type: String, index: true },
  category: { type: String, default: "Unknown", index: true },
  details: { type: [String], default: [] },
  size: { type: Number, default: 0 },
  files: { type: [String], default: [] },
  swarm: {
    seeders: { type: Number, default: 0, index: true },
    leechers: { type: Number, default: 0 }
  },
  imported: {type: Date, default: Date.now, index: true},
  lastmod: {type: Date, default: Date.now, index: true}
});
var Torrent = mongoose.model('Torrent',TorrentSchema);

module.exports = Torrent;


text:
var mongoose = require('mongoose');

var ClassifierSchema = mongoose.Schema({
  raw: { type: mongoose.Schema.Types.Mixed, default: {} },
  updated : { type: Date, default: Date.now, index: true }
});
var Classifier = mongoose.model('Classifier',ClassifierSchema);

module.exports = Classifier;


text:
var config = {
  db : {
    uri : 'mongodb://mongo:27017/bitcannon'
  },
  redis : {
    port : 6379,
    host : 'redis',
    options : {}
  },
  aria2 : {
    host: 'aria2',
    port: 6800,
    secure: false
  },
  trackers: [
    "udp://tracker.coppersurfer.tk:6969/announce",
    "udp://tracker.leechers-paradise.org:6969/announce",
    "udp://tracker.zer0day.to:1337/announce",
    "http://tracker.opentrackr.org:1337/announce",
    "udp://tracker.opentrackr.org:1337/announce",
    "udp://p4p.arenabg.com:1337/announce",
    "http://p4p.arenabg.com:1337/announce",
    "udp://9.rarbg.com:2710/announce",
    "http://explodie.org:6969/announce",
    "udp://explodie.org:6969/announce",
    "udp://public.popcorn-tracker.org:6969/announce",
    "udp://tracker.internetwarriors.net:1337/announce",
    "http://tracker.dler.org:6969/announce",
    "http://tracker1.wasabii.com.tw:6969/announce",
    "http://tracker.mg64.net:6881/announce",
    "http://mgtracker.org:6969/announce",
    "udp://tracker.mg64.net:6969/announce",
    "udp://mgtracker.org:2710/announce",
    "http://tracker2.wasabii.com.tw:6969/announce",
    "http://tracker.tiny-vps.com:6969/announce"
  ],
  limitExt : 8,
  extToIgnore : [
    '.url', '.txt', '.ico', '.srt', '.gif', '.log',
    '.nfo', '.ass', '.lnk', '.rtf', '.bc!',
    '.bmp', '.m3u', '.mht', '.cue', '.sfv', '.diz',
    '.azw3', '.odt', '.chm', '.md5', '.idx', '.sub',
    '.ini', '.html', '.ssa', '.lit', '.xml', '.clpi',
    '.bup', '.ifo', '.htm', '.info', '.css', '.php',
    '.js', '.jar', '.json', '.sha', '.docx', '.csv',
    '.scr', '.inf', '.hdr', '.prq', '.isn', '.inx', '.tpl',
    '.aco', '.opa', '.dpc', '.qdl2', '.acf', '.cdx',
    '.iwd', '.ff', '.tmp', '.asi', '.flt', '.cfg',
    '.tdl', '.tta', '.ape', '.btn', '.sig', '.sql', '.db',
    '.zdct', '.bak', '.fxp', '.nxp', '.nsk', '.256',
    '.mpls', '.clpi', '.bdmv', '.cdd', '.dbf',
    '.vmx', '.vmsd', '.vmxf', '.nvram'
  ],
  extToCateg : {
    'Picture' : ['.png', '.jpeg', '.jpg'],
    'Program' : ['.exe', '.dll', '.msi', '.jar'],
    'ISO'   : ['.rar', '.01', '.001', 'r01', 'r001', 'z01', 'z001', '.iso', '.zip', '.dmg', '.tgz', '.gz', '.chd', '.7z', '.cab', '.apk', '.cdr', '.wbfs', '.dat', '.rar', '.lzma', '.mds', '.gho', '.ima', '.nrg', '.pkg', '.b5i', '.mdx', '.isz', '.vmdk'],
    'Book' : ['.epub', '.pdf', '.cbz', '.cbr', '.cb7', '.cba', '.cbt', '.djvu', '.fb2', '.mobi', '.doc'],
    'Audio' : ['.flac', '.mp3', '.m4p', '.m4r', '.m4a', '.m4b', '.ape', '.wma', '.ogg', '.dsf', '.wav'],
    'Video' : ['.mp4', '.mkv', '.3gp', '.flv', '.f4v', '.avi', '.rm', '.rmvb', '.wmv', '.mov', '.mpg', '.mpeg', '.ts', '.m2ts', '.m4v', '.asf', '.vob', '.divx', '.srt'],
  }
};

module.exports = config;


text:
dir=/data
save-session=/session
check-integrity=true
log=-
log-level=error
rpc-listen-all=true
rpc-secure=false
dht-listen-port=6900
enable-rpc=true
enable-dht=true
enable-dht6=true
bt-metadata-only=true
bt-save-metadata=true
bt-stop-timeout=120
on-download-complete=/oncomplete.sh
seed-ratio=0
seed-time=0


text:
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem      https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem

@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem  Gradle startup script for Windows
@rem
@rem ##########################################################################

@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi

@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"

@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome

set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.

goto fail

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar

@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*

:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd

:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if  not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1

:mainEnd
if "%OS%"=="Windows_NT" endlocal

:omega

repo_name: vaperion/blade

repo_language: Java

mime_type: text/x-java


text:
install:
  - chmod +x ./gradlew
  - ./gradlew clean build publishToMavenLocal


text:
#!/usr/bin/env sh

#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

##############################################################################
##
##  Gradle start up script for UN*X
##
##############################################################################

# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
    ls=`ls -ld "$PRG"`
    link=`expr "$ls" : '.*-> \(.*\)$'`
    if expr "$link" : '/.*' > /dev/null; then
        PRG="$link"
    else
        PRG=`dirname "$PRG"`"/$link"
    fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null

APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"

warn () {
    echo "$*"
}

die () {
    echo
    echo "$*"
    echo
    exit 1
}

# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
  CYGWIN* )
    cygwin=true
    ;;
  Darwin* )
    darwin=true
    ;;
  MINGW* )
    msys=true
    ;;
  NONSTOP* )
    nonstop=true
    ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar

# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
    if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
        # IBM's JDK on AIX uses strange locations for the executables
        JAVACMD="$JAVA_HOME/jre/sh/java"
    else
        JAVACMD="$JAVA_HOME/bin/java"
    fi
    if [ ! -x "$JAVACMD" ] ; then
        die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
    fi
else
    JAVACMD="java"
    which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi

# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
    MAX_FD_LIMIT=`ulimit -H -n`
    if [ $? -eq 0 ] ; then
        if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
            MAX_FD="$MAX_FD_LIMIT"
        fi
        ulimit -n $MAX_FD
        if [ $? -ne 0 ] ; then
            warn "Could not set maximum file descriptor limit: $MAX_FD"
        fi
    else
        warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
    fi
fi

# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
    GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi

# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
    APP_HOME=`cygpath --path --mixed "$APP_HOME"`
    CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`

    JAVACMD=`cygpath --unix "$JAVACMD"`

    # We build the pattern for arguments to be converted via cygpath
    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
    SEP=""
    for dir in $ROOTDIRSRAW ; do
        ROOTDIRS="$ROOTDIRS$SEP$dir"
        SEP="|"
    done
    OURCYGPATTERN="(^($ROOTDIRS))"
    # Add a user-defined pattern to the cygpath arguments
    if [ "$GRADLE_CYGPATTERN" != "" ] ; then
        OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
    fi
    # Now convert the arguments - kludge to limit ourselves to /bin/sh
    i=0
    for arg in "$@" ; do
        CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
        CHECK2=`echo "$arg"|egrep -c "^-"`                                 ### Determine if an option

        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition
            eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
        else
            eval `echo args$i`="\"$arg\""
        fi
        i=`expr $i + 1`
    done
    case $i in
        0) set -- ;;
        1) set -- "$args0" ;;
        2) set -- "$args0" "$args1" ;;
        3) set -- "$args0" "$args1" "$args2" ;;
        4) set -- "$args0" "$args1" "$args2" "$args3" ;;
        5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
        6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
        7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
        8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
        9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
    esac
fi

# Escape application args
save () {
    for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
    echo " "
}
APP_ARGS=`save "$@"`

# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"

exec "$JAVACMD" "$@"


text:
# Blade

Blade is an easy-to-use command framework based on annotations. It currently supports Bukkit and Velocity.

If you make any changes or improvements to the project, please consider making a pull request to merge your changes back into the upstream project.
If you find any issues please open an issue.

This project follows [Semantic Versioning](https://semver.org/).

## YourKit

YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET applications. YourKit is the creator of [YourKit Java Profiler](https://www.yourkit.com/java/profiler/), [YourKit .NET Profiler](https://www.yourkit.com/.net/profiler/) and [YourKit YouMonitor](https://www.yourkit.com/youmonitor/).

![YourKit](https://www.yourkit.com/images/yklogo.png)

## Usage

First, you need to set up the dependency. You can do this by following the instructions below depending on your build system.
You also need to shade the library into your final jar. You can do this by choosing a shade plugin for your build system and shading the library into your final jar.

### Maven

```xml

<repositories>
    <repository>
        <id>jitpack.io</id>
        <url>https://jitpack.io</url>
    </repository>
</repositories>

<dependencies>
<dependency>
    <groupId>com.github.vaperion.blade</groupId>
    <!-- Replace PLATFORM with the platform you want to use: bukkit or velocity -->
    <artifactId>PLATFORM</artifactId>
    <!-- Replace VERSION with your desired version -->
    <version>VERSION</version>
    <scope>provided</scope>
</dependency>
</dependencies>
```

### Gradle

```groovy
repositories {
    maven { url 'https://jitpack.io' }
}

dependencies {
    // Replace PLATFORM with the platform you want to use: bukkit or velocity
    // Replace VERSION with your desired version
    implementation 'com.github.vaperion.blade:PLATFORM:VERSION'
}
```

## Getting Started

### Creating an example command

```java
public class ExampleCommand {
    @Command("example")
    @Description("The description of your command, optional.")
    @Permission("command.permission") // Optional, set to "op" to require OP
    @Hidden // Optional, hides the command from the generated help
    @Async // Optional
    @ParseQuotes // Optional, parses quoted strings into a single argument
    public static void example(
          // Command sender, required:
          @Sender CommandSender sender,
          // Type can be: CommandSender, Player, ConsoleCommandSender

          // Regular arguments:
          @Name("player") Player player,
          // You can make the argument optional:
          @Name("player") @Optional Player player,
          // or, you can make it default to the sender if not provided:
          @Name("player") @Optional("self") Player player,
          // Multi-word (combined) string arguments:
          @Name("message") @Text String message,
          // Number arguments with a range:
          @Name("amount") @Range(min = 1, max = 64) int amount,

          // Command flags:
          @Flag(value = 's', description = "Optional description") boolean flagSilent,
          // You can also have complex types as flags:
          @Flag('p') Player anotherPlayer
    ) {
        sender.sendMessage("(You -> " + anotherPlayer + ") $" + amount);

        if (!flagSilent) {
            player.sendMessage("(" + sender.getName() + " -> You) +$" + amount);
        } else {
            player.sendMessage("(Anonymous -> You) +$" + amount);
        }
    }
}
```

### Creating an example argument type

```java
public class Data {
    public String message;
    public boolean wasProvided;
}

public class DataArgumentProvider implements ArgumentProvider<Data> {
    @Override
    public @Nullable Data provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        Data data = new Data();

        if (arg.getType() == Argument.Type.OPTIONAL) {
            data.wasProvided = false;
            data.message = "Default value: " + arg.getString();
        } else {
            data.wasProvided = true;
            data.message = arg.getString();
        }

        // If you encounter an error while parsing, you may:
        throw new BladeUsageMessage(); // to show the usage message
        // or, return a custom error message:
        throw new BladeExitMessage("Custom error message");

        // If you couldn't parse their input, you can check if the argument is optional:
        if (arg.getParameter().ignoreFailedArgumentParse()) {
            // If it is, you can return null
            return null;
        } else {
            // If it isn't, you can throw an exception to show a custom message
            throw new BladeExitMessage("Sorry, I couldn't parse your input.");
        }

        return data;
    }

    @Override
    public @NotNull List<String> suggest(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        return Collections.singletonList("example");
    }
}
```

### Registering your commands and argument types

```java
public class MyPlugin extends JavaPlugin {
    @Override
    public void onEnable() {
        Blade.forPlatform(new BladeBukkitPlatform(this))
              .config(cfg -> {
                  cfg.setFallbackPrefix("myplugin"); // Optional, defaults to your plugin's name
                  cfg.setDefaultPermissionMessage("No permission!"); // Optional
              })
              .bind(binder -> {
                  binder.release(Player.class); // To remove the default provider
                  binder.bind(Player.class, new MyPlayerProvider()); // To add your own
              })
              .build()
              // Now, you can register all commands in a package:
              .registerPackage(MyPlugin.class, "com.example.commands")
              // or, you can register them individually:
              .register(ExampleCommand.class).register(AnotherCommand.class)
        ;
    }
}
```


text:
package me.vaperion.blade.bukkit;

import lombok.RequiredArgsConstructor;
import me.vaperion.blade.Blade;
import me.vaperion.blade.Blade.Builder.Binder;
import me.vaperion.blade.bukkit.argument.OfflinePlayerArgument;
import me.vaperion.blade.bukkit.argument.PlayerArgument;
import me.vaperion.blade.bukkit.container.BukkitContainer;
import me.vaperion.blade.bukkit.platform.BukkitHelpGenerator;
import me.vaperion.blade.bukkit.platform.ProtocolLibTabCompleter;
import me.vaperion.blade.container.ContainerCreator;
import me.vaperion.blade.platform.BladeConfiguration;
import me.vaperion.blade.platform.BladePlatform;
import me.vaperion.blade.platform.TabCompleter;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;

import java.util.Locale;

@RequiredArgsConstructor
public final class BladeBukkitPlatform implements BladePlatform {

    private final JavaPlugin plugin;

    @Override
    public @NotNull Object getPluginInstance() {
        return plugin;
    }

    @Override
    public @NotNull ContainerCreator<?> getContainerCreator() {
        return BukkitContainer.CREATOR;
    }

    @Override
    public void configureBlade(Blade.@NotNull Builder builder, @NotNull BladeConfiguration configuration) {
        configuration.setPluginInstance(plugin);
        configuration.setFallbackPrefix(plugin.getName().toLowerCase(Locale.ROOT));
        configuration.setHelpGenerator(new BukkitHelpGenerator());
        configuration.setTabCompleter(Bukkit.getPluginManager().isPluginEnabled("ProtocolLib") ? new ProtocolLibTabCompleter(plugin) : new TabCompleter.Default());

        Binder binder = new Binder(builder, true);
        binder.bind(Player.class, new PlayerArgument());
        binder.bind(OfflinePlayer.class, new OfflinePlayerArgument());
    }
}


text:
package me.vaperion.blade.bukkit.context;

import lombok.RequiredArgsConstructor;
import me.vaperion.blade.context.WrappedSender;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@RequiredArgsConstructor
public final class BukkitSender implements WrappedSender<CommandSender> {
    private final CommandSender commandSender;

    @NotNull
    @Override
    public CommandSender getSender() {
        return commandSender;
    }

    @NotNull
    @Override
    public String getName() {
        return commandSender.getName();
    }

    @Override
    public boolean hasPermission(@NotNull String permissionNode) {
        if ("op".equals(permissionNode))
            return commandSender instanceof ConsoleCommandSender || commandSender.isOp();
        if ("console".equals(permissionNode))
            return commandSender instanceof ConsoleCommandSender;
        return commandSender.hasPermission(permissionNode);
    }

    @Override
    public void sendMessage(@NotNull String message) {
        commandSender.sendMessage(message);
    }

    @Override
    public void sendMessage(@NotNull String... messages) {
        commandSender.sendMessage(messages);
    }

    @SuppressWarnings("unchecked")
    @Nullable
    @Override
    public <T> T parseAs(@NotNull Class<T> clazz) {
        if (clazz.equals(Player.class) && commandSender instanceof Player)
            return (T) commandSender;
        else if (clazz.equals(ConsoleCommandSender.class) && commandSender instanceof ConsoleCommandSender)
            return (T) commandSender;
        else if (clazz.equals(CommandSender.class))
            return (T) commandSender;

        return null;
    }
}


text:
package me.vaperion.blade.bukkit.command;

import me.vaperion.blade.annotation.argument.Flag;
import me.vaperion.blade.bukkit.util.MessageBuilder;
import me.vaperion.blade.command.Command;
import me.vaperion.blade.command.Parameter.CommandParameter;
import me.vaperion.blade.command.Parameter.FlagParameter;
import me.vaperion.blade.command.UsageMessage;
import me.vaperion.blade.context.Context;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;

public final class BukkitUsageMessage implements UsageMessage {

    private final BaseComponent[] components;
    private String toString;

    public BukkitUsageMessage(Command command) {
        this(command, true);
    }

    public BukkitUsageMessage(Command command, boolean addPrefix) {
        MessageBuilder messageBuilder = new MessageBuilder((addPrefix ? "Usage: " : "") + "/").color(ChatColor.RED)
              .hoverWithColor(ChatColor.GRAY, command.getDescription())
              .append(command.getUsageAlias());

        if (!command.getCustomUsage().isEmpty()) {
            messageBuilder.append(" ").append(command.getCustomUsage());
            this.components = messageBuilder.build();
            return;
        }

        // Add flag parameters
        boolean first = true;
        for (FlagParameter flagParameter : command.getFlagParameters()) {
            Flag flag = flagParameter.getFlag();

            if (first) {
                messageBuilder.append(" (").reset().color(ChatColor.RED).hoverWithColor(ChatColor.GRAY, command.getDescription());
                first = false;
            } else {
                messageBuilder.append(" | ").reset().color(ChatColor.RED).hoverWithColor(ChatColor.GRAY, command.getDescription());
            }

            messageBuilder
                  .append("-" + flag.value() + (flagParameter.isBooleanFlag() ? "" : " <" + flagParameter.getName() + ">"))
                  .color(ChatColor.AQUA)
                  .hoverWithColor(ChatColor.GRAY, flag.description());
        }
        if (!first) messageBuilder.append(")").reset().color(ChatColor.RED).hoverWithColor(ChatColor.GRAY, command.getDescription());

        // Add real parameters
        for (CommandParameter commandParameter : command.getCommandParameters()) {
            messageBuilder.append(" ");

            messageBuilder.append(commandParameter.isOptional() ? "(" : "<");
            messageBuilder.append(commandParameter.getName());
            if (commandParameter.isText()) messageBuilder.append("...");
            messageBuilder.append(commandParameter.isOptional() ? ")" : ">");
        }

        // Add extra usage
        if (!command.getExtraUsageData().isEmpty()) {
            messageBuilder.append(" ").append(command.getExtraUsageData().trim());
        }

        this.components = messageBuilder.build();
    }

    @Override
    public void sendTo(@NotNull Context context) {
        MessageBuilder.send((CommandSender) context.sender().getSender(), components);
    }

    @NotNull
    @Override
    public String toString() {
        if (toString == null) toString = MessageBuilder.toStringFormat(components);
        return toString;
    }
}


text:
package me.vaperion.blade.bukkit.argument;

import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.Argument.Type;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import me.vaperion.blade.exception.BladeUsageMessage;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

public class OfflinePlayerArgument implements ArgumentProvider<OfflinePlayer> {

    public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89ab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}");

    @Nullable
    @Override
    public OfflinePlayer provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        Player player = ctx.sender().parseAs(Player.class);

        if (arg.getType() == Type.OPTIONAL && "self".equals(arg.getString())) {
            if (player != null) return player;
            else
                throw new BladeUsageMessage(); // show usage to console if we have 'self' as a default value (only works on players)
        }

        return getOfflinePlayer(arg.getString());
    }

    @NotNull
    @Override
    public List<String> suggest(@NotNull Context context, @NotNull Argument arg) throws BladeExitMessage {
        Player sender = context.sender().parseAs(Player.class);
        List<String> completions = new ArrayList<>();

        String input = arg.getString();

        for (Player player : Bukkit.getServer().getOnlinePlayers()) {
            if (player.getName().toLowerCase().startsWith(input.toLowerCase()) && (sender == null || sender.canSee(player)))
                completions.add(player.getName());
        }

        return completions;
    }

    private boolean isUUID(@NotNull String input) {
        return UUID_PATTERN.matcher(input).matches();
    }

    @NotNull
    private OfflinePlayer getOfflinePlayer(@NotNull String input) {
        if (isUUID(input)) return Bukkit.getOfflinePlayer(UUID.fromString(input));
        return Bukkit.getOfflinePlayer(input);
    }

}


text:
package me.vaperion.blade.bukkit.argument;

import me.vaperion.blade.argument.Argument;
import me.vaperion.blade.argument.Argument.Type;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import me.vaperion.blade.exception.BladeUsageMessage;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.regex.Pattern;

public class PlayerArgument implements ArgumentProvider<Player> {

    public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[34][0-9a-fA-F]{3}-[89ab][0-9a-fA-F]{3}-[0-9a-fA-F]{12}");

    @Nullable
    @Override
    public Player provide(@NotNull Context ctx, @NotNull Argument arg) throws BladeExitMessage {
        Player player = ctx.sender().parseAs(Player.class);

        if (arg.getType() == Type.OPTIONAL && "self".equals(arg.getString())) {
            if (player != null) return player;
            else
                throw new BladeUsageMessage(); // show usage to console if we have 'self' as a default value (only works on players)
        }

        Player onlinePlayer = getPlayer(arg.getString());
        if (onlinePlayer == null && !arg.getParameter().ignoreFailedArgumentParse())
            throw new BladeExitMessage("Error: No online player with name or UUID '" + arg.getString() + "' found.");

        return onlinePlayer;
    }

    @NotNull
    @Override
    public List<String> suggest(@NotNull Context context, @NotNull Argument arg) throws BladeExitMessage {
        Player sender = context.sender().parseAs(Player.class);
        List<String> completions = new ArrayList<>();

        String input = arg.getString();

        for (Player player : Bukkit.getServer().getOnlinePlayers()) {
            if (player.getName().toLowerCase().startsWith(input.toLowerCase()) && (sender == null || sender.canSee(player)))
                completions.add(player.getName());
        }

        return completions;
    }

    private boolean isUUID(@NotNull String input) {
        return UUID_PATTERN.matcher(input).matches();
    }

    @Nullable
    private Player getPlayer(@NotNull String input) {
        if (isUUID(input)) return Bukkit.getPlayer(UUID.fromString(input));
        return Bukkit.getPlayer(input);
    }
}


text:
package me.vaperion.blade.bukkit.container;

import lombok.Getter;
import me.vaperion.blade.Blade;
import me.vaperion.blade.bukkit.command.BukkitUsageMessage;
import me.vaperion.blade.bukkit.context.BukkitSender;
import me.vaperion.blade.container.Container;
import me.vaperion.blade.container.ContainerCreator;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.exception.BladeExitMessage;
import me.vaperion.blade.exception.BladeUsageMessage;
import me.vaperion.blade.util.Tuple;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.plugin.SimplePluginManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.stream.Collectors;

@Getter
public final class BukkitContainer extends Command implements Container {

    public static final ContainerCreator<BukkitContainer> CREATOR = BukkitContainer::new;
    private static final Field COMMAND_MAP, KNOWN_COMMANDS;
    private static final String UNKNOWN_COMMAND_MESSAGE;

    static {
        Field mapField = null, commandsField = null;
        String unknownCommandMessage = ChatColor.WHITE + "Unknown command. Type \"/help\" for help.";

        try {
            Class<?> spigotConfigClass = Class.forName("org.spigotmc.SpigotConfig");
            Field unknownCommandField = spigotConfigClass.getDeclaredField("unknownCommandMessage");

            unknownCommandField.setAccessible(true);
            unknownCommandMessage = ChatColor.WHITE + (String) unknownCommandField.get(null);
        } catch (Exception ex) {
            System.err.println("Failed to grab unknown command message from SpigotConfig.");
            ex.printStackTrace();
        }

        try {
            mapField = SimplePluginManager.class.getDeclaredField("commandMap");
            commandsField = SimpleCommandMap.class.getDeclaredField("knownCommands");

            mapField.setAccessible(true);
            commandsField.setAccessible(true);
        } catch (Exception ex) {
            System.err.println("Failed to grab commandMap from the plugin manager.");
            ex.printStackTrace();
        }

        COMMAND_MAP = mapField;
        KNOWN_COMMANDS = commandsField;
        UNKNOWN_COMMAND_MESSAGE = unknownCommandMessage;
    }

    private final Blade blade;
    private final me.vaperion.blade.command.Command baseCommand;

    @SuppressWarnings("unchecked")
    private BukkitContainer(@NotNull Blade blade, @NotNull me.vaperion.blade.command.Command command, @NotNull String alias) throws Exception {
        super(alias, command.getDescription(), "/" + alias, new ArrayList<>());

        this.blade = blade;
        this.baseCommand = command;

        SimplePluginManager simplePluginManager = (SimplePluginManager) Bukkit.getServer().getPluginManager();
        SimpleCommandMap simpleCommandMap = (SimpleCommandMap) COMMAND_MAP.get(simplePluginManager);

        if (blade.getConfiguration().isOverrideCommands()) {
            Map<String, Command> knownCommands = (Map<String, Command>) KNOWN_COMMANDS.get(simpleCommandMap);
            Iterator<Map.Entry<String, Command>> iterator = knownCommands.entrySet().iterator();

            while (iterator.hasNext()) {
                Map.Entry<String, Command> entry = iterator.next();
                Command registeredCommand = entry.getValue();

                if (doesBukkitCommandConflict(registeredCommand, alias, command)) {
                    registeredCommand.unregister(simpleCommandMap);
                    iterator.remove();
                }
            }
        }

        simpleCommandMap.register(blade.getConfiguration().getFallbackPrefix(), this);
    }

    private boolean doesBukkitCommandConflict(@NotNull Command bukkitCommand, @NotNull String alias, @NotNull me.vaperion.blade.command.Command command) {
        if (bukkitCommand instanceof BukkitContainer) return false; // don't override our own commands
        if (bukkitCommand.getName().equalsIgnoreCase(alias) || bukkitCommand.getAliases().stream().anyMatch(a -> a.equalsIgnoreCase(alias)))
            return true;
        for (String realAlias : command.getBaseCommands()) {
            if (bukkitCommand.getName().equalsIgnoreCase(realAlias) || bukkitCommand.getAliases().stream().anyMatch(a -> a.equalsIgnoreCase(realAlias)))
                return true;
        }
        return false;
    }

    @Nullable
    private Tuple<me.vaperion.blade.command.Command, String> resolveCommand(@NotNull String[] arguments) throws BladeExitMessage {
        return blade.getResolver().resolveCommand(arguments);
    }

    @NotNull
    private String getSenderType(@NotNull Class<?> clazz) {
        switch (clazz.getSimpleName()) {
            case "Player":
                return "players";

            case "ConsoleCommandSender":
                return "the console";

            default:
                return "everyone";
        }
    }

    private void sendUsageMessage(@NotNull Context context, @Nullable me.vaperion.blade.command.Command command) {
        if (command == null) return;
        command.getUsageMessage().ensureGetOrLoad(() -> new BukkitUsageMessage(command)).sendTo(context);
    }

    private boolean hasPermission(@NotNull CommandSender sender, String[] args) throws BladeExitMessage {
        Tuple<me.vaperion.blade.command.Command, String> command = resolveCommand(joinAliasToArgs(baseCommand.getAliases()[0], args));
        Context context = new Context(blade, new BukkitSender(sender), command == null ? "" : command.getRight(), args);
        return checkPermission(context, command == null ? null : command.getLeft()).getLeft();
    }

    private Tuple<Boolean, String> checkPermission(@NotNull Context context, @Nullable me.vaperion.blade.command.Command command) throws BladeExitMessage {
        if (command == null)
            return new Tuple<>(false, "This command failed to execute as we couldn't find its registration.");

        return new Tuple<>(
              blade.getPermissionTester().testPermission(context, command),
              command.isHidden() ? UNKNOWN_COMMAND_MESSAGE : command.getPermissionMessage());
    }

    private String[] joinAliasToArgs(String alias, String[] args) {
        String[] aliasParts = alias.split(" ");
        String[] argsWithAlias = new String[args.length + aliasParts.length];
        System.arraycopy(aliasParts, 0, argsWithAlias, 0, aliasParts.length);
        System.arraycopy(args, 0, argsWithAlias, aliasParts.length, args.length);
        return argsWithAlias;
    }

    @Override
    public boolean testPermissionSilent(@NotNull CommandSender sender) {
        return hasPermission(sender, new String[0]);
    }

    @Override
    public boolean execute(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) {
        me.vaperion.blade.command.Command command = null;
        String resolvedAlias;

        String[] joined = joinAliasToArgs(alias, args);
        Context context = new Context(blade, new BukkitSender(sender), alias, args);

        try {
            Tuple<me.vaperion.blade.command.Command, String> resolved = resolveCommand(joined);
            if (resolved == null) {
                List<me.vaperion.blade.command.Command> availableCommands = blade.getCommands()
                      .stream().filter(c -> Arrays.stream(c.getAliases()).anyMatch(a -> a.toLowerCase().startsWith(alias.toLowerCase(Locale.ROOT) + " ") || a.equalsIgnoreCase(alias)))
                      .collect(Collectors.toList());

                for (String line : blade.getConfiguration().getHelpGenerator().generate(context, availableCommands)) {
                    sender.sendMessage(line);
                }

                return true;
            }

            Tuple<Boolean, String> permissionResult = checkPermission(context, resolved.getLeft());
            if (!permissionResult.getLeft()) throw new BladeExitMessage(permissionResult.getRight());

            command = resolved.getLeft();
            resolvedAlias = resolved.getRight();
            int offset = Math.min(args.length, resolvedAlias.split(" ").length - 1);

            if (command.isHasSenderParameter() && !command.isWrappedSenderBased() && !command.isContextBased() && !command.getSenderType().isInstance(sender))
                throw new BladeExitMessage("This command can only be executed by " + getSenderType(command.getSenderType()) + ".");

            final me.vaperion.blade.command.Command finalCommand = command;
            final String finalResolvedAlias = resolvedAlias;

            if (finalCommand.getMethod() == null) {
                throw new BladeExitMessage("The command " + finalResolvedAlias + " is a root command and cannot be executed.");
            }

            Runnable runnable = () -> {
                try {
                    List<Object> parsed;
                    if (finalCommand.isContextBased()) {
                        parsed = Collections.singletonList(context);
                    } else {
                        parsed = blade.getParser().parseArguments(finalCommand, context, Arrays.copyOfRange(args, offset, args.length));
                        if (finalCommand.isHasSenderParameter()) {
                            if (finalCommand.isWrappedSenderBased()) parsed.add(0, context.sender());
                            else parsed.add(0, sender);
                        }
                    }

                    finalCommand.getMethod().invoke(finalCommand.getInstance(), parsed.toArray(new Object[0]));
                } catch (BladeUsageMessage ex) {
                    sendUsageMessage(context, finalCommand);
                } catch (BladeExitMessage ex) {
                    sender.sendMessage(ChatColor.RED + ex.getMessage());
                } catch (InvocationTargetException ex) {
                    if (ex.getTargetException() != null) {
                        if (ex.getTargetException() instanceof BladeUsageMessage) {
                            sendUsageMessage(context, finalCommand);
                            return;
                        } else if (ex.getTargetException() instanceof BladeExitMessage) {
                            sender.sendMessage(ChatColor.RED + ex.getTargetException().getMessage());
                            return;
                        }
                    }

                    ex.printStackTrace();
                    sender.sendMessage(ChatColor.RED + "An exception was thrown while executing this command.");
                } catch (Throwable t) {
                    t.printStackTrace();
                    sender.sendMessage(ChatColor.RED + "An exception was thrown while executing this command.");
                }
            };

            if (command.isAsync()) {
                blade.getConfiguration().getAsyncExecutor().accept(runnable);
            } else {
                long time = System.nanoTime();
                runnable.run();
                long elapsed = (System.nanoTime() - time) / 1000000;

                if (elapsed >= blade.getConfiguration().getExecutionTimeWarningThreshold()) {
                    Bukkit.getLogger().warning(String.format(
                          "[Blade] Command '%s' (%s#%s) took %d milliseconds to execute!",
                          finalResolvedAlias,
                          finalCommand.getMethod().getDeclaringClass().getName(),
                          finalCommand.getMethod().getName(),
                          elapsed
                    ));
                }
            }

            return true;
        } catch (BladeUsageMessage ex) {
            sendUsageMessage(context, command);
        } catch (BladeExitMessage ex) {
            sender.sendMessage(ChatColor.RED + ex.getMessage());
        } catch (Throwable t) {
            t.printStackTrace();
            sender.sendMessage(ChatColor.RED + "An exception was thrown while executing this command.");
        }

        return false;
    }

    @NotNull
    @Override
    public List<String> tabComplete(@NotNull CommandSender sender, @NotNull String alias, @NotNull String[] args) throws IllegalArgumentException {
        if (!blade.getConfiguration().getTabCompleter().isDefault()) return Collections.emptyList();
        if (!hasPermission(sender, args)) return Collections.emptyList();

        try {
            Tuple<me.vaperion.blade.command.Command, String> resolved = resolveCommand(joinAliasToArgs(alias, args));
            if (resolved == null) {
                // maybe suggest subcommands?
                return Collections.emptyList();
            }

            me.vaperion.blade.command.Command command = resolved.getLeft();
            String foundAlias = resolved.getRight();

            List<String> argList = new ArrayList<>(Arrays.asList(args));
            if (foundAlias.split(" ").length > 1) argList.subList(0, foundAlias.split(" ").length - 1).clear();

            if (argList.isEmpty()) argList.add("");
            String[] actualArguments = argList.toArray(new String[0]);

            Context context = new Context(blade, new BukkitSender(sender), foundAlias, actualArguments);

            List<String> suggestions = new ArrayList<>();
            blade.getCompleter().suggest(suggestions, context, command, actualArguments);
            return suggestions;
        } catch (BladeExitMessage ex) {
            sender.sendMessage(ChatColor.RED + ex.getMessage());
        } catch (Exception ex) {
            ex.printStackTrace();
            sender.sendMessage(ChatColor.RED + "An exception was thrown while completing this command.");
        }

        return Collections.emptyList();
    }

}


text:
package me.vaperion.blade.bukkit.util;

import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class MessageBuilder {

    public static String toStringFormat(BaseComponent[] components) {
        StringBuilder strBuilder = new StringBuilder();

        for (BaseComponent component : components) {
            strBuilder.append(component.toLegacyText());
        }

        return strBuilder.toString();
    }

    public static void send(CommandSender sender, BaseComponent[] components) {
        if (sender instanceof Player) {
            ((Player) sender).spigot().sendMessage(components);
            return;
        }

        sender.sendMessage(toStringFormat(components));
    }

    private final ComponentBuilder builder;

    public MessageBuilder(String mainText) {
        builder = new ComponentBuilder(mainText);
    }

    public MessageBuilder color(ChatColor color) {
        builder.color(color);
        return this;
    }

    public MessageBuilder append(String text) {
        builder.append(text);
        return this;
    }

    public MessageBuilder hover(String... lines) {
        return hover(Arrays.asList(lines));
    }

    public MessageBuilder hoverWithColor(ChatColor color, String... lines) {
        return hoverWithColor(color, Arrays.asList(lines));
    }

    public MessageBuilder hover(List<String> lines) {
        if (lines.isEmpty() || lines.stream().allMatch(String::isEmpty)) return this;
        builder.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(combineMultiLine(lines)).create()));
        return this;
    }

    public MessageBuilder hoverWithColor(ChatColor color, List<String> lines) {
        if (lines.isEmpty() || lines.stream().allMatch(String::isEmpty)) return this;
        builder.event(new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ComponentBuilder(lines.stream().map(s -> color.toString() + s).collect(Collectors.joining("\n"))).create()));
        return this;
    }

    public MessageBuilder click(String command, boolean instant) {
        builder.event(new ClickEvent(instant ? ClickEvent.Action.RUN_COMMAND : ClickEvent.Action.SUGGEST_COMMAND, command));
        return this;
    }

    public MessageBuilder reset() {
        builder.reset();
        return this;
    }

    public BaseComponent[] build() {
        return builder.create();
    }

    private String combineMultiLine(String[] lines) {
        return String.join("\n", lines);
    }

    private String combineMultiLine(List<String> lines) {
        return String.join("\n", lines);
    }

    public String toStringFormat() {
        return toStringFormat(build());
    }

    public void sendTo(CommandSender sender) {
        send(sender, build());
    }
}


text:
package me.vaperion.blade.bukkit.platform;

import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import me.vaperion.blade.Blade;
import me.vaperion.blade.bukkit.context.BukkitSender;
import me.vaperion.blade.platform.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import org.jetbrains.annotations.NotNull;

import java.util.List;

public class ProtocolLibTabCompleter extends PacketAdapter implements TabCompleter {

    private Blade blade;

    public ProtocolLibTabCompleter(@NotNull JavaPlugin plugin) {
        super(plugin, PacketType.Play.Client.TAB_COMPLETE);
    }

    @Override
    public void init(@NotNull Blade blade) {
        this.blade = blade;
        ProtocolLibrary.getProtocolManager().addPacketListener(this);
    }

    @Override
    public void onPacketReceiving(PacketEvent event) {
        if (event.getPlayer() == null) return;

        Player player = event.getPlayer();
        String commandLine = event.getPacket().getStrings().read(0);

        if (!commandLine.startsWith("/")) return;
        else commandLine = commandLine.substring(1);

        List<String> suggestions = blade.getCompleter().suggest(commandLine, () -> new BukkitSender(player));
        if (suggestions == null) return; // if command was not found

        try {
            event.setCancelled(true);
            PacketContainer tabComplete = new PacketContainer(PacketType.Play.Server.TAB_COMPLETE);
            tabComplete.getStringArrays().write(0, suggestions.toArray(new String[0]));
            ProtocolLibrary.getProtocolManager().sendServerPacket(player, tabComplete);
        } catch (Exception ex) {
            System.err.println("An exception was thrown while attempting to tab complete '" + commandLine + "' for player " + player.getName());
            ex.printStackTrace();
        }
    }
}


text:
package me.vaperion.blade.bukkit.platform;

import me.vaperion.blade.bukkit.command.BukkitUsageMessage;
import me.vaperion.blade.command.Command;
import me.vaperion.blade.context.Context;
import me.vaperion.blade.platform.HelpGenerator;
import me.vaperion.blade.util.PaginatedOutput;
import org.bukkit.ChatColor;
import org.jetbrains.annotations.NotNull;

import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;

public class BukkitHelpGenerator implements HelpGenerator {

    @NotNull
    @Override
    public List<String> generate(@NotNull Context context, @NotNull List<Command> commands) {
        commands = commands.stream()
              .distinct()
              .filter(c -> !c.isHidden())
              .sorted(context.blade().getConfiguration().getHelpSorter())
              .collect(Collectors.toList());

        int originalCount = commands.size();
        commands = commands.stream()
              .filter(c -> context.blade().getPermissionTester().testPermission(context, c))
              .collect(Collectors.toList());

        if (originalCount != 0 && commands.isEmpty()) {
            return Collections.singletonList(ChatColor.RED + context.blade().getConfiguration().getDefaultPermissionMessage());
        }

        return new PaginatedOutput<Command>(10) {
            @Override
            public String formatErrorMessage(Error error, Object... args) {
                switch (error) {
                    case NO_RESULTS:
                        return ChatColor.RED + "No results found.";
                    case PAGE_OUT_OF_BOUNDS:
                        return ChatColor.RED + String.format("Page %d does not exist, valid range is 1 to %d.", args);
                }
                return null;
            }

            @Override
            public String getHeader(int page, int totalPages) {
                return ChatColor.AQUA + "==== " + ChatColor.YELLOW + "Help for /" + context.alias() + ChatColor.AQUA + " ====";
            }

            @Override
            public String getFooter(int page, int totalPages) {
                return ChatColor.AQUA + "==== " + ChatColor.YELLOW + "Page " + page + "/" + totalPages + ChatColor.AQUA + " ====";
            }

            @Override
            public String formatLine(Command result, int index) {
                String help = ChatColor.stripColor(result.getHelpMessage().ensureGetOrLoad(() -> new BukkitUsageMessage(result, false)).toString());
                return ChatColor.AQUA + " - " + ChatColor.YELLOW + help + (result.getDescription().isEmpty() ? "" : (" - " + ChatColor.GRAY + result.getDescription()));
            }
        }.generatePage(commands, parsePage(context.argument(0)));
    }

    private int parsePage(String argument) {
        if (argument == null) return 1;
        try {
            return Integer.parseInt(argument);
        } catch (NumberFormatException e) {
            return 1;
        }
    }
}


text:
package me.vaperion.blade;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import me.vaperion.blade.Blade.Builder.Binder;
import me.vaperion.blade.argument.ArgumentProvider;
import me.vaperion.blade.argument.Provider;
import me.vaperion.blade.argument.impl.*;
import me.vaperion.blade.command.Command;
import me.vaperion.blade.container.Container;
import me.vaperion.blade.platform.BladeConfiguration;
import me.vaperion.blade.platform.BladePlatform;
import me.vaperion.blade.service.*;
import me.vaperion.blade.util.Binding;
import me.vaperion.blade.util.ClassUtil;
import me.vaperion.blade.util.PermissionPredicate;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.lang.annotation.Annotation;
import java.util.*;
import java.util.function.Consumer;

@Getter
public final class Blade {

    @NotNull
    @Contract("_ -> new")
    public static Builder forPlatform(@NotNull BladePlatform platform) {
        return new Builder(platform);
    }

    private final BladePlatform platform;
    private final BladeConfiguration configuration;

    private final Map<String, PermissionPredicate> permissionPredicates = new HashMap<>();

    private final List<Provider<?>> providers = new ArrayList<>();
    private final List<Command> commands = new ArrayList<>();
    private final Map<String, List<Command>> aliasToCommands = new HashMap<>();
    private final Map<String, Container> containers = new HashMap<>();

    private final CommandRegistrar registrar = new CommandRegistrar(this);
    private final CommandResolver resolver = new CommandResolver(this);
    private final CommandParser parser = new CommandParser(this);
    private final CommandCompleter completer = new CommandCompleter(this);
    private final PermissionTester permissionTester = new PermissionTester(this);

    private Blade(Builder builder) {
        this.platform = builder.platform;
        this.configuration = builder.configuration;

        permissionPredicates.putAll(builder.permissionPredicates);

        Binder binder = new Binder(builder, true);
        binder.bind(UUID.class, new UUIDArgument());
        binder.bind(String.class, new StringArgument());
        binder.bind(boolean.class, new BooleanArgument());
        binder.bind(Boolean.class, new BooleanArgument());
        binder.bind(int.class, new IntArgument());
        binder.bind(Integer.class, new IntArgument());
        binder.bind(long.class, new LongArgument());
        binder.bind(Long.class, new LongArgument());
        binder.bind(double.class, new DoubleArgument());
        binder.bind(Double.class, new DoubleArgument());
        binder.bind(float.class, new FloatArgument());
        binder.bind(Float.class, new FloatArgument());
        binder.bind(Enum.class, new EnumArgument());

        for (Binding<?> binding : builder.bindings) {
            if (binding instanceof Binding.Release) {
                providers.removeIf(provider -> binding.getType() == provider.getType() && provider.doAnnotationsMatch(binding.getAnnotations()));
            } else {
                providers.add(Provider.unsafe(binding.getType(), binding.getProvider(), binding.getAnnotations()));
            }
        }

        configuration.getTabCompleter().init(this);
    }

    @NotNull
    @Contract("_, _ -> this")
    public Blade register(@Nullable Object instance, @NotNull Class<?> clazz) {
        registrar.registerClass(instance, clazz);
        return this;
    }

    @NotNull
    @Contract("_ -> this")
    public Blade register(@NotNull Class<?> clazz) {
        registrar.registerClass(null, clazz);
        return this;
    }

    @NotNull
    @Contract("_ -> this")
    public Blade register(@NotNull Object instance) {
        registrar.registerClass(instance, instance.getClass());
        return this;
    }

    @NotNull
    @Contract("_, _ -> this")
    public Blade registerPackage(@NotNull Class<?> clazz, @NotNull String packageName) {
        ClassUtil.getClassesInPackage(clazz, packageName).forEach(this::register);
        return this;
    }

    public static final class Builder {
        private final BladePlatform platform;
        private final BladeConfiguration configuration;

        private final Map<String, PermissionPredicate> permissionPredicates = new HashMap<>();
        private final List<Binding<?>> bindings = new ArrayList<>();

        private Builder(BladePlatform platform) {
            this.platform = platform;
            this.configuration = new BladeConfiguration();
            platform.configureBlade(this, this.configuration);
        }

        @NotNull
        @Contract("_ -> this")
        public Builder config(@NotNull Consumer<BladeConfiguration> consumer) {
            consumer.accept(configuration);
            configuration.validate();
            return this;
        }

        @NotNull
        @Contract("_ -> this")
        public Builder bind(@NotNull Consumer<Binder> consumer) {
            Binder binder = new Binder(this, false);
            consumer.accept(binder);
            return this;
        }

        @NotNull
        @Contract("_ -> this")
        public Builder permission(@NotNull Consumer<PredicateAdder> consumer) {
            PredicateAdder adder = new PredicateAdder(this);
            consumer.accept(adder);
            return this;
        }

        @NotNull
        @Contract(" -> new")
        public Blade build() {
            return new Blade(this);
        }

        @RequiredArgsConstructor
        public static final class Binder {
            private final Builder builder;
            private final boolean insertToBeginning;

            @SafeVarargs
            public final <T> void bind(@NotNull Class<T> type, @NotNull ArgumentProvider<T> provider, @NotNull Class<? extends Annotation>... annotations) {
                bind(type, provider, Arrays.asList(annotations));
            }

            public <T> void bind(@NotNull Class<T> type, @NotNull ArgumentProvider<T> provider, @NotNull List<Class<? extends Annotation>> annotations) {
                Binding<T> binding = new Binding<>(type, provider, annotations);
                if (insertToBeginning) {
                    builder.bindings.add(0, binding);
                } else {
                    builder.bindings.add(binding);
                }
            }

            @SafeVarargs
            public final <T> void unsafeBind(@NotNull Class<T> type, @NotNull ArgumentProvider<?> provider, @NotNull Class<? extends Annotation>... annotations) {
                unsafeBind(type, provider, Arrays.asList(annotations));
            }

            public <T> void unsafeBind(@NotNull Class<T> type, @NotNull ArgumentProvider<?> provider, @NotNull List<Class<? extends Annotation>> annotations) {
                Binding<?> binding = Binding.unsafe(type, provider, annotations);
                if (insertToBeginning) {
                    builder.bindings.add(0, binding);
                } else {
                    builder.bindings.add(binding);
                }
            }

            @SafeVarargs
            public final <T> void release(@NotNull Class<T> type, @NotNull Class<? extends Annotation>... annotations) {
                release(type, Arrays.asList(annotations));
            }

            public <T> void release(@NotNull Class<T> type, @NotNull List<Class<? extends Annotation>> annotations) {
                Binding<?> binding = Binding.release(type, annotations);
                if (insertToBeginning) {
                    builder.bindings.add(0, binding);
                } else {
                    builder.bindings.add(binding);
                }
            }
        }

        @RequiredArgsConstructor
        public static final class PredicateAdder {
            private final Builder builder;

            public void predicate(@NotNull String permission, @NotNull PermissionPredicate predicate) {
                builder.permissionPredicates.put(permission, predicate);
            }
        }
    }

}


text:
package me.vaperion.blade.annotation.command;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to set the description of the command.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Description {
    String value();
}


text:
package me.vaperion.blade.annotation.command;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to set a custom usage message for a command.
 * If this annotation is not present, a usage message will be generated automatically.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Usage {
    String value();
}


text:
package me.vaperion.blade.annotation.command;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to set the permission of the command.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Permission {
    /**
     * The required permission.
     * <p> If the permission is not set, the command will be executed without any permission check.
     * <p> If you prefix the permission with {@code "@"}, it will be treated as a permission predicate,
     * and checked accordingly against the registered permission predicates. If the predicate is not registered,
     * the command will be executed without any permission check.
     */
    String value() default "";

    /**
     * The message that gets displayed if the player does not have permission to execute the command.
     * <p> If the message is not set, the default message will be displayed, set in the {@link me.vaperion.blade.Blade} builder.
     */
    String message() default "";
}


text:
package me.vaperion.blade.annotation.command;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to indicate that a command should be hidden.
 * Hidden commands do not show up in the generated help message, and cannot be tab completed.
 * Players will see the default unknown command message instead of the no permission message when executed without permission.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Hidden {}


text:
package me.vaperion.blade.annotation.command;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to set the alias that the usage message generation should use.
 * If this is not set, the first name specified in the {@link Command} annotation will be used.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface UsageAlias {
    String value();
}


text:
package me.vaperion.blade.annotation.command;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to indicate that quotes in the arguments of the command should be parsed.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ParseQuotes {}


text:
package me.vaperion.blade.annotation.command;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to add extra data after the generated usage of the command.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExtraUsage {
    String value();
}


text:
package me.vaperion.blade.annotation.command;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to mark a method as a command.
 * You must provide at least one name for the command.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Command {
    String[] value();
}


text:
package me.vaperion.blade.annotation.command;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to indicate that a command should be executed asynchronously.
 * This is useful for interacting with a database in a command, so you don't have to do the threading manually.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Async {}


text:
package me.vaperion.blade.annotation.argument;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to set the allowed range of a number argument;
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Range {
    double min() default Double.NaN;

    double max() default Double.NaN;
}


text:
package me.vaperion.blade.annotation.argument;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to set the name of a parameter.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Name {
    String value();
}


text:
package me.vaperion.blade.annotation.argument;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation can be used to pass specific data flags to the argument provider.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Data {
    String[] value();
}


text:
package me.vaperion.blade.annotation.argument;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation indicates which parameter should be used as the sender of the command.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Sender {}


text:
package me.vaperion.blade.annotation.argument;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to create a flag.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Flag {
    char value();

    String description() default "";
}


text:
package me.vaperion.blade.annotation.argument;

import me.vaperion.blade.argument.ArgumentProvider;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation is used to override the tab completer for the specific argument.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Completer {
    Class<? extends ArgumentProvider<?>> value();
}


text:
package me.vaperion.blade.annotation.argument;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * This annotation indicates that the annotated {@linkplain String} parameter should be
 * parsed as a text, meaning that it will contain the rest of the arguments combined into a
 * single string.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Text {}