src/cli.js
// Import the neccesary modules.
import bytes from "bytes";
import parseTorrent from "parse-torrent";
import path from "path";
import program from "commander";
import prompt from "prompt";
import torrentHealth from "torrent-tracker-health";
import Index from "./index";
import AnimeHelper from "./providers/helpers/animehelper";
import MovieHelper from "./providers/helpers/moviehelper";
import ShowHelper from "./providers/helpers/showhelper";
import Logger from "./config/logger";
import packageJSON from "../package.json";
import Setup from "./config/setup";
import Util from "./util";
/** Class The class for the command line interface. */
export default class CLI {
/**
* Create a cli object.
* @param {String} [providerName=CLI] - The default provider name.
*/
constructor(providerName = "CLI") {
/**
* The name of the CLI provider.
* @type {String}
*/
CLI._providerName = providerName;
/**
* The logger object to configure the logging.
* @type {Logger}
*/
CLI._logger = new Logger();
/**
* The util object with general functions.
* @type {Util}
*/
this._util = new Util();
// Setup the CLI program.
program
.version(`${packageJSON.name} v${packageJSON.version}`)
.option("-c, --content <type>", "Add content from the MongoDB database (anime | show | movie).", /^(anime)|^(show)|^(movie)/i, false)
.option("-r, --run", "Run the API and start the scraping process.")
.option("-s, --server", "Run the API without starting the scraping process.")
.option("-e, --export <collection>", "Export a collection to a JSON file.", /^(anime)|^(show)|^(movie)/i, false)
.option("-i, --import <collection>", "Import a JSON file to the database.");
// Extra output on top of the default help output
program.on("--help", () => {
console.info(" Examples:");
console.info("");
console.info(" $ popcorn-api -c <anime|movie|show>");
console.info(" $ popcorn-api --content <anime|movie|show>");
console.info("");
console.info(" $ popcorn-api -r");
console.info(" $ popcorn-api --run");
console.info("");
console.info(" $ popcorn-api -s");
console.info(" $ popcorn-api --server");
console.info("");
console.info(" $ popcorn-api -e <anime|movie|show>");
console.info(" $ popcorn-api --export <anime|movie|show>");
console.info("");
console.info(" $ popcorn-api -i <path-to-json>");
console.info(" $ popcorn-api --import <path-to-json>");
console.info("");
});
// Parse the command line arguments.
program.parse(process.argv);
// The imdb property.
const imdb = {
description: "The imdb id of the show/movie to add (tt1234567)",
type: "string",
pattern: /^(tt\d{7}|)|^(.*)/i,
message: "Not a valid imdb id.",
required: true
};
// The Hummingbird id property.
const hummingbirdId = {
description: "The Hummingbird id of the anime to add",
type: "string",
pattern: /^(.*)/i,
message: "Not a validHhummingbird id.",
required: true
};
// The torrent property.
const torrent = {
description: "The link of the torrent to add",
type: "string",
message: "Not a valid torrent.",
required: true
};
// The language property.
const language = {
description: "The language of the torrent to add (en, fr, jp)",
type: "string",
pattern: /^([a-zA-Z]{2})/i,
message: "Not a valid language",
required: true
};
// The quality property.
const quality = {
description: "The quality of the torrent (480p | 720p | 1080p)",
type: "string",
pattern: /^(480p|720p|1080p)/i,
message: "Not a valid quality.",
required: true
};
// The season property.
const season = {
description: "The season number of the torrent",
type: "integer",
pattern: /^(\d+)/i,
message: "Not a valid season.",
required: true
};
// The episode property.
const episode = {
description: "The episode number of the torrent",
type: "integer",
pattern: /^(\d+)/i,
message: "Not a valid episode.",
required: true
};
const confirm = {
description: "Do you really want to import a collection, this can override the current data?",
type: "string",
pattern: /^(yes|no|y|n)$/i,
message: "Type yes/no",
required: true,
default: "no"
};
/**
* The shema used by `prompt` insert an anime show.
* @type {Object}
*/
this._animeSchema = {
properties: {
"hummingbirdId": hummingbirdId,
"season": season,
"episode": episode,
"torrent": torrent,
"quality": quality
}
};
/**
* The schema used by `prompt` insert a movie.
* @type {Object}
*/
this._movieSchema = {
properties: {
"imdb": imdb,
"language": language,
"torrent": torrent,
"quality": quality
}
};
/**
* The schema used by `prompt` insert a show.
* @type {Object}
*/
this._showSchema = {
properties: {
"imdb": imdb,
"season": season,
"episode": episode,
"torrent": torrent,
"quality": quality
}
};
/**
* The schema used by `prompt` to confirm an import.
* @type {Object}
*/
this._importSchema = {
properties: {
"confirm": confirm
}
};
}
/** Adds a show to the database through the CLI. */
_animePrompt() {
prompt.get(this._animeSchema, async(err, result) => {
if (err) {
console.error(`An error occurred: ${err}`);
process.exit(1);
} else {
try {
const { hummingbirdId, season, episode, quality, torrent } = result;
const animeHelper = new AnimeHelper(CLI._providerName);
const newAnime = await animeHelper.getHummingbirdInfo(hummingbirdId);
if (newAnime && newAnime._id) {
const data = await this._getShowTorrentDataRemote(torrent, quality, season, episode);
await animeHelper.addEpisodes(newAnime, data, hummingbirdId);
process.exit(0);
}
} catch (err) {
console.error(`An error occurred: ${err}`);
process.exit(1);
}
}
});
}
/**
* Get movie data from a given torrent url.
* @param {String} torrent - The url of the torrent.
* @param {String} language - The language of the torrent.
* @param {String} quality - The quality of the torrent.
* @returns {Promise} - Movie data from the torrent.
*/
_getMovieTorrentDataRemote(torrent, language, quality) {
return new Promise((resolve, reject) => {
parseTorrent.remote(torrent, (err, result) => {
if (err) return reject(err);
const magnet = parseTorrent.toMagnetURI(result);
torrentHealth(magnet).then(res => {
const { seeds, peers } = res;
const data = {};
if (!data[language]) data[language] = {};
if (!data[language][quality]) data[language][quality] = {
url: magnet,
seed: seeds,
peer: peers,
size: result.length,
filesize: bytes(result.length),
provider: CLI._providerName
};
return resolve(data);
}).catch(err => reject(err));
});
});
}
/** Adds a movie to the database through the CLI. */
_moviePrompt() {
prompt.get(this._movieSchema, async(err, result) => {
if (err) {
console.error(`An error occurred: ${err}`);
process.exit(1);
} else {
try {
const { imdb, quality, language, torrent } = result;
const movieHelper = new MovieHelper(CLI._providerName);
const newMovie = await movieHelper.getTraktInfo(imdb);
if (newMovie && newMovie._id) {
const data = await this._getMovieTorrentDataRemote(torrent, language, quality);
await movieHelper.addTorrents(newMovie, data);
process.exit(0);
}
} catch (err) {
console.error(`An error occurred: ${err}`);
process.exit(1);
}
}
});
}
/**
* Get show data from a given torrent url.
* @param {String} torrent - The url of the torrent.
* @param {String} quality - The quality of the torrent.
* @param {Integer} season - The season of the show from the torrent file.
* @param {Integer} episode - The episode of the show from the torrent.
* @returns {Promise} - Show data from the torrent.
*/
_getShowTorrentDataRemote(torrent, quality, season, episode) {
return new Promise((resolve, reject) => {
parseTorrent.remote(torrent, (err, result) => {
if (err) return reject(err);
const magnet = parseTorrent.toMagnetURI(result);
torrentHealth(magnet).then(res => {
const { seeds, peers } = res;
const data = {};
if (!data[season]) data[season] = {};
if (!data[season][episode]) data[season][episode] = {};
if (!data[season][episode][quality]) data[season][episode][quality] = {
url: magnet,
seeds,
peers,
provider: CLI._providerName
};
return resolve(data);
}).catch(err => reject(err));
});
});
}
/** Adds a show to the database through the CLI. */
_showPrompt() {
prompt.get(this._showSchema, async(err, result) => {
if (err) {
console.error(`An error occurred: ${err}`);
process.exit(1);
} else {
try {
const { imdb, season, episode, quality, torrent } = result;
const showHelper = new ShowHelper(CLI._providerName);
const newShow = await showHelper.getTraktInfo(imdb);
if (newShow && newShow._id) {
const data = await this._getShowTorrentDataRemote(torrent, quality, season, episode);
await showHelper.addEpisodes(newShow, data, imdb);
process.exit(0);
}
} catch (err) {
console.error(`An error occurred: ${err}`);
process.exit(1);
}
}
});
}
/** Confimation to import a collection */
_importPrompt() {
prompt.get(this._importSchema, (err, result) => {
if (err) {
console.error(`An error occured: ${err}`);
process.exit(1);
} else {
if (result.confirm.match(/^(y|yes)/i)) {
let collection = path.basename(program.import);
const index = collection.lastIndexOf(".");
collection = collection.substring(0, index);
this._util.importCollection(collection, program.import)
.catch(err => console.error(err));
} else if (result.confirm.match(/^(n|no)/i)) {
process.exit(0);
}
}
});
}
/** Run the CLI program. */
run() {
if (program.run) {
new Index({
start: true,
pretty: true,
verbose: false,
debug: false
});
} else if (program.server) {
new Index({
start: false,
pretty: true,
verbose: false,
debug: false
});
} else if (program.content) {
prompt.start();
Setup.connectMongoDB();
if (program.content.match(/^(show)/i)) {
this._showPrompt();
} else if (program.content.match(/^(movie)/i)) {
this._moviePrompt();
} else if (program.content.match(/^(anime)/i)) {
this._animePrompt();
} else {
console.error(`\n \x1b[31mError:\x1b[36m No valid value given for adding content: '${program.content}'\x1b[0m`)
}
} else if (program.export) {
this._util.exportCollection(program.export);
} else if (program.import) {
this._importPrompt();
} else {
console.error("\n \x1b[31mError:\x1b[36m No valid command given. Please check below:\x1b[0m");
program.help();
}
}
}