diff --git a/README.md b/README.md index 421baef..d3cde5a 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ A simple and useful Command Line tool for Codeforces coders ## Releases #### Work in progress - + ## Inspirations - [Codeforces API](http://codeforces.com/api/help) diff --git a/package.json b/package.json index b1b9d6a..95c62ff 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,12 @@ "single": "./node_modules/.bin/nyc ./node_modules/.bin/mocha --compilers js:babel-register \"tests/crawler/test_ratings.js\" & npm run cov-html" }, "nyc": { - "include": ["src/lib/crawler/*.js", "src/lib/api/*.js", "src/lib/utils/*.js", "tests/**/*.js"] + "include": [ + "src/lib/crawler/*.js", + "src/lib/api/*.js", + "src/lib/utils/*.js", + "tests/**/*.js" + ] }, "repository": { "type": "git", diff --git a/src/bin/cli/cf.js b/src/bin/cli/cf.js index fb8635e..1e060e5 100644 --- a/src/bin/cli/cf.js +++ b/src/bin/cli/cf.js @@ -17,9 +17,31 @@ program .version(version) .usage('[options] [command]'); +program.on('--help', () => { + log(' All options:'); + log(' -r, --remember save/update handle'); + log(' -c, --count total items to fetch and show'); + log(' -w, --watch watch submission status live'); + log(' -p, --problem also download problem statement'); + log(' -u, --user codeforces user handle'); + log(' -l, --language programming language id of the solution'); + log(' -d, --directory directory to save solutions'); + log(' --logout delete saved password'); + log(' --gym gym problem submit'); + log(' --no-chart disable showing chart of user rating'); + log(' --org show Organization of users'); + log(' --unofficial unofficial standings'); + log(' --handles comma separated codeforces handles'); + log(' --offset offset of the items to fetch from'); + log(' --limit maximum number of simultaneous downloads'); + log(' --country country name for rating'); + log(' --delay refreshing delay of live submission status [in millisecond]'); + log(' --contest specific contest submissions'); +}); + program .command('runs') - .option('-r, --remember', 'save handle for future') + .option('-r, --remember', 'save/update handle') .option('-c, --count ', 'total submission status to display') .option('-w, --watch', 'watch submission status live') .option('--delay ', 'refreshing delay of live submission status [in millisecond]') @@ -104,10 +126,10 @@ program program .command('rating') - .option('-u, --user ', 'user handle for rating') + .option('-u, --user ', 'codeforces user handle') .option('--country ', 'country name for rating') - .option('--no-chart', 'disable showing chart for user') - .option('--org', 'show Organization for country rating') + .option('--no-chart', 'disable showing chart of user rating') + .option('--org', 'show Organization of users') .description('user ratings') .action( (prg) => { @@ -129,7 +151,7 @@ program program .command('tags') - .description('all tags and problem quantity') + .description('all tags distribution') .action( () => { CF.tags(); }); @@ -187,7 +209,7 @@ program .command('solutions ') .option('-d, --directory ','directory to save solutions') .option('-p, --problem','also download problem statement') - .option('--limit ','limit async task') + .option('--limit ','maximum number of simultaneous downloads') .description('user solution download') .action( (handle,prg) => { @@ -209,7 +231,7 @@ program program .command('standings ') .description('contest standings') - .option('--handles ', 'handles for standings') + .option('--handles ', 'comma separated codeforces handles') .option('--country ', 'country name for standings') .option('-c, --count ', 'total standings to display') .option('--offset ', 'standings offset') @@ -266,7 +288,7 @@ if( !program.args.length && has(program,'help') ){ // https://github.com/tj/programer.js/issues/57 // if (!program.args.length) { - figlet('Codeforces CLI',{ font: 'ANSI Shadow' }, (err, data) => { + figlet('Codeforces CLI', (err, data) => { log(''); log(''); if(!err){ @@ -279,19 +301,18 @@ if (!program.args.length) { //warn aboud invalid programs var validprograms = program.commands.map(function(cmd){ - return cmd.name; + return cmd._name; }); - var invalidprograms = program.args.filter(function(cmd){ - //if program executed it will be an object and not a string - return (typeof cmd === 'string' && validprograms.indexOf(cmd) === -1 ); + var invalidprograms = program.args.map(function(cmd){ + return cmd._name; }); - - if (invalidprograms.length) { - // log('\n [ERROR] - Invalid program: "%s". See "--help" for a list of available programs.\n', invalidprograms.join(', ')); + log(''); + log(` [ERROR] - Invalid command: ${invalidprograms.join(', ')}. run "cf --help" for a list of available commands.`); + log(''); } } */ diff --git a/src/lib/api/Userrating.js b/src/lib/api/Userrating.js index d944dee..b1c3607 100644 --- a/src/lib/api/Userrating.js +++ b/src/lib/api/Userrating.js @@ -5,6 +5,7 @@ import ora from 'ora'; import Table from 'cli-table2'; import chalk from 'chalk'; import qs from 'qs'; +import has from 'has'; import forEach from 'lodash/forEach'; import * as contrib from 'blessed-contrib'; import blessed from 'blessed'; @@ -50,33 +51,28 @@ export default class Userrating { if (err) { spinner.fail(); - logr('Failed [Request]'); - return; + return logr('Failed [Request]'); } - if (response.statusCode !== 200) { + let {statusCode} = response; + if (statusCode !== 200) { spinner.fail(); - logr('Failed HTTP'); - return; + return logr( has(body,'comment') ? body.comment : `HTTP Failed with status ${statusCode}`); } let contentType = response.headers['content-type']; if (contentType.indexOf('application/json') === -1) { spinner.fail(); - logr('Failed.Not valid data.'); - return; + return logr('Failed.Not valid data.'); } if (body.status !== 'OK') { spinner.fail(); - logr(body.comment); - return; + return logr(body.comment); } - spinner.succeed(); if ( self.noChart ) { - let table = new Table({ head: [CB('Contest'), CB('Rank'), CB('Rating change'), CB('New rating')] }); @@ -90,8 +86,7 @@ export default class Userrating { ]); }); - log(table.toString()); - return; + return log(table.toString()); } let axisX = []; diff --git a/src/lib/api/standings.js b/src/lib/api/standings.js index dac3580..0edbeae 100644 --- a/src/lib/api/standings.js +++ b/src/lib/api/standings.js @@ -54,120 +54,104 @@ export default ({ contestId = null, count = 200, unofficial = false, from = 1, h spinner.start(); /* istanbul ignore next */ - request - .get(reqOptions) - .on('error', (err) => { - - debugs('Failed: Request error'); - debugs(err); - - logr(err); - }) - .on('complete', () => { - - debugs('parsing completed'); - - if( responseCode !== 200 ){ - log('a ' + responseCode); - spinner.fail(); - if( apiMsg !== null ){ - return logr(apiMsg); - } - return logr('Failed HTTP'); - } - - if( contentType.indexOf('application/json;') === -1 ){ - spinner.fail(); - return logr('Failed.Not valid data.'); - } - - if( apiFailed ){ - spinner.fail(); - return logr(apiMsg); - } - - spinner.succeed(); - - let head = [ GB('Rank'), GB('Who'), GB('Points'), GB('Hacks') ]; - forEach(problemSet, prb => { - head.push( GB(prb.index) ); - }); - table.options.head = head; - - log(''); - log( CB(` Title: ${contestInfo.name}`) ); - log( CB(` Type : ${contestInfo.type}`) ); - log( CB(` Phase: ${contestInfo.phase}`) ); - log(table.toString()); - - }) - .on('response', (response) => { - - responseCode = response.statusCode; - contentType = response.headers['content-type']; - - log(responseCode); - - debugs(`HTTP Code: ${responseCode}`); - debugs(`Content-Type: ${contentType}`); - }) - .pipe( JSONStream.parse('result.rows.*') ) - .on('header', (data) => { - - debugs(`API Status: ${data.status}`); - - if( data.status !== 'OK' ){ - apiFailed = true; - apiMsg = data.comment; - return; - } - contestInfo = data.contest; - problemSet = data.problems; - }) - .on('data', (data) => { - - let hacks = ''; - - if( data.successfulHackCount > 0 ){ - hacks = `+${data.successfulHackCount.toString()}`; + let reqStream = request.get(reqOptions); + let jsonStream = reqStream.pipe( JSONStream.parse('result.rows.*') ); + + reqStream.on('error', (err) => { + debugs('Failed: Request error'); + logr(err); + }); + + reqStream.on('complete', () => { + debugs('parsing completed'); + + if( responseCode !== 200 ){ + spinner.fail(); + return logr(apiMsg || `HTTP Failed with status ${responseCode}`); + } + + if( contentType.indexOf('application/json;') === -1 ){ + spinner.fail(); + return logr('Failed.Invalid data.'); + } + + if( apiFailed ){ + spinner.fail(); + return logr(apiMsg || 'Unknown error.[Report?]'); + } + spinner.succeed(); + + let head = [ GB('Rank'), GB('Who'), GB('Points'), GB('Hacks') ]; + forEach(problemSet, prb => { + head.push( GB(prb.index) ); + }); + table.options.head = head; + + log(''); + log( CB(` Title: ${contestInfo.name}`) ); + log( CB(` Type : ${contestInfo.type}`) ); + log( CB(` Phase: ${contestInfo.phase}`) ); + log(table.toString()); + }); + + reqStream.on('response', (response) => { + responseCode = response.statusCode; + contentType = response.headers['content-type']; + + debugs(`HTTP Code: ${responseCode}`); + debugs(`Content-Type: ${contentType}`); + }); + + jsonStream.on('header', (data) => { + debugs(`API Status: ${data.status}`); + + if( data.status !== 'OK' ){ + apiFailed = true; + apiMsg = data.comment; + return; + } + contestInfo = data.contest; + problemSet = data.problems; + }); + + jsonStream.on('data', (data) => { + let hacks = ''; + + if( data.successfulHackCount > 0 ){ + hacks = `+${data.successfulHackCount.toString()}`; + } + + if( data.unsuccessfulHackCount > 0 ){ + hacks = `${hacks} : -${data.unsuccessfulHackCount.toString()}`; // +x : -y + } + + /**********TO-DO*************/ + // handle Multiple members (team) + // + let chunk = [data.rank.toString(), CB(data.party.members[0].handle), data.points.toString(), hacks]; + + let results = map(data.problemResults, (result) => { + + if( result.points === 0 && result.rejectedAttemptCount > 0 ){ + return RB(`-${result.rejectedAttemptCount.toString()}`); } - - if( data.unsuccessfulHackCount > 0 ){ - hacks = `${hacks} : -${data.unsuccessfulHackCount.toString()}`; + else if( result.points === 0 && result.rejectedAttemptCount === 0 ){ + return ''; } - /**********TO-DO*************/ - // handle Multiple members (team) - // - let chunk = [ - data.rank.toString(), - CB(data.party.members[0].handle), - data.points.toString(), - hacks - ]; - + let subSecond = duration(result.bestSubmissionTimeSeconds, 'seconds'); + let h = parseInt(subSecond.hours(),10); + let s = parseInt(subSecond.minutes(),10); + let subTime = `${Math.floor(h/10)}${h%10}:${Math.floor(s/10)}${s%10}`; - let results = map(data.problemResults, (result) => { - - if( result.points === 0 && result.rejectedAttemptCount > 0 ){ - return RB(`-${result.rejectedAttemptCount.toString()}`); - } - else if( result.points === 0 && result.rejectedAttemptCount === 0 ){ - return ''; - } - - let subSecond = duration(result.bestSubmissionTimeSeconds, 'seconds'); - let h = parseInt(subSecond.hours(),10); - let s = parseInt(subSecond.minutes(),10); - let subTime = `${Math.floor(h/10)}${h%10}:${Math.floor(s/10)}${s%10}`; - - return ` ${result.points.toString()}\n${subTime}`; - }); - - table.push(chunk.concat(results)); + return ` ${result.points.toString()}\n${subTime}`; }); + + table.push(chunk.concat(results)); + }); }; + /** * Generate API url from given parameters * @param {Object} options @@ -190,6 +174,7 @@ function generateUrl(options) { handles = split(handles,','); param['handles'] = join(handles,';'); } + //else invalid handles..may be throw? currently ignore let sp = qs.stringify(param,{ encode: false }); diff --git a/src/lib/api/submission.js b/src/lib/api/submission.js index 8fba394..620630c 100644 --- a/src/lib/api/submission.js +++ b/src/lib/api/submission.js @@ -23,11 +23,9 @@ var GB = chalk.bold.green; var RB = chalk.bold.red; var TIME_OUT = 30000; //30 seconds -var STATUS_DELAY = 5000; //4 seconds +var STATUS_DELAY = 5000; //5 seconds -/** - * /** * @param {Number} count - total submisison to fetch * @param {Boolean} remember - if true, save handle in config file @@ -82,11 +80,10 @@ function readConfig(options, next) { jsonfile.readFile(options.config, (err, obj) => { let askHandle = false; - if( err ){ if( err.code === 'EPERM' ){ - throw new Error('Permission denied config file.'); + throw new Error(`Permission denied.Can't read config file '${options.config}'`); } if( err.code !== 'ENOENT' ){ @@ -96,7 +93,6 @@ function readConfig(options, next) { debugs('Config file not found'); askHandle = true; } - spinner.stop(); if( askHandle || !has(obj,'user') ){ @@ -107,17 +103,14 @@ function readConfig(options, next) { validate: validateEmpty }]; - inquirer.prompt(credentials).then( (answer) => { - + return inquirer.prompt(credentials).then( (answer) => { options.handle = answer.handle; jsonfile.writeFileSync(options.config, { user: options.handle });//save handle return next(null, options); }); - return; } - debugs('Handle found in config file'); spinner.text = `Saved handle found '${obj.user}'`; spinner.succeed(); @@ -151,7 +144,6 @@ function getSubmission(options, next) { spinner.text = 'Fetching submissions..'; spinner.start(); - request .get(reqOptions, (error, response, body) => { @@ -161,10 +153,7 @@ function getSubmission(options, next) { let { statusCode } = response; if( statusCode!==200 ){ - if( has(body,'comment') ){ - return next(body.comment); - } - return next('HTTP error'); + return next( has(body,'comment') ? body.comment : `HTTP failed with status ${statusCode}`); } if( body.status !== 'OK' ){ @@ -213,11 +202,8 @@ function watchRun(options, next) { } let { statusCode } = response; - if( statusCode!==200 ){ - if( has(body,'comment') ){ - return callback(body.comment); - } - return callback('HTTP error'); + if( statusCode !== 200 ){ + return next( has(body,'comment') ? body.comment : `HTTP failed with status ${statusCode}`); } if( body.status !== 'OK' ){ @@ -231,22 +217,15 @@ function watchRun(options, next) { // Still testing, Wait x seconds and get status again // if( keepWatching ) { - setTimeout(() => { + return setTimeout(() => { callback(); }, STATUS_DELAY); - return; } return callback(); }); }, - (err) => { - if(err){ - debugs('Error while watching'); - return next(err); - } - return next(); - } + next ); } @@ -273,9 +252,7 @@ function generateTable(runs, isWatch = false){ let passed = parseInt(passedTestCount,10); who = author.members[0].handle; - debugs(run.testset); - - if( verdict === undefined ){ + if( verdict === undefined || typeof verdict != 'string' ){ done = false; verdict = chalk.white.bold('In queue'); } diff --git a/src/lib/api/tags.js b/src/lib/api/tags.js index 7a1813e..e98cb50 100644 --- a/src/lib/api/tags.js +++ b/src/lib/api/tags.js @@ -4,6 +4,7 @@ import JSONStream from 'JSONStream'; import debug from 'debug'; import request from 'request'; import ora from 'ora'; +import has from 'has'; import _ from 'lodash'; import Table from 'cli-table2'; import chalk from 'chalk'; @@ -14,9 +15,7 @@ var spinner = ora({ spinner: 'line' }); /** * Get all tags and quantity - * - * @param callback - * if callback given, return tags, otherwise print + * @param callback - if callback given, return tags, otherwise print */ export default (callback) => { @@ -25,146 +24,106 @@ export default (callback) => { json: true }; - // - // Request validators - // - let apiFailed= false; - let apiMsg = ''; - let responseCode = '404'; + let apiMsg = null; + let responseCode = 404; let contentType = ''; - let allTags = {}; + let isCallback = typeof callback === 'function'; spinner.text = 'Fetching all tags...'; spinner.start(); - /* istanbul ignore next */ - request - .get(reqOptions) - .on('error', (err) => { - - debugs('Failed: Request error'); - debugs(err); + let reqStream = request.get(reqOptions); + let jsonStream = reqStream.pipe( JSONStream.parse('result.problems.*') ); + + reqStream.on('error', (err) => { + debugs('Failed: Request error'); + debugs(err); + + return isCallback ? callback(err) : logr('Request connection Failed.'); + }); + + + reqStream.on('complete', () => { + debugs('parsing completed'); + + if( responseCode !== 200 ){ + spinner.fail(); + apiMsg = apiMsg || `HTTP Failed with status: ${responseCode}`; + return isCallback ? callback(apiMsg) : logr(apiMsg); + } + + // Content not json, request failed + if( contentType.indexOf('application/json;') === -1 ){ + spinner.fail(); + apiMsg = 'Failed.Not valid data.'; + return isCallback ? callback(apiMsg) : logr(apiMsg); + } + + // API rejects the request + if( apiMsg ){ + spinner.fail(); + return isCallback ? callback(apiMsg) : logr(apiMsg); + } + spinner.succeed(); + + if(isCallback){ + return callback(null,allTags); + } + + // Sort tags by problem count, need to update if better way + allTags = _ + .chain(allTags) + .map((value, key) => { + return {key, value}; + }) + .orderBy('value') + .reverse() + .keyBy('key') + .mapValues('value') + .value(); - if( typeof callback === 'function'){ - return callback(err); - } + let table = new Table({ + head: [ chalk.green('TAG'), chalk.green('Total Problem')] + }); - logr('Request Failed.Bug?'); + _.forEach(allTags, (value, key) => { + table.push([key, value]); + }); - }) - .on('complete', () => { + log(''); + log(chalk.bold.green(` Total tag: ${table.length}`)); + log(table.toString()); + }); - /** - * Very messy code, need to update callback logic for clean code (??) - */ - debugs('parsing completed'); + reqStream.on('response', (response) => { + debugs(`HTTP Code: ${responseCode}`); + debugs(`Content-Type: ${contentType}`); - let isCallback = typeof callback === 'function'; + responseCode = response.statusCode; + contentType = response.headers['content-type']; + }); - if( responseCode !== 200 ){ - spinner.fail(); - if(isCallback){ - return callback(`Failed, HTTP status: ${responseCode}`); - } - logr(`Failed, HTTP status: ${responseCode}`); - return; - } - - // - // Content not json, request failed - // - if( contentType.indexOf('application/json;') === -1 ){ - spinner.fail(); - if(isCallback){ - return callback('Failed.Not valid data.'); - } - logr('Failed.Not valid data.'); - return; - } - - // - // API rejects the request - // - if( apiFailed ){ - spinner.fail(); - if(isCallback){ - return callback(apiMsg); - } - logr(apiMsg); - return; - } - spinner.succeed(); + jsonStream.on('header', (data) => { + debugs(`API Status: ${data.status}`); - // - // If callback given, return tags - // - if(isCallback){ - return callback(null,allTags); - } + if( data.status !== 'OK' ){ + apiMsg = data.comment || 'Unknown Error?'; + } + }); - // - // Sort tags by problem count, need to update if better way - // - allTags = _ - .chain(allTags) - .map((value, key) => { - return {key, value}; -}) - .orderBy('value') - .reverse() - .keyBy('key') - .mapValues('value') - .value(); - - let table = new Table({ - head: [ chalk.green('TAG'), chalk.green('Total Problem')] - }); - - _.forEach(allTags, (value, key) => { - table.push([key, value]); + jsonStream.on('data', (data) => { + if( has(data,'tags') ) { + _.forEach(data.tags, (tag) => { + if (has(allTags, tag)) { + allTags[tag]++; + } else { + allTags[tag] = 1; + } }); - - log(''); - log(chalk.bold.green(` Total tag: ${table.length}`)); - log(table.toString()); - }) - .on('response', (response) => { - - responseCode = response.statusCode; - contentType = response.headers['content-type']; - - debugs(`HTTP Code: ${responseCode}`); - debugs(`Content-Type: ${contentType}`); - }) - .pipe( JSONStream.parse('result.problems.*') ) - .on('header', (data) => { - - debugs(`API Status: ${data.status}`); - - if( data.status !== 'OK' ){ - apiFailed = true; - apiMsg = data.comment; - } - }) - .on('data', (data) => { - - // debugs('data received'); - - // - // If request failed, tags may not exist - // - if( _.has(data,'tags') ) { - _.forEach(data.tags, (tag) => { - if (_.has(allTags, tag)) { - allTags[tag]++; - } else { - allTags[tag] = 1; - } - }); - } - }); + } + }); }; diff --git a/src/lib/api/userinfo.js b/src/lib/api/userinfo.js index bea5196..13ecb72 100644 --- a/src/lib/api/userinfo.js +++ b/src/lib/api/userinfo.js @@ -4,22 +4,22 @@ import request from 'request'; import debug from 'debug'; import Table from 'cli-table2'; import chalk from 'chalk'; -import _ from 'lodash'; +import has from 'has'; +import { isArray, join , split, forEach } from 'lodash'; import qs from 'qs'; import ora from 'ora'; import { log, logr } from '../helpers'; var debugs = debug('CF:userinfo'); var spinner = ora({ spinner: 'line' }); -var GB = chalk.bold.green; - +const GB = chalk.bold.green; /** * @param handles */ export default (handles) => { - let invalidHandle = !_.isArray(handles) && typeof handles !== 'string'; + let invalidHandle = !isArray(handles) && typeof handles !== 'string'; if( invalidHandle ){ throw new Error('handles must be array or string'); } @@ -30,12 +30,12 @@ export default (handles) => { }; let handlesString = handles; - if( _.isArray(handles) ){ - handlesString = _.join(handles,';'); + if( isArray(handles) ){ + handlesString = join(handles,';'); } else if( handles.indexOf(',') !== -1 ){ - handlesString = _.split(handles,','); - handlesString = _.join(handlesString,';'); + handlesString = split(handles,','); + handlesString = join(handlesString,';'); } let qsf = qs.stringify({ handles: handlesString }, { encode: false }); @@ -45,56 +45,50 @@ export default (handles) => { spinner.text = 'fetching user info...'; spinner.start(); - request - .get(reqOptions, (error, response, body) => { + request.get(reqOptions, (error, response, body) => { - if(error){ - spinner.fail(); - logr(error); - return; - } + if(error){ + spinner.fail(); + return logr(error); + } - let { statusCode } = response; + let { statusCode } = response; + if( statusCode !== 200 ){ + spinner.fail(); + return logr( has(body,'comment') ? body.comment : `HTTP failed with status ${statusCode}`); + } - if( statusCode !== 200 ){ - spinner.fail(); - logr(body.comment || ' HTTP error'); - return; - } + if( body.status !== 'OK' ){ + spinner.fail(); + return logr(body.comment); + } + spinner.succeed(); - if( body.status !== 'OK' ){ - spinner.fail(); - logr(` ${body.comment}`); - return; + let table = new Table({ + head: [ GB('Name'), GB('Handle'), GB('Rank'), GB('Rating'), GB('Max'), GB('Contrib.'), GB('Country'), GB('Organization') ] + }); + + forEach(body.result, (data) => { + + let name = ''; + if( has(data,'firstName') && has(data,'lastName') ){ + name = `${data.firstName} ${data.lastName}`; } - spinner.succeed(); - - let table = new Table({ - head: [ GB('Name'), GB('Handle'), GB('Rank'), GB('Rating'), GB('Max'), GB('Contrib.'), GB('Country'), GB('Organization') ] - }); - - _.forEach(body.result, (data) => { - - let name = ''; - if( _.has(data,'firstName') && _.has(data,'lastName') ){ - name = `${data.firstName} ${data.lastName}`; - } - - let info = [ - name, - data.handle || '', - data.rank || '0', - data.rating || '', - data.maxRating || '', - data.contribution || '', - data.country || '', - data.organization || '' - ]; - table.push(info); - }); - - log(''); - log(table.toString()); + let info = [ + name, + data.handle || '', + data.rank || '0', + data.rating || '', + data.maxRating || '', + data.contribution || '', + data.country || '', + data.organization || '' + ]; + table.push(info); }); + + log(''); + log(table.toString()); + }); }; \ No newline at end of file diff --git a/src/lib/api/usertags.js b/src/lib/api/usertags.js index 39207ee..6ec5a9e 100644 --- a/src/lib/api/usertags.js +++ b/src/lib/api/usertags.js @@ -14,7 +14,7 @@ import { log, logr } from '../helpers'; import tags from './tags'; var spinner = ora({ spinner: 'line' }); -const debugs = debug('CF:usertags'); +var debugs = debug('CF:usertags'); const CB = chalk.cyan.bold; const PROGRESS_WIDTH = 20; @@ -69,94 +69,90 @@ function getUserTags(handle, callback) { let apiFailed= false; let apiMsg = null; - let responseCode = '404'; + let responseCode = 404; let contentType = ''; let totalSubmissions = 0; let totalAccepted = 0; - let userTags = new Map(); spinner.text = 'Fetching user tags...'; spinner.start(); - request - .get(reqOptions) - .on('error', (err) => { + let reqStream = request.get(reqOptions); + let jsonStream = reqStream.pipe( JSONStream.parse('result.*') ); - debugs('Failed: Request error'); - debugs(err); + reqStream.on('error', (err) => { + debugs('Failed: Request error'); + debugs(err); - return callback(err); - }) - .on('complete', () => { + return callback(err); + }); - debugs('parsing completed'); + reqStream.on('complete', () => { + debugs('parsing completed'); - if( responseCode !== 200 ){ - spinner.fail(); - if( apiMsg !== null ){ - return callback(apiMsg); - } - return callback('Connection error.Failed.'); - } + if( responseCode !== 200 ){ + spinner.fail(); + return callback(apiMsg || `HTTP failed with status ${responseCode}`); + } - if( contentType.indexOf('application/json;') === -1 ){ - spinner.fail(); - return callback('Failed.Invalid data.'); - } + if( contentType.indexOf('application/json;') === -1 ){ + spinner.fail(); + return callback('Failed.Invalid data.'); + } - if( apiFailed ){ - spinner.fail(); - return callback(apiMsg); - } + if( apiFailed ){ + spinner.fail(); + return callback(apiMsg); + } + spinner.succeed(); - spinner.succeed(); + return callback(null, userTags, totalSubmissions, totalAccepted); + }); - return callback(null, userTags, totalSubmissions, totalAccepted); - }) - .on('response', (response) => { - responseCode = response.statusCode; - contentType = response.headers['content-type']; + reqStream.on('response', (response) => { + debugs(`HTTP Code: ${responseCode}`); + debugs(`Content-Type: ${contentType}`); - debugs(`HTTP Code: ${responseCode}`); - debugs(`Content-Type: ${contentType}`); - }) - .pipe( JSONStream.parse('result.*') ) - .on('header', (data) => { + responseCode = response.statusCode; + contentType = response.headers['content-type']; + }); - debugs(`API Status: ${data.status}`); - if( data.status !== 'OK' ){ - apiFailed = true; - apiMsg = data.comment; - } - }) - .on('data', (data) => { - - totalSubmissions++; - - if( has(data,'problem') && data.verdict === 'OK' ) { - - totalAccepted++; - - let prob = `${data.problem.contestId}${data.problem.index}`; - let problemTags = data.problem.tags; - - forEach(problemTags, (tag) => { - if( userTags.has(tag) ){ - let mySet = userTags.get(tag); - mySet.add(prob); - userTags.set(tag, mySet); - } - else{ - let mySet = new Set(); - mySet.add(prob); - userTags.set(tag,mySet); - } - }); - } - }); + jsonStream.on('header', (data) => { + debugs(`API Status: ${data.status}`); + + if( data.status !== 'OK' ){ + apiFailed = true; + apiMsg = data.comment; + } + }); + + + jsonStream.on('data', (data) => { + totalSubmissions++; + + if( has(data,'problem') && data.verdict === 'OK' ) { + totalAccepted++; + + let prob = `${data.problem.contestId}${data.problem.index}`; + let problemTags = data.problem.tags; + + forEach(problemTags, (tag) => { + if( userTags.has(tag) ){ + let mySet = userTags.get(tag); + mySet.add(prob); + userTags.set(tag, mySet); + } + else{ + let mySet = new Set(); + mySet.add(prob); + userTags.set(tag,mySet); + } + }); + } + }); } diff --git a/src/lib/crawler/Countrystandings.js b/src/lib/crawler/Countrystandings.js index 643a6de..0b2af49 100644 --- a/src/lib/crawler/Countrystandings.js +++ b/src/lib/crawler/Countrystandings.js @@ -13,16 +13,13 @@ import countries from '../countries'; var debugs = debug('CF:standings:c'); var spinner = ora({ spinner: 'line' }); -var GB = chalk.bold.green; -var CB = chalk.bold.cyan; -var RB = chalk.bold.red; +const TIME_OUT = 30000; +const GB = chalk.bold.green; +const CB = chalk.bold.cyan; +const RB = chalk.bold.red; + -/* - *********** TO-DO / FIX ********************************* - * check 702 contest, its not match with table headers - * ******************************************************* - */ export default class Countrystandings { /* @@ -30,7 +27,7 @@ export default class Countrystandings { * @param {String} country * @param {Number} total */ - constructor({contestId = null, country = null, total = 50} = {}) { + constructor({contestId = null, country = null, total = 50, offset = 1} = {}) { let isInvalid = contestId === null || country === null || !Number.isInteger(contestId) || typeof country !== 'string'; if (isInvalid) { @@ -40,11 +37,13 @@ export default class Countrystandings { this.error = null; if (countries.indexOf(country) === -1) { this.error = `'${country}' not found in supported country list.Please run 'cf country' to see all supported countries.`; + return; } this.contestId = contestId; this.country = country; this.total = total; + this.offset = offset; } @@ -55,7 +54,7 @@ export default class Countrystandings { show(callback){ let self = this; - if( self.error !== null ){ + if( self.error ){ if( typeof callback === 'function' ){ return callback(self.error); } @@ -67,11 +66,12 @@ export default class Countrystandings { let reqOptions = { uri: '', headers: headers, - timeout: 30000 + timeout: TIME_OUT }; + let head; let table = new Table(); - var totalPage = 5; + var totalPage = 2; var count = 0; var found = 0; var page = 1; @@ -85,12 +85,13 @@ export default class Countrystandings { }, (next) => { - reqOptions.uri = `http://codeforces.com/contest/${self.contestId}/standings/page/${page}`; - - debugs(`Fetching from page ${page}...`); - spinner.text = `Fetching standings - page ${page}...`; + let remain = (self.total - found) < 0 + ? 0 + : (self.total - found); + spinner.text = `Fetching standings - ${found} participants found,${remain} remaining...`; spinner.start(); + reqOptions.uri = `http://codeforces.com/contest/${self.contestId}/standings/page/${page}`; request.get(reqOptions, (err, response, body) => { if (err) { @@ -99,26 +100,13 @@ export default class Countrystandings { let {statusCode} = response; if (statusCode !== 200) { - return next('HTTP error'); + return next(`HTTP failed with status ${statusCode}`); } - spinner.stop(); var $ = cheerio.load(body, {decodeEntities: true}); - let standings = $('table.standings .standings-flag'); - - standings = _.filter(standings, (stdng) => { - return $(stdng).attr('title') === self.country; - }); - + let standings = $(`table.standings .standings-flag[title="${self.country}"]`); found += standings.length; - let remain = (self.total - found) < 0 - ? 0 - : (self.total - found); - - spinner.text = `${standings.length} users found in page ${page} [${remain} remaining]`; - spinner.start(); - spinner.succeed(); _.forEach(standings, (standing) => { @@ -148,6 +136,9 @@ export default class Countrystandings { else if (val.indexOf('-') !== -1) { val = RB(val); } + else{ + val = chalk.bold.white(val); + } break; default: if (val.indexOf(':') !== -1) { @@ -165,6 +156,7 @@ export default class Countrystandings { }); if (page === 1) { + contestName = _.replace($('.contest-name a').text(), /\s\s+/g, ''); let pg = $('.page-index'); let indxes = pg.length; @@ -173,8 +165,19 @@ export default class Countrystandings { totalPage = parseInt($(pg).attr('pageindex'), 10); debugs(`Total page: ${totalPage}`); } - } + let tabHeads = $('table.standings tr')[0]; + head = _.map( $(tabHeads).children(), (heads) => { + let name = _.replace( $(heads).text() , /\s\s+/g, ''); + if( name === '#' ){ + name = 'Rank'; + } + else if( name.length > 1 && self.hasDigit(name) ){ + name = self.splitPenalty(name, 1); + } + return GB(name); + }); + } page++; return next(); }); @@ -197,18 +200,13 @@ export default class Countrystandings { return; } - let totalProblem = table[0].length - 5; - let problemChar = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); - let head = [GB('#'), GB('Rank'), GB('Who'), GB('#'), GB('*')]; - - let problemNames = _ - .slice(problemChar, 0, totalProblem) - .map(x => { - return GB(x); - }); + let colWidths = [ null, ...(_.map(head, (hd,key) => { //bad practice? who cares! eslint, is that you?? + return key == 1 ? 30 : null; + }))]; - head = head.concat(problemNames); - table.options.head = head; + table.options.head = [ GB('#'), ...head ]; + table.options.colWidths = colWidths; + table.options.wordWrap = true; log(''); log(CB(`Contest: ${contestName}`)); @@ -229,4 +227,13 @@ export default class Countrystandings { /* istanbul ignore next */ return ` ${value.substring(0, index)}\n${value.substring(index)}`; } + + + /** + * Check if a string contains any digit + * @param val + */ + hasDigit(val) { + return val.match(/\d+/g) != null; + } } \ No newline at end of file diff --git a/src/lib/crawler/Ratings.js b/src/lib/crawler/Ratings.js index bab2067..5bfa1ec 100644 --- a/src/lib/crawler/Ratings.js +++ b/src/lib/crawler/Ratings.js @@ -15,9 +15,9 @@ import countries from '../countries'; var spinner = ora({ spinner: 'line' }); var debugs = debug('CF:standings:c'); -var GB = chalk.bold.green; -var CB = chalk.bold.cyan; -var RB = chalk.bold.red; +const GB = chalk.bold.green; +const CB = chalk.bold.cyan; +const RB = chalk.bold.red; export default class Ratings { @@ -35,14 +35,18 @@ export default class Ratings { this.error = null; if (countries.indexOf(country) === -1) { - this.error = `Invalid country '${country}'.Please run command 'cf country' to see supported country list.`; + this.error = `Invalid country '${country}'.Please run 'cf country' to see supported country list.`; + return; } this.country = country; this.withOrg = org; } - /** + + /******* TO-DO ************************************************ + * Add count and offset (and may be categorize by CF colors?) + ************************************************************** * @param callback * @returns {*} */ @@ -50,16 +54,11 @@ export default class Ratings { let self = this; let isCallback = typeof callback === 'function'; - if( self.error !== null ){ - if( isCallback ){ - return callback(self.error); - } - logr(self.error); - return; + if( self.error ){ + return isCallback ? callback(self.error) : logr(self.error); } - debugs(`Fetching ratings of ${self.country}...`); - spinner.text = `fetching top 200 user ratings of ${self.country}...`; + spinner.text = `fetching top few user ratings of ${self.country}...`; spinner.start(); waterfall([ @@ -78,7 +77,7 @@ export default class Ratings { let {statusCode} = response; if (statusCode !== 200) { - return next('HTTP error'); + return next(`HTTP failed with status ${statusCode}`); } var $ = cheerio.load(body, {decodeEntities: true}); @@ -186,16 +185,12 @@ export default class Ratings { let {statusCode} = response; if (statusCode !== 200) { - if( has(body, 'comment') ){ - return callback(body.comment); - } - return callback('HTTP error [org]'); + return callback( has(body, 'comment') ? body.comment : `HTTP failed with status ${statusCode}`); } if (body.status !== 'OK') { return callback(body.comment); } - spinner.succeed(); forEach(body.result, (data, key) => { diff --git a/src/lib/crawler/Sourcecode.js b/src/lib/crawler/Sourcecode.js index c110442..4cb140b 100644 --- a/src/lib/crawler/Sourcecode.js +++ b/src/lib/crawler/Sourcecode.js @@ -11,13 +11,13 @@ import chalk from 'chalk'; import has from 'has'; import ora from 'ora'; import languages from '../languages'; -import { waterfall, each, eachLimit, parallel } from 'async'; +import { waterfall, eachLimit, series, eachSeries } from 'async'; import { log, logr, checkPath, commonHeaders } from '../helpers'; var debugs = debug('CF:sourcecode'); var spinner = ora({ spinner: 'line' }); -var GB = chalk.green.bold; +const GB = chalk.green.bold; const TIME_OUT = 60000; //1 minute var headers = commonHeaders(); @@ -36,7 +36,7 @@ export default class Sourcecode { constructor({handle = null, limit = 10, withProblem = false, dir = '.'} = {}) { if (handle === null || typeof handle != 'string') { - throw new Error(`handle should not be null or empty`); + throw new Error('handle should not be null or empty'); } this.options = { handle, withProblem, limit, dir }; @@ -66,8 +66,7 @@ export default class Sourcecode { totalSubmissions = submissions.length; if (self.options.withProblem) { - eachLimit(submissions, self.options.limit, self.getResource.bind(self, dir), next); - return; + return eachLimit(submissions, self.options.limit, self.getResource.bind(self, dir), next); } eachLimit(submissions, self.options.limit, self.getOnlySource.bind(self, dir), next); @@ -99,7 +98,6 @@ export default class Sourcecode { createOutputDir(options, callback) { let {dir, handle} = options; - waterfall([ (next) => { if (dir !== '.') { @@ -116,7 +114,6 @@ export default class Sourcecode { dir = path.join(dir, handle); } - spinner.text = `creating directory ${dir}`; spinner.start(); @@ -159,85 +156,86 @@ export default class Sourcecode { spinner.text = 'fetching submissions..'; spinner.start(); - request - .get(reqOptions) - .on('error', (err) => { + let reqStream = request.get(reqOptions); + let jsonStream = reqStream.pipe(JSONStream.parse('result.*')); - debugs('Failed: Request error'); - debugs(err); + reqStream.on('error', (err) => { + debugs('Failed: Request error'); + debugs(err); - return callback(err); - }) - .on('complete', () => { + return callback(err); + }); - debugs('parsing completed'); - if (responseCode !== 200) { - if (apiMsg !== null) { - return callback(apiMsg); - } - return callback('Failed HTTP'); - } + reqStream.on('complete', () => { + debugs('parsing completed'); - if (contentType.indexOf('application/json;') === -1) { - return callback('Failed.Not valid data.'); - } + if (responseCode !== 200) { + return callback(apiMsg || `HTTP failed with status ${responseCode}`); + } - if (apiFailed) { - return callback(apiMsg); - } + if (contentType.indexOf('application/json;') === -1) { + return callback('Failed.Not valid data.'); + } - spinner.stop(); - spinner.text = `total accepted submission: ${acSubmissions.length}`; - spinner.succeed(); + if (apiFailed) { + return callback(apiMsg); + } - return callback(null, dir, acSubmissions); - }) - .on('response', (response) => { + spinner.stop(); + spinner.text = `total accepted submission: ${acSubmissions.length}`; + spinner.succeed(); - responseCode = response.statusCode; - contentType = response.headers['content-type']; + return callback(null, dir, acSubmissions); + }); - debugs(`HTTP Code: ${responseCode}`); - debugs(`Content-Type: ${contentType}`); - }) - .pipe(JSONStream.parse('result.*')) - .on('header', (data) => { - debugs(`API Status: ${data.status}`); + reqStream.on('response', (response) => { + debugs(`HTTP Code: ${responseCode}`); + debugs(`Content-Type: ${contentType}`); - if (data.status !== 'OK') { - apiFailed = true; - apiMsg = data.comment; - } - }) - .on('data', (data) => { + responseCode = response.statusCode; + contentType = response.headers['content-type']; + }); - // `data.contestId < 10000` is for detecting gym.Need authorization - if (has(data, 'problem') && data.verdict === 'OK' && data.contestId < 10000) { - let {problem, id, contestId, programmingLanguage} = data; - let {index} = problem; - let problemId = `${contestId}${index}`; - let root = contestId > 10000 + jsonStream.on('header', (data) => { + debugs(`API Status: ${data.status}`); + + if (data.status !== 'OK') { + apiFailed = true; + apiMsg = data.comment; + } + }); + + + jsonStream.on('data', (data) => { + + // `data.contestId < 10000` is for detecting gym.Useless now.Need authorization + if (has(data, 'problem') && data.verdict === 'OK' && data.contestId < 10000) { + + let {problem, id, contestId, programmingLanguage} = data; + let {index} = problem; + let problemId = `${contestId}${index}`; + let root = contestId > 10000 ? 'gym' : 'contest'; //currently gym not working. need authorization - let submissionUrl = `http://codeforces.com/${root}/${contestId}/submission/${id}`; - let problemUrl = `http://codeforces.com/${root}/${contestId}/problem/${index}`; - - acSubmissions.push({ - submissionId: id, - contestId: contestId, - problemIndex: index, - problemId: problemId, - submissionUrl: submissionUrl, - problemUrl: withProblem + let submissionUrl = `http://codeforces.com/${root}/${contestId}/submission/${id}`; + let problemUrl = `http://codeforces.com/${root}/${contestId}/problem/${index}`; + + acSubmissions.push({ + submissionId: id, + contestId: contestId, + problemIndex: index, + problemId: problemId, + submissionUrl: submissionUrl, + problemUrl: withProblem ? problemUrl : null, - language: programmingLanguage - }); - } - }); + language: programmingLanguage + }); + } + }); } /** @@ -253,16 +251,14 @@ export default class Sourcecode { let outputPath = path.join(dir, `${problemId}`); let ext = languages.getExtension(language); - parallel([ + series([ (next) => { - log(GB(` fetching sourecode ${problemId}_${submissionId}`)); let filePath = path.join(outputPath, `${problemId}_${submissionId}.${ext}`); self.getSourceCode(submissionUrl, filePath, `code ${problemId}_${submissionId}`, next); }, (next) => { - log(GB(` fetching problem ${problemId}`)); let filePath = path.join(outputPath, `${problemId}.html`); @@ -281,7 +277,7 @@ export default class Sourcecode { return callback(); } - each([data[0], data[1]], self.writeOutputs, callback); + eachSeries([data[0], data[1]], self.writeOutputs, callback); }); } @@ -299,9 +295,8 @@ export default class Sourcecode { let outputPath = path.join(dir, `${problemId}`); let ext = languages.getExtension(language); - parallel([ + series([ (next) => { - log(GB(` fetching sourecode ${problemId}_${submissionId}`)); let filePath = path.join(outputPath, `${problemId}_${submissionId}.${ext}`); diff --git a/src/lib/crawler/Submit.js b/src/lib/crawler/Submit.js index d6f3192..269fdfb 100644 --- a/src/lib/crawler/Submit.js +++ b/src/lib/crawler/Submit.js @@ -96,7 +96,7 @@ export default class Submit { }, self.getCSRFToken, self.login, - self.getCSRFToken, + // self.getCSRFToken, //ok great, same csrf token working for both login and submit! self.submitSolution ], (err, res) => { @@ -116,8 +116,7 @@ export default class Submit { spinner.fail(); return; } - logr(err); - return; + return logr(err); } /* istanbul ignore next */ @@ -254,8 +253,6 @@ export default class Submit { // Ask for handle and password // inquirer.prompt(credentials).then((answers) => { - - options.handle = answers.handle; options.password = answers.password; @@ -301,13 +298,8 @@ export default class Submit { return callback('token not found'); } - debugs('Csrf token found!'); + debugs(`Csrf token found! ${csrf_token}`); - // TO-DO? - // Next form is resistration form.This idea works only for two form - // and hey, in this module we do not need anymore form - // but need to keep this idea and improve! - // options.form = options.nextForm; spinner.succeed(); @@ -395,7 +387,7 @@ export default class Submit { debugs('Successfully logged in'); spinner.succeed(); - return callback(null, options); + return callback(null, csrf_token, options); }); } @@ -472,8 +464,6 @@ export default class Submit { return callback('Error: Submission failed.Please check your options.'); } - debugs('Solution submitted!'); - spinner.succeed(); spinner.text = chalk.bold.green(`Submitted at ${location.date}`); spinner.start(); diff --git a/tests/api/test_submission.js b/tests/api/test_submission.js index 4ddb33f..1c90810 100644 --- a/tests/api/test_submission.js +++ b/tests/api/test_submission.js @@ -222,7 +222,7 @@ describe('Codeforces', function() { expect(jsonfile.writeFileSync.called).to.be.false; expect(request.get.called).to.be.true; expect(helpers.logr.called).to.false; - expect(err).to.equal('HTTP error'); + expect(err).to.equal('HTTP failed with status 404'); done(); }); }}); @@ -242,7 +242,7 @@ describe('Codeforces', function() { expect(jsonfile.writeFileSync.called).to.be.false; expect(request.get.called).to.be.true; expect(helpers.logr.called).to.false; - expect(err).to.equal('HTTP error'); + expect(err).to.equal('HTTP failed with status 404'); done(); }); }}); diff --git a/tests/crawler/test_countrystandings.js b/tests/crawler/test_countrystandings.js index c138f6c..1dde27e 100644 --- a/tests/crawler/test_countrystandings.js +++ b/tests/crawler/test_countrystandings.js @@ -133,7 +133,7 @@ describe('Codeforces',function () { new Countrystandings({ contestId: 10, country: 'Bangladesh', total: 1 }).show(function (err) { expect(request.get.called).to.be.true; expect(helpers.logr.called).to.be.false; - expect(err).to.equal(`HTTP error`); + expect(err).to.equal(`HTTP failed with status 404`); done(); }); }); @@ -184,7 +184,7 @@ describe('Codeforces',function () { it('should request call 5 times', function (done) { new Countrystandings({ contestId: 10, country: 'Bangladesh', total: 1 }).show(function (err) { expect(request.get.called).to.be.true; - expect(request.get.callCount).to.equal(5); + expect(request.get.callCount).to.equal(2); expect(err).to.be.null; done(); }); @@ -211,7 +211,7 @@ describe('Codeforces',function () { it('should request call 5 times and call log', function (done) { new Countrystandings({ contestId: 10, country: 'Bangladesh', total: 1 }).show(); expect(request.get.called).to.be.true; - expect(request.get.callCount).to.equal(5); + expect(request.get.callCount).to.equal(2); expect(helpers.log.called).to.be.true; done(); }); diff --git a/tests/crawler/test_ratings.js b/tests/crawler/test_ratings.js index b45d799..c0b923a 100644 --- a/tests/crawler/test_ratings.js +++ b/tests/crawler/test_ratings.js @@ -68,7 +68,7 @@ describe('Codeforces',function () { new Ratings({ country: 'invlid' }).show(function (err,result) { expect(request.get.called).to.be.false; expect(helpers.logr.called).to.be.false; - expect(err).to.equal(`Invalid country 'invlid'.Please run command 'cf country' to see supported country list.`); + expect(err).to.equal(`Invalid country 'invlid'.Please run 'cf country' to see supported country list.`); done(); }); }); @@ -145,7 +145,7 @@ describe('Codeforces',function () { instnecRating.show(function (err,result) { expect(request.get.called).to.be.true; expect(helpers.logr.called).to.be.false; - expect(err).to.equal('HTTP error'); + expect(err).to.equal('HTTP failed with status 404'); expect(instnecRating.getOrg.called).to.be.false; done(); }); @@ -267,7 +267,7 @@ describe('Codeforces',function () { expect(request.get.called).to.be.true; expect(helpers.logr.called).to.be.false; expect(helpers.log.called).to.be.false; - expect(err).to.equal('HTTP error [org]'); + expect(err).to.equal('HTTP failed with status 404'); done(); }); }); diff --git a/tests/crawler/test_submit.js b/tests/crawler/test_submit.js index e232a28..cbdeaa6 100644 --- a/tests/crawler/test_submit.js +++ b/tests/crawler/test_submit.js @@ -827,7 +827,7 @@ describe('Codeforces', function() { sinon.stub(helpers, 'checkPath').yields(null); sinon.stub(instanceSubmit, 'prepareInput').yields(null, { handle: 'some', password: 'adad' }); sinon.stub(instanceSubmit, 'getCSRFToken').yields(null,'adad', { handle: 'some', password: 'adad', logout: true, contestId: 123, problemIndex: 'A', language: 10, codePath: 'ada' }); - sinon.stub(instanceSubmit, 'login').yields(null, { handle: 'some', password: 'adad' }); + sinon.stub(instanceSubmit, 'login').yields(null, 'someToken', { handle: 'some', password: 'adad' }); sinon.stub(fs,'createReadStream').returns('adad'); sinon.stub(request,'post').yields('posterror'); sinon.stub(helpers,'log', function (text) {}); @@ -860,7 +860,7 @@ describe('Codeforces', function() { sinon.stub(helpers, 'checkPath').yields(null); sinon.stub(instanceSubmit, 'prepareInput').yields(null, { handle: 'some', password: 'adad' }); sinon.stub(instanceSubmit, 'getCSRFToken').yields(null,'adad', { handle: 'some', password: 'adad', logout: true, contestId: 123, problemIndex: 'A', language: 10, codePath: 'ada' }); - sinon.stub(instanceSubmit, 'login').yields(null, { handle: 'some', password: 'adad' }); + sinon.stub(instanceSubmit, 'login').yields(null, 'someToken', { handle: 'some', password: 'adad' }); sinon.stub(fs,'createReadStream').returns('adad'); sinon.stub(request,'post').yields(null, { headers: { } }, {}); sinon.stub(helpers,'log', function (text) {}); @@ -893,7 +893,7 @@ describe('Codeforces', function() { sinon.stub(helpers, 'checkPath').yields(null); sinon.stub(instanceSubmit, 'prepareInput').yields(null, { handle: 'some', password: 'adad' }); sinon.stub(instanceSubmit, 'getCSRFToken').yields(null,'adad', { handle: 'some', password: 'adad', logout: true, contestId: 123, problemIndex: 'A', language: 10, codePath: 'ada' }); - sinon.stub(instanceSubmit, 'login').yields(null, { handle: 'some', password: 'adad' }); + sinon.stub(instanceSubmit, 'login').yields(null, 'someToken', { handle: 'some', password: 'adad' }); sinon.stub(fs,'createReadStream').returns('adad'); sinon.stub(request,'post').yields(null, { headers: { location: 'adad' } }, {}); sinon.stub(helpers,'log', function (text) {}); @@ -926,7 +926,7 @@ describe('Codeforces', function() { sinon.stub(helpers, 'checkPath').yields(null); sinon.stub(instanceSubmit, 'prepareInput').yields(null, { handle: 'some', password: 'adad' }); sinon.stub(instanceSubmit, 'getCSRFToken').yields(null,'adad', { handle: 'some', password: 'adad', logout: true, contestId: 123, problemIndex: 'A', language: 10, codePath: 'ada' }); - sinon.stub(instanceSubmit, 'login').yields(null, { handle: 'some', password: 'adad' }); + sinon.stub(instanceSubmit, 'login').yields(null, 'someToken', { handle: 'some', password: 'adad' }); sinon.stub(fs,'createReadStream').returns('adad'); sinon.stub(request,'post').yields(null, { headers: { location: 'adad' } }, `
someError
`); sinon.stub(helpers,'log', function (text) {}); @@ -959,7 +959,7 @@ describe('Codeforces', function() { sinon.stub(helpers, 'checkPath').yields(null); sinon.stub(instanceSubmit, 'prepareInput').yields(null, { handle: 'some', password: 'adad' }); sinon.stub(instanceSubmit, 'getCSRFToken').yields(null,'adad', { handle: 'some', password: 'adad', logout: true, contestId: 123, problemIndex: 'A', language: 10, codePath: 'ada', type: 'contest' }); - sinon.stub(instanceSubmit, 'login').yields(null, { handle: 'some', password: 'adad' }); + sinon.stub(instanceSubmit, 'login').yields(null, 'someToken', { handle: 'some', password: 'adad', logout: true, contestId: 123, problemIndex: 'A', language: 10, codePath: 'ada', type: 'contest' }); sinon.stub(fs,'createReadStream').returns('adad'); sinon.stub(request,'post').yields(null, { headers: { location: '/contest/123/my' } }, {}); sinon.stub(helpers,'log', function (text) {}); @@ -992,7 +992,7 @@ describe('Codeforces', function() { sinon.stub(helpers, 'checkPath').yields(null); sinon.stub(instanceSubmit, 'prepareInput').yields(null, { handle: 'some', password: 'adad' }); sinon.stub(instanceSubmit, 'getCSRFToken').yields(null,'adad', { handle: 'some', password: 'adad', logout: true, contestId: 123, problemIndex: 'A', language: 10, codePath: 'ada', type: 'gym' }); - sinon.stub(instanceSubmit, 'login').yields(null, { handle: 'some', password: 'adad' }); + sinon.stub(instanceSubmit, 'login').yields(null, 'someToken', { handle: 'some', password: 'adad', logout: true, contestId: 123, problemIndex: 'A', language: 10, codePath: 'ada', type: 'gym' }); sinon.stub(fs,'createReadStream').returns('adad'); sinon.stub(request,'post').yields(null, { headers: { location: '/gym/123/my' } }, {}); sinon.stub(helpers,'log', function (text) {});