From f34db8115f8909264ac5468f612e732989807613 Mon Sep 17 00:00:00 2001 From: Robert Dyer Date: Fri, 12 Jul 2024 22:15:43 -0500 Subject: [PATCH 1/6] get CLI working --- lib/config.js | 3 +- lib/helper.js | 3 +- lib/plugins/cookie.chrome.js | 184 +++++++++++++++++++++++++++++++++++ lib/plugins/leetcode.js | 12 ++- 4 files changed, 197 insertions(+), 5 deletions(-) create mode 100644 lib/plugins/cookie.chrome.js diff --git a/lib/config.js b/lib/config.js index 373b9f0f..ac7dd953 100644 --- a/lib/config.js +++ b/lib/config.js @@ -28,7 +28,8 @@ const DEFAULT_CONFIG = { 'ruby', 'rust', 'scala', - 'swift' + 'swift', + 'typescript' ], urls: { base: 'https://leetcode.com', diff --git a/lib/helper.js b/lib/helper.js index 8806086e..99e02f1e 100644 --- a/lib/helper.js +++ b/lib/helper.js @@ -45,7 +45,8 @@ const LANGS = [ {lang: 'ruby', ext: '.rb', style: '#'}, {lang: 'rust', ext: '.rs', style: 'c'}, {lang: 'scala', ext: '.scala', style: 'c'}, - {lang: 'swift', ext: '.swift', style: 'c'} + {lang: 'swift', ext: '.swift', style: 'c'}, + {lang: 'typescript', ext: '.ts', style: 'c'} ]; const h = {}; diff --git a/lib/plugins/cookie.chrome.js b/lib/plugins/cookie.chrome.js new file mode 100644 index 00000000..e2d42780 --- /dev/null +++ b/lib/plugins/cookie.chrome.js @@ -0,0 +1,184 @@ +var path = require('path'); + +var log = require('../log'); +var Plugin = require('../plugin'); +var Queue = require('../queue'); +var session = require('../session'); + +// [Usage] +// +// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cookie.chrome.md +// +var plugin = new Plugin(13, 'cookie.chrome', '2018.11.18', + 'Plugin to reuse Chrome\'s leetcode cookie.', + ['ffi:win32', 'keytar:darwin', 'ref:win32', 'ref-struct:win32', 'sqlite3']); + +plugin.help = function() { + switch (process.platform) { + case 'darwin': + break; + case 'linux': + log.warn('To complete the install: sudo apt install libsecret-tools'); + break; + case 'win32': + break; + } +}; + +var Chrome = {}; + +var ChromeMAC = { + getDBPath: function() { + return `${process.env.HOME}/Library/Application Support/Google/Chrome/${this.profile}/Cookies`; + }, + iterations: 1003, + getPassword: function(cb) { + var keytar = require('keytar'); + keytar.getPassword('Chrome Safe Storage', 'Chrome').then(cb); + } +}; + +var ChromeLinux = { + getDBPath: function() { + return `${process.env.HOME}/.config/google-chrome/${this.profile}/Cookies`; + }, + iterations: 1, + getPassword: function(cb) { + // FIXME: keytar failed to read gnome-keyring on ubuntu?? + var cmd = 'secret-tool lookup application chrome'; + var password = require('child_process').execSync(cmd).toString(); + return cb(password); + } +}; + +var ChromeWindows = { + getDBPath: function() { + return path.resolve(process.env.APPDATA || '', `../Local/Google/Chrome/User Data/${this.profile}/Cookies`); + }, + getPassword: function(cb) { cb(); } +}; + +Object.setPrototypeOf(ChromeMAC, Chrome); +Object.setPrototypeOf(ChromeLinux, Chrome); +Object.setPrototypeOf(ChromeWindows, Chrome); + +Chrome.getInstance = function() { + switch (process.platform) { + case 'darwin': return ChromeMAC; + case 'linux': return ChromeLinux; + case 'win32': return ChromeWindows; + } +}; +var my = Chrome.getInstance(); + +ChromeWindows.decodeCookie = function(cookie, cb) { + var ref = require('ref'); + var ffi = require('ffi'); + var Struct = require('ref-struct'); + + var DATA_BLOB = Struct({ + cbData: ref.types.uint32, + pbData: ref.refType(ref.types.byte) + }); + var PDATA_BLOB = new ref.refType(DATA_BLOB); + var Crypto = new ffi.Library('Crypt32', { + 'CryptUnprotectData': ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]] + }); + + var inBlob = new DATA_BLOB(); + inBlob.pbData = cookie; + inBlob.cbData = cookie.length; + var outBlob = ref.alloc(DATA_BLOB); + + Crypto.CryptUnprotectData(inBlob.ref(), null, null, null, null, 0, outBlob); + var outDeref = outBlob.deref(); + var buf = ref.reinterpret(outDeref.pbData, outDeref.cbData, 0); + + return cb(null, buf.toString('utf8')); +}; + +Chrome.decodeCookie = function(cookie, cb) { + var crypto = require('crypto'); + crypto.pbkdf2(my.password, 'saltysalt', my.iterations, 16, 'sha1', function(e, key) { + if (e) return cb(e); + + var iv = new Buffer(' '.repeat(16)); + var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv); + decipher.setAutoPadding(false); + + var buf = decipher.update(cookie.slice(3)); // remove prefix "v10" or "v11" + var final = decipher.final(); + final.copy(buf, buf.length - 1); + + var padding = buf[buf.length - 1]; + if (padding) buf = buf.slice(0, buf.length - padding); + + return cb(null, buf.toString('utf8')); + }); +}; + +function doDecode(key, queue, cb) { + var ctx = queue.ctx; + var cookie = ctx[key]; + if (!cookie) return cb('Not found cookie: ' + key); + + my.decodeCookie(cookie, function(e, cookie) { + ctx[key] = cookie; + return cb(); + }); +} + +Chrome.getCookies = function(cb) { + var sqlite3 = require('sqlite3'); + var db = new sqlite3.Database(my.getDBPath()); + db.on('error', cb); + var KEYS = ['csrftoken', 'LEETCODE_SESSION']; + + db.serialize(function() { + var cookies = {}; + var sql = 'select name, encrypted_value from cookies where host_key like "%leetcode.com"'; + db.each(sql, function(e, x) { + if (e) return cb(e); + if (KEYS.indexOf(x.name) < 0) return; + cookies[x.name] = x.encrypted_value; + }); + + db.close(function() { + my.getPassword(function(password) { + my.password = password; + var q = new Queue(KEYS, cookies, doDecode); + q.run(null, cb); + }); + }); + }); +}; + +plugin.signin = function(user, cb) { + log.debug('running cookie.chrome.signin'); + log.debug('try to copy leetcode cookies from chrome ...'); + + my.profile = plugin.config.profile || 'Default'; + my.getCookies(function(e, cookies) { + if (e) { + log.error(`Failed to copy cookies from profile "${my.profile}"`); + log.error(e); + return plugin.next.signin(user, cb); + } + + log.debug('Successfully copied leetcode cookies!'); + user.sessionId = cookies.LEETCODE_SESSION; + user.sessionCSRF = cookies.csrftoken; + session.saveUser(user); + return cb(null, user); + }); +}; + +plugin.login = function(user, cb) { + log.debug('running cookie.chrome.login'); + plugin.signin(user, function(e, user) { + if (e) return cb(e); + plugin.getUser(user, cb); + }); +}; + +module.exports = plugin; diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index 24331ec6..61c55df8 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -213,7 +213,8 @@ function runCode(opts, problem, cb) { opts.json = false; opts.body = null; - return cb(null, body); + const reRun2 = _.partial(cb, null, body); + return setTimeout(reRun2, opts._delay * 250); }); } @@ -229,8 +230,12 @@ function verifyResult(task, queue, cb) { if (e) return cb(e); let result = JSON.parse(body); + console.log(task); + console.log(result); if (result.state === 'SUCCESS') { + console.log('DONE HERE') result = formatResult(result); + console.log(result) _.extendOwn(result, task); queue.ctx.results.push(result); } else { @@ -287,9 +292,10 @@ plugin.testProblem = function(problem, cb) { if (e) return cb(e); const tasks = [ - {type: 'Actual', id: task.interpret_id}, - {type: 'Expected', id: task.interpret_expected_id} + {type: 'Actual', id: task.interpret_id} ]; + if (task.interpret_expected_id) + tasks.push({type: 'Expected', id: task.interpret_expected_id}); const q = new Queue(tasks, {opts: opts, results: []}, verifyResult); q.run(null, function(e, ctx) { return cb(e, ctx.results); From 2858460000c104fd248eaab73f7269a37d047403 Mon Sep 17 00:00:00 2001 From: Robert Dyer Date: Sat, 13 Jul 2024 17:40:15 -0500 Subject: [PATCH 2/6] handle internal failures --- lib/plugins/leetcode.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index 61c55df8..d2134dc5 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -238,6 +238,11 @@ function verifyResult(task, queue, cb) { console.log(result) _.extendOwn(result, task); queue.ctx.results.push(result); + } else if (result.state === 'FAILURE') { + console.log('FAILED') + console.log(result) + _.extendOwn(result, task); + queue.ctx.results.push(result); } else { queue.addTask(task); } From 1ad457cae72a138f4fc7f093ee0360df88dd21f3 Mon Sep 17 00:00:00 2001 From: Robert Dyer Date: Sat, 13 Jul 2024 17:40:46 -0500 Subject: [PATCH 3/6] have submit/test take metadata --- lib/commands/submit.js | 18 +++++++++++++----- lib/commands/test.js | 18 +++++++++++++----- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/lib/commands/submit.js b/lib/commands/submit.js index 56f5ed04..8b7b891f 100644 --- a/lib/commands/submit.js +++ b/lib/commands/submit.js @@ -9,11 +9,21 @@ var core = require('../core'); var session = require('../session'); const cmd = { - command: 'submit ', + command: 'submit ', aliases: ['push', 'commit'], desc: 'Submit code', builder: function(yargs) { return yargs + .positional('id', { + type: 'number', + default: '', + describe: 'Problem identifier number' + }) + .positional('lang', { + type: 'string', + default: '', + describe: 'Programming language' + }) .positional('filename', { type: 'string', describe: 'Code file to submit', @@ -46,13 +56,11 @@ cmd.handler = function(argv) { if (!file.exist(argv.filename)) return log.fatal('File ' + argv.filename + ' not exist!'); - const meta = file.meta(argv.filename); - - core.getProblem(meta.id, function(e, problem) { + core.getProblem(argv.id, function(e, problem) { if (e) return log.fail(e); problem.file = argv.filename; - problem.lang = meta.lang; + problem.lang = argv.lang; core.submitProblem(problem, function(e, results) { if (e) return log.fail(e); diff --git a/lib/commands/test.js b/lib/commands/test.js index 21c4a4eb..879c6ea3 100644 --- a/lib/commands/test.js +++ b/lib/commands/test.js @@ -9,7 +9,7 @@ var core = require('../core'); var session = require('../session'); const cmd = { - command: 'test ', + command: 'test ', aliases: ['run'], desc: 'Test code', builder: function(yargs) { @@ -26,6 +26,16 @@ const cmd = { default: '', describe: 'Provide test case' }) + .positional('id', { + type: 'number', + default: '', + describe: 'Problem identifier number' + }) + .positional('lang', { + type: 'string', + default: '', + describe: 'Programming language' + }) .positional('filename', { type: 'string', default: '', @@ -56,9 +66,7 @@ function runTest(argv) { if (!file.exist(argv.filename)) return log.fatal('File ' + argv.filename + ' not exist!'); - const meta = file.meta(argv.filename); - - core.getProblem(meta.id, function(e, problem) { + core.getProblem(argv.id, function(e, problem) { if (e) return log.fail(e); if (!problem.testable) @@ -71,7 +79,7 @@ function runTest(argv) { return log.fail('missing testcase?'); problem.file = argv.filename; - problem.lang = meta.lang; + problem.lang = argv.lang; log.info('\nInput data:'); log.info(problem.testcase); From 43451276211f870a049b6dfb2860e9691c331ad8 Mon Sep 17 00:00:00 2001 From: Robert Dyer Date: Thu, 18 Jul 2024 23:21:44 -0500 Subject: [PATCH 4/6] better output --- lib/commands/submit.js | 20 ++++++++++---------- lib/plugins/leetcode.js | 10 +++++----- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/commands/submit.js b/lib/commands/submit.js index 8b7b891f..cd6672ae 100644 --- a/lib/commands/submit.js +++ b/lib/commands/submit.js @@ -67,9 +67,9 @@ cmd.handler = function(argv) { const result = results[0]; - printResult(result, 'state'); - printLine(result, '%d/%d cases passed (%s)', - result.passed, result.total, result.runtime); + //printResult(result, 'state'); + //printLine(result, '%d/%d cases passed (%s)', + // result.passed, result.total, result.runtime); if (result.ok) { session.updateStat('ac', 1); @@ -88,15 +88,15 @@ cmd.handler = function(argv) { ratio += parseFloat(score[1]); } - printLine(result, 'Your runtime beats %d %% of %s submissions', - ratio.toFixed(2), lang); + //printLine(result, 'Your runtime beats %d %% of %s submissions', + // ratio.toFixed(2), lang); }); } else { - printResult(result, 'error'); - printResult(result, 'testcase'); - printResult(result, 'answer'); - printResult(result, 'expected_answer'); - printResult(result, 'stdout'); + //printResult(result, 'error'); + //printResult(result, 'testcase'); + //printResult(result, 'answer'); + //printResult(result, 'expected_answer'); + //printResult(result, 'stdout'); } // update this problem status in local cache diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index d2134dc5..a51de5d9 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -230,20 +230,20 @@ function verifyResult(task, queue, cb) { if (e) return cb(e); let result = JSON.parse(body); - console.log(task); - console.log(result); + console.error(task); if (result.state === 'SUCCESS') { - console.log('DONE HERE') - result = formatResult(result); + console.error('DONE HERE') console.log(result) + result = formatResult(result); _.extendOwn(result, task); queue.ctx.results.push(result); } else if (result.state === 'FAILURE') { - console.log('FAILED') + console.error('FAILED') console.log(result) _.extendOwn(result, task); queue.ctx.results.push(result); } else { + console.error(result); queue.addTask(task); } return cb(); From 215905d791948150293ef98988287d0053406507 Mon Sep 17 00:00:00 2001 From: Robert Dyer Date: Thu, 18 Jul 2024 23:46:01 -0500 Subject: [PATCH 5/6] output in proper JSON --- lib/plugins/leetcode.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins/leetcode.js b/lib/plugins/leetcode.js index a51de5d9..c928b196 100644 --- a/lib/plugins/leetcode.js +++ b/lib/plugins/leetcode.js @@ -233,13 +233,13 @@ function verifyResult(task, queue, cb) { console.error(task); if (result.state === 'SUCCESS') { console.error('DONE HERE') - console.log(result) + console.log(JSON.stringify(result)) result = formatResult(result); _.extendOwn(result, task); queue.ctx.results.push(result); } else if (result.state === 'FAILURE') { console.error('FAILED') - console.log(result) + console.log(JSON.stringify(result)) _.extendOwn(result, task); queue.ctx.results.push(result); } else { From fcfadc162c8668d4573381551454ea0a2e158111 Mon Sep 17 00:00:00 2001 From: Robert Dyer Date: Mon, 4 Nov 2024 14:43:25 -0600 Subject: [PATCH 6/6] fix issue with cookies on newer Chrome --- lib/plugins/cookie.chrome.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/plugins/cookie.chrome.js b/lib/plugins/cookie.chrome.js index e2d42780..bf548a5f 100644 --- a/lib/plugins/cookie.chrome.js +++ b/lib/plugins/cookie.chrome.js @@ -113,7 +113,13 @@ Chrome.decodeCookie = function(cookie, cb) { var padding = buf[buf.length - 1]; if (padding) buf = buf.slice(0, buf.length - padding); - return cb(null, buf.toString('utf8')); + var result = buf.toString('utf8'); + if (result.match(/[^a-zA-Z0-9.-_]+/g)) { + buf = buf.slice(32, buf.length); + result = buf.toString('utf8'); + } + + return cb(null, result); }); };