├── lctool ├── __init__.py ├── run.py └── func.py ├── setup.py └── README.md /lctool/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """ 2 | leetcode commandline tool 3 | """ 4 | from setuptools import setup 5 | import os 6 | 7 | NAME = "lctool" 8 | MAJOR = 0 9 | MINOR = 1 10 | MICRO = 0 11 | ISRELEASED = False 12 | VERSION = '%d.%d.%d' % (MAJOR, MINOR, MICRO) 13 | 14 | if not ISRELEASED: 15 | VERSION += '.dev' 16 | 17 | 18 | def write_version_py(filename=NAME+'/version.py'): 19 | if os.path.exists(filename): 20 | os.remove(filename) 21 | cnt = """\ 22 | # THIS FILE IS AUTOMATICALLY GENERATED BY SETUP.PY 23 | version = '%(version)s' 24 | release = %(isrelease)s 25 | """ 26 | a = open(filename, 'w') 27 | try: 28 | a.write(cnt % {'version': VERSION, 29 | 'isrelease': str(ISRELEASED)}) 30 | finally: 31 | a.close() 32 | 33 | write_version_py() 34 | 35 | setup( 36 | name=NAME, 37 | version=VERSION, 38 | author="Hao Zhang", 39 | description=("commandline tool for leetcode"), 40 | packages=['lctool'], 41 | scripts=[], 42 | install_requires=[ 43 | 'requests', 44 | 'BeautifulSoup', 45 | ], 46 | entry_points={ 47 | 'console_scripts': [ 48 | 'lc-get = lctool.run: lcget', 49 | 'lc-submit = lctool.run: submit', 50 | ] 51 | }, 52 | classifiers=[ 53 | "Topic :: Utilities", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # The command line tool for Leetcode 2 | ------------------------------------ 3 | 4 | ## Introduction 5 | --------------- 6 | It's a tool to get all problems and organize the source codes with problem contents 7 | into folders according to tags. You can also submit it using command line. 8 | 9 | ## Installation 10 | --------------- 11 | Requirements: 12 | 1. Anaconda (recommended, required in most raw linux distros) or other python environments 13 | 2. pip install BeautifulSoup 14 | 15 | Install: 16 | python setup.py install 17 | 18 | ## Usage 19 | -------- 20 | Use freely all the functions and/or two easy commands (lc-get and lc-submit). 21 | * Please input your own username and password in func.py if you want to use submit functions. 22 | 23 | * Please change the language in func.py's get_problem_source method for another language. For example, if you want to download java problems change to this: "def get_problem_source(self, problem, language='Java'):". 24 | > Available languages are 'C++', 'Java', 'Python', 'C', 'C#', 'JavaScript', 'Ruby', 'Swift', 'Go'. (Case sensitive) 25 | 26 | 27 | ### New lc-get command with --overwrite argument: 28 | * lc-get command will skip the process of downloading and overriding specific file content if original file exists. 29 | 30 | * Newest problems can be added without losing solutions for old ones. 31 | 32 | * It can help with the bad network connection. (For example: if there are HTTP error and therefore unable to dowland the whole repo of problems from leetcode, this command will resume from break-points by default) 33 | 34 | * Whole problem sets can be updated by adding --overwrite argument. 35 | -------------------------------------------------------------------------------- /lctool/run.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | from lctool.func import lctool 4 | 5 | def lcget(): 6 | parser = argparse.ArgumentParser() 7 | parser.add_argument("-p", "--path", help="Directory for lc files", 8 | type=str) 9 | parser.add_argument("--overwrite", help="Overwrite existed file and code", action="store_true") 10 | 11 | args = parser.parse_args() 12 | path = args.path 13 | overwriteflag = args.overwrite 14 | lc = lctool() 15 | tag_list = lc.get_tag_list() 16 | suffix = {'cpp': 'cpp', 'python': 'py', 'c': 'c', 'csharp': 'cs', 'java': 'java', 17 | 'javascript': 'js', 'ruby': 'rb', 'swift': 'swift', 'golang': 'go'} 18 | comments = {'cpp': ['/*', '*/'], 'python': ['"""', '"""'], 'c': ['/*', '*/'], 19 | 'csharp': ['/*', '*/'], 'javascript': ['/*', '*/'], 20 | 'ruby': ['=begin', '=end'], 'swift': ['/*', '*/'], 21 | 'golang': ['/*', '*/'], 'java': ['/*', '*/']} 22 | if not path: 23 | path = '.' 24 | for tag in tag_list: 25 | dirpath = path + '/' + tag 26 | if not os.path.exists(dirpath): 27 | os.makedirs(dirpath) 28 | notefile = dirpath + ('/%s_notes.txt' % tag) 29 | problem_list = lc.get_problem_list(tag=tag) 30 | with open(notefile, 'w') as nf: 31 | for problem in problem_list: 32 | print 'getting problem: ', tag, problem 33 | filepath = dirpath + '/' + problem 34 | content = lc.get_problem(problem) 35 | if not content: 36 | continue 37 | try: 38 | source, _, lang = lc.get_problem_source(problem) 39 | except: 40 | continue 41 | lan_suffix = suffix[lang] 42 | filepath += '.' + lan_suffix 43 | if os.path.exists(filepath) and not overwriteflag: 44 | print 'problem exists: ', tag, problem 45 | continue 46 | with open(filepath, 'w') as f: 47 | f.write("%s\n" % comments[lang][0]) 48 | f.write(content.encode('utf-8')) 49 | f.write("%s\n" % comments[lang][1]) 50 | f.write('\n\n') 51 | f.write(source.encode('utf-8')) 52 | nf.write('*' * 30 + problem + '\n') 53 | nf.write(content.encode('utf-8')) 54 | nf.write('\n') 55 | 56 | def submit(): 57 | parser = argparse.ArgumentParser() 58 | parser.add_argument("-p", "--problem_path", help="lc file for submission", 59 | type=str, required=True) 60 | args = parser.parse_args() 61 | problem_path = args.problem_path 62 | lc = lctool() 63 | res = lc.submit_problem(problem_path) 64 | for info in res: 65 | print "%s: %s" % (info, res[info]) 66 | -------------------------------------------------------------------------------- /lctool/func.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import urllib 4 | import requests 5 | import os.path 6 | import textwrap 7 | import time 8 | import shutil 9 | from collections import defaultdict 10 | from BeautifulSoup import BeautifulSoup 11 | 12 | class lctool: 13 | def __init__(self): 14 | self.baseurl = 'https://leetcode.com/' 15 | self.loginurl = 'https://leetcode.com/accounts/login/' 16 | self.basepath = '.' 17 | self.username = '' 18 | self.password = '' 19 | 20 | def get_tag_list(self): 21 | url = self.baseurl + 'problemset/algorithms/' 22 | f = urllib.urlopen(url) 23 | soup = BeautifulSoup(f) 24 | taglistinfo = soup.findAll('a', href=re.compile('^/tag/.*')) 25 | taglist = [] 26 | for info in taglistinfo: 27 | tmp = info.attrMap['href'].split('/') 28 | taglist.append(tmp[-2]) 29 | return taglist 30 | 31 | def get_problem_list(self, tag=None): 32 | if not tag: 33 | return 34 | url = self.baseurl + '/tag/' + tag 35 | f = urllib.urlopen(url).read() 36 | pattern = re.compile('.*/problems/.*') 37 | found = re.findall(pattern, f) 38 | problem_list = [tt.split('/problems/')[-1].split(u'/')[0] for tt in found if "Pick" not in tt] 39 | return list(set(problem_list)) 40 | 41 | def get_problem(self, problem): 42 | url = self.baseurl + '/problems/' + problem 43 | f = urllib.urlopen(url) 44 | soup = BeautifulSoup(f) 45 | try: 46 | mydiv = soup.findAll("div", { "class" : "question-content" }).pop() 47 | except: 48 | return None 49 | newlines = re.compile(r'[\r\n|\r|\n]\s+') 50 | txt = mydiv.getText(' ') 51 | txt = newlines.sub('\n', txt).split('\n') 52 | res = '' 53 | for tx in txt: 54 | res += '\n'.join(textwrap.wrap(tx, 80)) 55 | res += '\n' 56 | return res 57 | 58 | def get_problem_source(self, problem, language='C++'): 59 | url = self.baseurl + '/problems/' + problem 60 | f = urllib.urlopen(url) 61 | soup = BeautifulSoup(f) 62 | mydivs = soup.findAll("div", { "class" : "container" }) 63 | codes = [] 64 | for div in mydivs: 65 | if 'ng-init' in div.attrMap: 66 | codes = div.attrMap['ng-init'] 67 | qid = int(codes.split('[{')[1].split('},],')[1].split(',')[1]) 68 | codesj = ("{%s}" % codes.split('[{')[1].split('},],')[0]).split('},{') 69 | res = '' 70 | lang = '' 71 | for cj in codesj: 72 | if not cj.endswith('}'): 73 | cj += '}' 74 | if not cj.startswith('{'): 75 | cj = '{' + cj; 76 | js = str(cj).replace("'", "\"") 77 | codeinfo = defaultdict(str) 78 | try: 79 | codeinfo = json.loads(js) 80 | except: 81 | pass 82 | if codeinfo['text'] == language: 83 | res = codeinfo[u'defaultCode'] 84 | lang = codeinfo[u'value'] 85 | break 86 | return res, qid, lang 87 | 88 | def submit_problem(self, problem_path): 89 | abspath = os.path.abspath(problem_path) 90 | problem = abspath.split('/')[-1].split('.')[0] 91 | url = self.baseurl + '/problems/' + problem 92 | client = requests.session() 93 | tmp = client.get(self.loginurl) 94 | payloadl = {'csrfmiddlewaretoken':client.cookies['csrftoken'], 95 | 'login': self.username, 'password': self.password} 96 | midres = client.post(self.loginurl, data = payloadl, headers = dict(referer=self.loginurl)) 97 | url_submit = url + '/submit' 98 | payload = {} 99 | _, qid, lang = self.get_problem_source(problem) 100 | payload['question_id'] = qid 101 | payload['lang'] = lang 102 | payload['judge_type'] = 'large' 103 | payload['typed_code'] = 'dummy' 104 | with open(abspath) as f: 105 | payload['typed_code'] = f.read() 106 | tmp = client.get(url_submit) 107 | cookie_str = '' 108 | for c in client.cookies.keys(): 109 | cookie_str += c + '=' + client.cookies[c] + ';' 110 | headers = {'X-CSRFToken': client.cookies['csrftoken'], 'Referer': url, 'Cookie': cookie_str} 111 | midres1 = client.post(url_submit, data = json.dumps(payload), headers=headers) 112 | submission = midres1.json() 113 | sid = submission[u'submission_id'] 114 | url_check = 'https://leetcode.com/submissions/detail/' + str(sid) + '/check' 115 | res = {u'state': u'PENDING'} 116 | while (res[u'state'] != u'SUCCESS'): 117 | res = client.get(url_check, headers = headers) 118 | time.sleep(1) 119 | res = res.json() 120 | if res[u'status_runtime'] != 'N/A': 121 | tmp = abspath.split('/') 122 | dst = '/'.join(tmp[:-1]) + '/' + '-DONE.'.join(tmp[-1].split('.')) 123 | shutil.move(abspath, dst) 124 | return res 125 | --------------------------------------------------------------------------------