Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 229b92c

Browse files
author
Haokun Lin
committed
Add export to csv feature to list command
1 parent 5245886 commit 229b92c

14 files changed

+9395
-2
lines changed

lib/commands/list.js

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@ var chalk = require('../chalk');
66
var icon = require('../icon');
77
var log = require('../log');
88
var core = require('../core');
9+
var file = require('../file');
910
var session = require('../session');
11+
var path = require('path');
12+
const createCsvWriter = require('csv-writer').createObjectCsvWriter;
1013

1114
const cmd = {
1215
command: 'list [keyword]',
@@ -21,6 +24,18 @@ const cmd = {
2124
default: false,
2225
describe: 'Show statistics of listed questions'
2326
})
27+
.option('c', {
28+
alias: 'csv',
29+
type: 'boolean',
30+
default: false,
31+
describe: 'Output csv file of problem list'
32+
})
33+
.option('o', {
34+
alias: 'output',
35+
type: 'string',
36+
default: path.join(file.homeDir(), 'out.csv'),
37+
describe: 'Target directory for output file'
38+
})
2439
.option('t', core.filters.tag)
2540
.option('x', {
2641
alias: 'extra',
@@ -55,17 +70,39 @@ cmd.handler = function(argv) {
5570
}
5671
problems = problems.filter(x => x.name.toLowerCase().includes(word));
5772
}
58-
73+
const csvWriter = createCsvWriter({
74+
path: argv.output,
75+
header: [
76+
{id: 'id', title: 'id'},
77+
{id: 'name', title: 'name'},
78+
{id: 'level', title: 'level'},
79+
{id: 'tags', title: 'tags'},
80+
{id: 'companies', title: 'companies'},
81+
{id: 'percent', title: 'percent'},
82+
{id: 'category', title: 'category'},
83+
]
84+
});
5985
const stat = {};
6086
for (let x of ['locked', 'starred', 'ac', 'notac', 'None', 'Easy', 'Medium', 'Hard']) stat[x] = 0;
6187

6288
problems = _.sortBy(problems, x => -x.fid);
89+
var data = [];
6390
for (let problem of problems) {
6491
stat[problem.level] = (stat[problem.level] || 0) + 1;
6592
stat[problem.state] = (stat[problem.state] || 0) + 1;
6693
if (problem.locked) ++stat.locked;
6794
if (problem.starred) ++stat.starred;
6895

96+
if(argv.csv){
97+
data.push( {id:problem.fid,
98+
name: '=HYPERLINK("'+problem.link+'","'+problem.name+'")',
99+
level: problem.level,
100+
tags: problem.tags,
101+
companies: problem.companies,
102+
percent: problem.percent.toFixed(2),
103+
category: problem.category
104+
});
105+
}
69106
log.printf('%s %s %s [%=4s] %-60s %-6s (%s %%)',
70107
(problem.starred ? chalk.yellow(icon.like) : icon.empty),
71108
(problem.locked ? chalk.red(icon.lock) : icon.nolock),
@@ -96,6 +133,11 @@ cmd.handler = function(argv) {
96133
}
97134
}
98135

136+
if(argv.csv){
137+
csvWriter
138+
.writeRecords(data)
139+
.then(()=> console.log('The CSV file was written successfully, output path: '+argv.output));
140+
}
99141
if (argv.stat) {
100142
log.info();
101143
log.printf(' Listed: %-9s Locked: %-9s Starred: %-9s', problems.length, stat.locked, stat.starred);

lib/commands/update.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
3+
var core = require('../core');
4+
5+
6+
const cmd = {
7+
command: 'update',
8+
aliases: ['update'],
9+
desc: 'Update companies and tags for problems',
10+
builder: function(yargs) {
11+
return yargs
12+
.example(chalk.yellow('leetcode update'), 'Update companies and tags for all problems')
13+
}
14+
};
15+
16+
cmd.handler = function() {
17+
session.argv = argv;
18+
core.updateData();
19+
};
20+
21+
module.exports = cmd;

lib/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ const DEFAULT_CONFIG = {
3535
graphql: 'https://leetcode.com/graphql',
3636
login: 'https://leetcode.com/accounts/login/',
3737
problems: 'https://leetcode.com/api/problems/$category/',
38+
tags: 'https://leetcode.com/problems/api/tags/',
3839
problem: 'https://leetcode.com/problems/$slug/description/',
3940
test: 'https://leetcode.com/problems/$slug/interpret_solution/',
4041
session: 'https://leetcode.com/session/',

lib/plugins/company.js

Lines changed: 2851 additions & 0 deletions
Large diffs are not rendered by default.

lib/plugins/cookie.chrome.js

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
var path = require('path');
2+
3+
var log = require('../log');
4+
var Plugin = require('../plugin');
5+
var Queue = require('../queue');
6+
var session = require('../session');
7+
8+
// [Usage]
9+
//
10+
// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cookie.chrome.md
11+
//
12+
var plugin = new Plugin(13, 'cookie.chrome', '2018.11.18',
13+
'Plugin to reuse Chrome\'s leetcode cookie.',
14+
['ffi:win32', 'keytar:darwin', 'ref:win32', 'ref-struct:win32', 'sqlite3']);
15+
16+
plugin.help = function() {
17+
switch (process.platform) {
18+
case 'darwin':
19+
break;
20+
case 'linux':
21+
log.warn('To complete the install: sudo apt install libsecret-tools');
22+
break;
23+
case 'win32':
24+
break;
25+
}
26+
};
27+
28+
var Chrome = {};
29+
30+
var ChromeMAC = {
31+
getDBPath: function() {
32+
return `${process.env.HOME}/Library/Application Support/Google/Chrome/${this.profile}/Cookies`;
33+
},
34+
iterations: 1003,
35+
getPassword: function(cb) {
36+
var keytar = require('keytar');
37+
keytar.getPassword('Chrome Safe Storage', 'Chrome').then(cb);
38+
}
39+
};
40+
41+
var ChromeLinux = {
42+
getDBPath: function() {
43+
return `${process.env.HOME}/.config/google-chrome/${this.profile}/Cookies`;
44+
},
45+
iterations: 1,
46+
getPassword: function(cb) {
47+
// FIXME: keytar failed to read gnome-keyring on ubuntu??
48+
var cmd = 'secret-tool lookup application chrome';
49+
var password = require('child_process').execSync(cmd).toString();
50+
return cb(password);
51+
}
52+
};
53+
54+
var ChromeWindows = {
55+
getDBPath: function() {
56+
return path.resolve(process.env.APPDATA || '', `../Local/Google/Chrome/User Data/${this.profile}/Cookies`);
57+
},
58+
getPassword: function(cb) { cb(); }
59+
};
60+
61+
Object.setPrototypeOf(ChromeMAC, Chrome);
62+
Object.setPrototypeOf(ChromeLinux, Chrome);
63+
Object.setPrototypeOf(ChromeWindows, Chrome);
64+
65+
Chrome.getInstance = function() {
66+
switch (process.platform) {
67+
case 'darwin': return ChromeMAC;
68+
case 'linux': return ChromeLinux;
69+
case 'win32': return ChromeWindows;
70+
}
71+
};
72+
var my = Chrome.getInstance();
73+
74+
ChromeWindows.decodeCookie = function(cookie, cb) {
75+
var ref = require('ref');
76+
var ffi = require('ffi');
77+
var Struct = require('ref-struct');
78+
79+
var DATA_BLOB = Struct({
80+
cbData: ref.types.uint32,
81+
pbData: ref.refType(ref.types.byte)
82+
});
83+
var PDATA_BLOB = new ref.refType(DATA_BLOB);
84+
var Crypto = new ffi.Library('Crypt32', {
85+
'CryptUnprotectData': ['bool', [PDATA_BLOB, 'string', 'string', 'void *', 'string', 'int', PDATA_BLOB]]
86+
});
87+
88+
var inBlob = new DATA_BLOB();
89+
inBlob.pbData = cookie;
90+
inBlob.cbData = cookie.length;
91+
var outBlob = ref.alloc(DATA_BLOB);
92+
93+
Crypto.CryptUnprotectData(inBlob.ref(), null, null, null, null, 0, outBlob);
94+
var outDeref = outBlob.deref();
95+
var buf = ref.reinterpret(outDeref.pbData, outDeref.cbData, 0);
96+
97+
return cb(null, buf.toString('utf8'));
98+
};
99+
100+
Chrome.decodeCookie = function(cookie, cb) {
101+
var crypto = require('crypto');
102+
crypto.pbkdf2(my.password, 'saltysalt', my.iterations, 16, 'sha1', function(e, key) {
103+
if (e) return cb(e);
104+
105+
var iv = new Buffer(' '.repeat(16));
106+
var decipher = crypto.createDecipheriv('aes-128-cbc', key, iv);
107+
decipher.setAutoPadding(false);
108+
109+
var buf = decipher.update(cookie.slice(3)); // remove prefix "v10" or "v11"
110+
var final = decipher.final();
111+
final.copy(buf, buf.length - 1);
112+
113+
var padding = buf[buf.length - 1];
114+
if (padding) buf = buf.slice(0, buf.length - padding);
115+
116+
return cb(null, buf.toString('utf8'));
117+
});
118+
};
119+
120+
function doDecode(key, queue, cb) {
121+
var ctx = queue.ctx;
122+
var cookie = ctx[key];
123+
if (!cookie) return cb('Not found cookie: ' + key);
124+
125+
my.decodeCookie(cookie, function(e, cookie) {
126+
ctx[key] = cookie;
127+
return cb();
128+
});
129+
}
130+
131+
Chrome.getCookies = function(cb) {
132+
var sqlite3 = require('sqlite3');
133+
var db = new sqlite3.Database(my.getDBPath());
134+
db.on('error', cb);
135+
var KEYS = ['csrftoken', 'LEETCODE_SESSION'];
136+
137+
db.serialize(function() {
138+
var cookies = {};
139+
var sql = 'select name, encrypted_value from cookies where host_key like "%leetcode.com"';
140+
db.each(sql, function(e, x) {
141+
if (e) return cb(e);
142+
if (KEYS.indexOf(x.name) < 0) return;
143+
cookies[x.name] = x.encrypted_value;
144+
});
145+
146+
db.close(function() {
147+
my.getPassword(function(password) {
148+
my.password = password;
149+
var q = new Queue(KEYS, cookies, doDecode);
150+
q.run(null, cb);
151+
});
152+
});
153+
});
154+
};
155+
156+
plugin.signin = function(user, cb) {
157+
log.debug('running cookie.chrome.signin');
158+
log.debug('try to copy leetcode cookies from chrome ...');
159+
160+
my.profile = plugin.config.profile || 'Default';
161+
my.getCookies(function(e, cookies) {
162+
if (e) {
163+
log.error(`Failed to copy cookies from profile "${my.profile}"`);
164+
log.error(e);
165+
return plugin.next.signin(user, cb);
166+
}
167+
168+
log.debug('Successfully copied leetcode cookies!');
169+
user.sessionId = cookies.LEETCODE_SESSION;
170+
user.sessionCSRF = cookies.csrftoken;
171+
session.saveUser(user);
172+
return cb(null, user);
173+
});
174+
};
175+
176+
plugin.login = function(user, cb) {
177+
log.debug('running cookie.chrome.login');
178+
plugin.signin(user, function(e, user) {
179+
if (e) return cb(e);
180+
plugin.getUser(user, cb);
181+
});
182+
};
183+
184+
module.exports = plugin;

lib/plugins/cookie.firefox.js

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
var log = require('../log');
2+
var Plugin = require('../plugin');
3+
var session = require('../session');
4+
5+
// [Usage]
6+
//
7+
// https://github.com/skygragon/leetcode-cli-plugins/blob/master/docs/cookie.firefox.md
8+
//
9+
var plugin = new Plugin(13, 'cookie.firefox', '2018.11.19',
10+
'Plugin to reuse firefox\'s leetcode cookie.',
11+
['glob', 'sqlite3']);
12+
13+
function getCookieFile(cb) {
14+
var f;
15+
switch (process.platform) {
16+
case 'darwin':
17+
f = process.env.HOME + '/Library/Application Support/Firefox/Profiles/*.default*/cookies.sqlite';
18+
break;
19+
case 'linux':
20+
f = process.env.HOME + '/.mozilla/firefox/*.default*/cookies.sqlite';
21+
break;
22+
case 'win32':
23+
f = (process.env.APPDATA || '') + '/Mozilla/Firefox/Profiles/*.default*/cookies.sqlite';
24+
break;
25+
}
26+
require('glob')(f, {}, cb);
27+
}
28+
29+
function getCookies(cb) {
30+
getCookieFile(function(e, files) {
31+
if (e || files.length === 0) return cb('Not found cookie file!');
32+
33+
var sqlite3 = require('sqlite3');
34+
var db = new sqlite3.Database(files[0]);
35+
var KEYS = ['csrftoken', 'LEETCODE_SESSION'];
36+
37+
db.serialize(function() {
38+
var cookies = {};
39+
var sql = 'select name, value from moz_cookies where host like "%leetcode.com"';
40+
db.each(sql, function(e, x) {
41+
if (e) return cb(e);
42+
if (KEYS.indexOf(x.name) < 0) return;
43+
cookies[x.name] = x.value;
44+
});
45+
46+
db.close(function() {
47+
return cb(null, cookies);
48+
});
49+
});
50+
});
51+
}
52+
53+
plugin.signin = function(user, cb) {
54+
log.debug('running cookie.firefox.signin');
55+
log.debug('try to copy leetcode cookies from firefox ...');
56+
getCookies(function(e, cookies) {
57+
if (e) {
58+
log.error('Failed to copy cookies: ' + e);
59+
return plugin.next.signin(user, cb);
60+
}
61+
62+
if (!cookies.LEETCODE_SESSION || !cookies.csrftoken) {
63+
log.error('Got invalid cookies: ' + JSON.stringify(cookies));
64+
return plugin.next.signin(user, cb);
65+
}
66+
67+
log.debug('Successfully copied leetcode cookies!');
68+
user.sessionId = cookies.LEETCODE_SESSION;
69+
user.sessionCSRF = cookies.csrftoken;
70+
session.saveUser(user);
71+
return cb(null, user);
72+
});
73+
};
74+
75+
plugin.login = function(user, cb) {
76+
log.debug('running cookie.firefox.login');
77+
plugin.signin(user, function(e, user) {
78+
if (e) return cb(e);
79+
plugin.getUser(user, cb);
80+
});
81+
};
82+
83+
module.exports = plugin;

0 commit comments

Comments
 (0)