mirror of
https://github.com/snobu/destreamer.git
synced 2026-01-17 05:22:18 +00:00
Major code refactoring (#164)
* Added Chromium caching of identity provider cookies * Moved token expiry check in standalone method * Created refreshSession function * Session is now refreshed if the token expires * Linting fixes * Removed debug console.log() * Added CC support * Created function to prompt user for download parameters (interactive mode) * Fix data folder for puppeteer * Fixed multiple session error * Fix token expire time * Moved session refreshing to a more sensible place * Changed Metadata name to Video (to better reflect the data structure) * Complete CLI refactoring * Removed useless sleep function * Added outDir check from CLI * Complete input parsing refactoring (both inline and file) * Fixed and improved tests to work with the new input parsing * Moved and improved output path generation to videoUtils * Main code refactoring, added outpath to video type * Minor changes in spacing and type definition style * Updated readme after code refactoring * Fix if inputFile doesn't start with url on line 1 * Minor naming change * Use module 'winston' for logging * Created logge, changed all console.log and similar to use the logger * Added verbose logging, changed posterUrl property name on Video type * Moved GUID extraction to input parsing * Added support for group links * Fixed test after last input parsing update * Removed debug proces.exit() * Changed from desc to asc order for group videos * Updated test to reflect GUIDs output after parsing * Added couple of comments and restyled some imports * More readable verbose GUIDs logging * Removed unused errors * Temporary fix for timeout not working in ApiClient * Explicit class member accessibility * Defined array naming schema to be Array<T> * Defined type/interface schema to be type only * A LOT of type definitions
This commit is contained in:
@@ -1,37 +1,43 @@
|
||||
import { CLI_ERROR } from './Errors';
|
||||
import { CLI_ERROR, ERROR_CODE } from './Errors';
|
||||
import { checkOutDir } from './Utils';
|
||||
import { logger } from './Logger';
|
||||
|
||||
import yargs from 'yargs';
|
||||
import colors from 'colors';
|
||||
import fs from 'fs';
|
||||
import readlineSync from 'readline-sync';
|
||||
import yargs from 'yargs';
|
||||
|
||||
export const argv = yargs.options({
|
||||
|
||||
export const argv: any = yargs.options({
|
||||
username: {
|
||||
alias: 'u',
|
||||
type: 'string',
|
||||
describe: 'The username used to log into Microsoft Stream (enabling this will fill in the email field for you)',
|
||||
demandOption: false
|
||||
},
|
||||
videoUrls: {
|
||||
alias: 'i',
|
||||
describe: 'List of video urls',
|
||||
type: 'array',
|
||||
demandOption: false
|
||||
},
|
||||
videoUrlsFile: {
|
||||
inputFile: {
|
||||
alias: 'f',
|
||||
describe: 'Path to txt file containing the urls',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
},
|
||||
username: {
|
||||
alias: 'u',
|
||||
describe: 'Path to text file containing URLs and optionally outDirs. See the README for more on outDirs.',
|
||||
type: 'string',
|
||||
demandOption: false
|
||||
},
|
||||
outputDirectory: {
|
||||
alias: 'o',
|
||||
describe: 'The directory where destreamer will save your downloads [default: videos]',
|
||||
describe: 'The directory where destreamer will save your downloads',
|
||||
type: 'string',
|
||||
default: 'videos',
|
||||
demandOption: false
|
||||
},
|
||||
outputDirectories: {
|
||||
alias: 'O',
|
||||
describe: 'Path to a txt file containing one output directory per video',
|
||||
type: 'string',
|
||||
keepLoginCookies: {
|
||||
alias: 'k',
|
||||
describe: 'Let Chromium cache identity provider cookies so you can use "Remember me" during login',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
demandOption: false
|
||||
},
|
||||
noExperiments: {
|
||||
@@ -55,6 +61,13 @@ export const argv = yargs.options({
|
||||
default: false,
|
||||
demandOption: false
|
||||
},
|
||||
closedCaptions: {
|
||||
alias: 'cc',
|
||||
describe: 'Check if closed captions are aviable and let the user choose which one to download (will not ask if only one aviable)',
|
||||
type: 'boolean',
|
||||
default: false,
|
||||
demandOption: false
|
||||
},
|
||||
noCleanup: {
|
||||
alias: 'nc',
|
||||
describe: 'Do not delete the downloaded video file when an FFmpeg error occurs',
|
||||
@@ -87,147 +100,74 @@ export const argv = yargs.options({
|
||||
demandOption: false
|
||||
}
|
||||
})
|
||||
/**
|
||||
* Do our own argv magic before destreamer starts.
|
||||
* ORDER IS IMPORTANT!
|
||||
* Do not mess with this.
|
||||
*/
|
||||
.check(() => isShowHelpRequest())
|
||||
.check(argv => checkRequiredArgument(argv))
|
||||
.check(argv => checkVideoUrlsArgConflict(argv))
|
||||
.check(argv => checkOutputDirArgConflict(argv))
|
||||
.check(argv => checkVideoUrlsInput(argv))
|
||||
.check(argv => windowsFileExtensionBadBehaviorFix(argv))
|
||||
.check(argv => mergeVideoUrlsArguments(argv))
|
||||
.check(argv => mergeOutputDirArguments(argv))
|
||||
.wrap(120)
|
||||
.check(() => noArguments())
|
||||
.check((argv: any) => inputConflicts(argv.videoUrls, argv.inputFile))
|
||||
.check((argv: any) => {
|
||||
if (checkOutDir(argv.outputDirectory)) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
logger.error(CLI_ERROR.INVALID_OUTDIR);
|
||||
|
||||
throw new Error(' ');
|
||||
}
|
||||
})
|
||||
.argv;
|
||||
|
||||
function hasNoArgs() {
|
||||
return process.argv.length === 2;
|
||||
}
|
||||
|
||||
function isShowHelpRequest() {
|
||||
if (hasNoArgs()) {
|
||||
throw new Error(CLI_ERROR.GRACEFULLY_STOP);
|
||||
function noArguments(): boolean {
|
||||
// if only 2 args no other args (0: node path, 1: js script path)
|
||||
if (process.argv.length === 2) {
|
||||
logger.error(CLI_ERROR.MISSING_INPUT_ARG, {fatal: true});
|
||||
|
||||
// so that the output stays clear
|
||||
throw new Error(' ');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkRequiredArgument(argv: any) {
|
||||
if (hasNoArgs()) {
|
||||
return true;
|
||||
|
||||
function inputConflicts(videoUrls: Array<string | number> | undefined,
|
||||
inputFile: string | undefined): boolean {
|
||||
// check if both inputs are declared
|
||||
if ((videoUrls !== undefined) && (inputFile !== undefined)) {
|
||||
logger.error(CLI_ERROR.INPUT_ARG_CONFLICT);
|
||||
|
||||
throw new Error(' ');
|
||||
}
|
||||
// check if no input is declared or if they are declared but empty
|
||||
else if (!(videoUrls || inputFile) || (videoUrls?.length === 0) || (inputFile?.length === 0)) {
|
||||
logger.error(CLI_ERROR.MISSING_INPUT_ARG);
|
||||
|
||||
if (!argv.videoUrls && !argv.videoUrlsFile) {
|
||||
throw new Error(colors.red(CLI_ERROR.MISSING_REQUIRED_ARG));
|
||||
throw new Error(' ');
|
||||
}
|
||||
else if (inputFile) {
|
||||
// check if inputFile doesn't end in '.txt'
|
||||
if (inputFile.substring(inputFile.length - 4) !== '.txt') {
|
||||
logger.error(CLI_ERROR.INPUTFILE_WRONG_EXTENSION);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkVideoUrlsArgConflict(argv: any) {
|
||||
if (hasNoArgs()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (argv.videoUrls && argv.videoUrlsFile) {
|
||||
throw new Error(colors.red(CLI_ERROR.VIDEOURLS_ARG_CONFLICT));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkOutputDirArgConflict(argv: any) {
|
||||
if (hasNoArgs()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (argv.outputDirectory && argv.outputDirectories) {
|
||||
throw new Error(colors.red(CLI_ERROR.OUTPUTDIR_ARG_CONFLICT));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function checkVideoUrlsInput(argv: any) {
|
||||
if (hasNoArgs() || !argv.videoUrls) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!argv.videoUrls.length) {
|
||||
throw new Error(colors.red(CLI_ERROR.MISSING_REQUIRED_ARG));
|
||||
}
|
||||
|
||||
const t = argv.videoUrls[0] as string;
|
||||
if (t.substring(t.length-4) === '.txt') {
|
||||
throw new Error(colors.red(CLI_ERROR.FILE_INPUT_VIDEOURLS_ARG));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Users see 2 separate options, but we don't really care
|
||||
* cause both options have no difference in code.
|
||||
*
|
||||
* Optimize and make this transparent to destreamer
|
||||
*/
|
||||
function mergeVideoUrlsArguments(argv: any) {
|
||||
if (!argv.videoUrlsFile) {
|
||||
return true;
|
||||
}
|
||||
|
||||
argv.videoUrls = [argv.videoUrlsFile]; // noone will notice ;)
|
||||
|
||||
// these are not valid anymore
|
||||
delete argv.videoUrlsFile;
|
||||
delete argv.F;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Users see 2 separate options, but we don't really care
|
||||
* cause both options have no difference in code.
|
||||
*
|
||||
* Optimize and make this transparent to destreamer
|
||||
*/
|
||||
function mergeOutputDirArguments(argv: any) {
|
||||
if (!argv.outputDirectories && argv.outputDirectory) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!argv.outputDirectory && !argv.outputDirectories) {
|
||||
argv.outputDirectory = 'videos'; // default out dir
|
||||
}
|
||||
else if (argv.outputDirectories) {
|
||||
argv.outputDirectory = argv.outputDirectories;
|
||||
}
|
||||
|
||||
if (argv.outputDirectories) {
|
||||
// these are not valid anymore
|
||||
delete argv.outputDirectories;
|
||||
delete argv.O;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// yeah this is for windows, but lets check everyone, who knows...
|
||||
function windowsFileExtensionBadBehaviorFix(argv: any) {
|
||||
if (hasNoArgs() || !argv.videoUrlsFile || !argv.outputDirectories) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!fs.existsSync(argv.videoUrlsFile)) {
|
||||
if (fs.existsSync(argv.videoUrlsFile + '.txt')) {
|
||||
argv.videoUrlsFile += '.txt';
|
||||
throw new Error(' ');
|
||||
}
|
||||
else {
|
||||
throw new Error(colors.red(CLI_ERROR.INPUT_URLS_FILE_NOT_FOUND));
|
||||
// check if the inputFile exists
|
||||
else if (!fs.existsSync(inputFile)) {
|
||||
logger.error(CLI_ERROR.INPUTFILE_NOT_FOUND);
|
||||
|
||||
throw new Error(' ');
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
export function promptUser(choices: Array<string>): number {
|
||||
let index: number = readlineSync.keyInSelect(choices, 'Which resolution/format do you prefer?');
|
||||
|
||||
if (index === -1) {
|
||||
process.exit(ERROR_CODE.CANCELLED_USER_INPUT);
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user