def do_hijack(self, cmd): """ usage: hijack <stolen_cookie> Set the SID and cookie to the stolen ones. """ brutejudge.cheats.cheating(self) if cmd.strip() == '': return self.do_help('hijack') try: a, b = cmd.strip().split('-') int(a, 16) int(b, 16) except Exception: raise BruteError("Invalid cookie: " + cmd.strip()) if not isinstance(self.url, Ejudge): raise BruteError("Testing system is not ejudge") if isinstance(self.url, Informatics): raise BruteError("informatics.msk.ru is not supported") self.url.urls = { k: v.replace(self.url['sid'], a) for k, v in self.url.items() } self.url.cookie = 'EJSID=' + b if isinstance(self.url, EJFuse): self.url.cookies = (a, b)
def download_file(self, prob_id, filename): code, headers, data = self._cache_get(self.urls['download_file'].format(prob_id=prob_id, filename=filename)) if code == 404: raise BruteError("File not found.") elif code != 200: raise BruteError("Error downloading.") return data
def tasks(self): code, headers, data = self._cache_get(self.urls['summary']) if b'<input type="submit" name="action_35" value="Change!" />' in data: raise BruteError("Password change is required.") if code != 200: raise BruteError("Failed to fetch task list.") data = data.decode('utf-8') #tl = self._task_list(data) ti = self._task_ids(data) #if len(tl) < len(ti): tl.extend([None]*(len(ti)-len(tl))) #else: ti.extend([None]*(len(tl)-len(ti))) return [bjtypes.task_t(i, j, None) for i, j in ti]
def status(self): code, headers, data = self._cache_get(self.urls['summary']) if b'<input type="submit" name="action_35" value="Change!" />' in data: raise BruteError("Password change is required.") if code != 200: raise BruteError("Failed to fetch task list") ths = [i.split('</th>', 1)[0] for i in data.decode('utf-8').split('<th class="b1">')[1:]] w = len(ths) if w == 0: return {} splitted = data.decode('utf-8').split('<td class="b1">')[1:] data = [x.split("</td>")[0] for x in splitted] idx = ths.index('Status') return collections.OrderedDict((a, b if b != ' ' else None) for a, b in zip(data[ths.index('Short name')::w], data[idx::w]))
def do_samples(self, cmd): """ usage: samples [--binary] <submission_id> (<dump_dir> || --input || --output || --correct || --stderr || --checker) Dump sample test cases from a submission. If a flag is specified, dump the corresponding files to standard output. """ if len(cmd.split(' ')) < 2: return self.do_help("samples") cmd, ddir = cmd.split(' ', 1) kwargs = {} if cmd == '--binary': if not has_feature(self.url, self.cookie, 'get_samples', 'binary'): raise BruteError( "This system does not support --binary in samples.") kwargs['binary'] = True if len(ddir.split(' ')) < 2: return self.do_help("samples") cmd, ddir = ddir.split(' ', 1) if not cmd.isnumeric(): raise BruteError("Submission ID must be a number") tests = get_samples(self.url, self.cookie, int(cmd), **kwargs) flags = { '--input': 'Input', '--output': 'Output', '--correct': 'Correct', '--stderr': 'Stderr', '--checker': 'Checker output' } if ddir not in flags: os.makedirs(ddir) out_sep = False for k, v in tests.items(): for what, data in v.items(): if ddir in flags: if what == flags[ddir]: if out_sep: print('#' * 20) else: out_sep = True (sys.stdout.buffer if 'binary' in kwargs else sys.stdout).write(data) (sys.stdout.buffer if 'binary' in kwargs else sys.stdout).flush() continue if what == 'Input': suff = '' elif what == 'Correct': suff = '.a' else: suff = '_' + what.replace(' ', '_') + '.txt' with open(os.path.join(ddir, '%02d' % k + suff), 'wb' if 'binary' in kwargs else 'w') as file: file.write(data)
def compile_error(self, id): code, headers, data = self._cache_get(self.urls['protocol'].format(run_id=id)) if b'<input type="submit" name="action_35" value="Change!" />' in data: raise BruteError("Password change is required.") if code != 200: raise BruteError("Failed to fetch testing protocol.") splitted = data.decode('utf-8').split('<pre>')[1:] ans = [] for i in splitted: i = i.split('</pre>')[0] i = i.split('<') i = i[0] + ''.join(j.split('>', 1)[1] for j in i[1:]) import html ans.append(html.unescape(i)) return '\n'.join(ans)
def _get_submissions(self): with self.cache_lock: if self._gs_cache != None: return self._gs_cache data = self.opener.open(self.base_url + '/my?locale=en') if data.geturl() not in (self.base_url + '/my', self.base_url + '/my?locale=en'): raise BruteError("Failed to fetch submission list") data = data.read().decode('utf-8', 'replace') csrf = self._get_csrf(data) data = data.replace( '<tr class="last-row" data-submission-id="', '<tr data-submission-id="').split('<tr data-submission-id="') subms = [] for i in data[1:]: subm_id = int(i.split('"', 1)[0]) meta = {} data2 = i.split('>', 1)[1].split('</tr>', 1)[0].split('<td') for j in data2[1:]: try: cls = j.split('class="', 1)[1].split('"', 1)[0] except IndexError: cls = None data = j.split('>', 1)[1].split('</td>', 1)[0] meta[cls] = data subms.append((subm_id, meta)) ans = (subms, csrf) with self.cache_lock: if self.caching: self._gs_cache = ans return ans
def do_incat(self, cmd): """ usage: incat [--base64] <task> <filepath> [savepath] Tries to retrieve the specified ASCII file using .incbin directive. The data will be base64-encoded if --base64 is specified. """ brutejudge.cheats.cheating(self) data = shlex.split(cmd) filter = '' if data and data[0] == '--base64': filter = base64_filter del data[0] if len(data) not in (2, 3): return self.do_help('incat') data.append(None) task, filepath, savepath = data[:3] task_list = tasks(self.url, self.cookie) for i in task_list: if i[1] == task: task_id = i[0] break else: raise BruteError("No such task.") if savepath == None: f = sys.stdout else: f = open(savepath, "w") incat(self, task, task_id, filepath, f, filter=filter) if savepath != None: f.close()
def do_astatus(self, cmd): """ usage: astatus <subm_id> Fancy testing progress display """ subm_id = cmd.strip() if not subm_id.isnumeric(): return self.do_help('astatus') chars = '\\|/-' idx = 0 prev = '' subm_id = int(subm_id) while True: try: cur = next(i.status for i in submissions(self.url, self.cookie) if i.id == subm_id) except StopIteration: raise BruteError('No such submission') cur = cur.strip() sys.stderr.write(' ' * len(prev) + '\r') sys.stderr.flush() if still_running(cur): prev = '%%%ds' % len(prev) % (cur + ' ' + chars[idx]) idx = (idx + 1) % 4 # print(prev) sys.stderr.write(prev + '\r') sys.stderr.flush() else: print(cur) break
def get_tailcode(self, subm_id, suf): if submission_status(self.url, self.cookie, subm_id) not in ('Compilation error', 'Compiler failed'): raise BruteError("Submission didn't fail to compile") err = compile_error(self.url, self.cookie, subm_id) if err == None: err = '' err = err.strip() lines = {} it = iter(err.split('\n')) for line in it: if not line[:1].isspace(): l = (line + ':::').split(':') if l[0].endswith(suf) and l[1].isnumeric() and l[2].isnumeric(): lineno = int(l[1]) if lineno not in lines: try: lines[lineno] = next(it).strip() except StopIteration: break if not lines: return '' minno = min(lines) maxno = max(lines) ans = '' for i in range(minno, maxno + 1): ans += lines.get(i, '###### FAILED TO FETCH ######') + '\n' return ans
def __init__(self, url, login, password): Backend.__init__(self) self.locale = 'en' if '#locale=' in url: url, self.locale = url.rsplit('#locale=', 1) if url.startswith('http:'): url = 'https:' + url[5:] if url.find('/contest') == url.find('/contests'): url = '/contest'.join(url.split('/contests', 1)) self.base_url = url self.handle = login self.host = url.split('/')[2] self.opener = OpenerWrapper( urllib.request.build_opener(urllib.request.HTTPCookieProcessor)) csrf = self._get_csrf( self.opener.open('https://%s/enter?back=%%2F' % self.host).read().decode('utf-8', 'replace')) ln = self.opener.open( 'https://%s/enter?back=%%2F' % self.host, urllib.parse.urlencode({ 'csrf_token': csrf, 'action': 'enter', 'ftaa': '', 'bfaa': '', 'handleOrEmail': login, 'password': password }).encode('ascii')) if ln.geturl() != 'https://%s/' % self.host: raise BruteError("Login failed.") self._gs_cache = None self._st_cache = None self._subms_cache = {}
def _get_samples(self, err): if err == None: err = '' err = err.strip() if "====== Test #" not in err: raise BruteError("No test cases available") err = err[err.find("====== Test #"):] lines = iter(err.split('\n')) tests = {} curr = None for line in lines: if line.startswith("====== Test #"): num = int(line[13:-7]) curr = tests[num] = {} elif line.startswith('--- '): line = line[4:-4] if ': size ' not in line: continue what, size = line.split(': size ') size = int(size) + 1 data = '' while len(data) < size: try: data += '\n' + next(lines) except StopIteration: break data = data[1:] curr[what] = data return tests
def contest_info(self): code, headers, data = self._cache_get(self.urls['contest_info']) if b'<input type="submit" name="action_35" value="Change!" />' in data: raise BruteError("Password change is required.") data = data.decode('utf-8') try: pbs = '\n'.join(html.unescape(i.split('</b></p>', 1)[0]) for i in data.split('<p><b>')[1:]) except IndexError: pbs = '' datas = {} for i in data.split('<tr><td class="b0">')[1:]: i = i.split('</td></tr>', 1)[0] try: key, value = i.split('<td class="b0">') except IndexError: pass else: datas[html.unescape(key.split('</td>', 1)[0])] = html.unescape(value) data1 = {} for k1, k2 in (('server_time', 'Server time:'), ('contest_start', 'Contest start time'), ('contest_duration', 'Duration:')): if k2 not in datas: continue if datas[k2] == 'Unlimited': data1[k1] = math.inf continue if ' ' in datas[k2]: date, s_time = datas[k2].split(' ') year, month, day = map(int, date.split('/')) hour, minute, second = map(int, s_time.split(':')) data1[k1] = time.mktime((year, month, day, hour, minute, second, -1, -1, -1)) else: data1[k1] = 0 for i in map(int, datas[k2].split(':')): data1[k1] = data1[k1] * 60 + i if 'contest_start' in data1 and 'contest_duration' in data1: data1['contest_end'] = data1['contest_start'] + data1['contest_duration'] if 'contest_start' in data1 and 'server_time' in data1: data1['contest_time'] = data1['server_time'] - data1['contest_start'] return (pbs, datas, data1)
def do_info(self, cmd): """ usage: info [-t] [task] Show testing information and problem statement for a task, or for the whole contest if task is not specified. If -t is specified, attempt to decode embedded TeX expressions into human-readable form. """ cmd = cmd.strip() tex = False if cmd[:2] == '-t' and (len(cmd) == 2 or cmd[2:3].isspace()): tex = True cmd = cmd[2:].strip() if cmd == '--help': return self.do_help('info') if cmd: try: task_id = next(i.id for i in tasks(self.url, self.cookie) if i.short_name == cmd) except StopIteration: raise BruteError("No such task.") a, b = problem_info(self.url, self.cookie, task_id) else: b, a, j = contest_info(self.url, self.cookie) print(j) for k, v in a.items(): print(k+': '+v) print() print(untex(b) if tex else b)
def submission_protocol(self, id): code, headers, data = self._cache_get(self.urls['protocol'].format(run_id=id)) if b'<input type="submit" name="action_35" value="Change!" />' in data: raise BruteError("Password change is required.") if code != 200: raise BruteError("Failed to fetch testing protocol.") w = data.count(b'<th ') if w == 0: return [] splitted = data.decode('utf-8').split('<td class="b1">')[1:] data = [x.split("</td>")[0] for x in splitted] statuses = [i[:-7].split('>')[-1] for i in data[1::w]] tls = [] for i in map(html.unescape, data[2::w]): if i.startswith('>'): i = i[1:] tls.append(float(i)) assert len(statuses) == len(tls) return [bjtypes.test_t(i, {'time_usage': j}) for i, j in zip(statuses, tls)]
def scores(self, *, total=None): code, headers, data = self._cache_get(self.urls['summary']) if b'<input type="submit" name="action_35" value="Change!" />' in data: raise BruteError("Password change is required.") if code != 200: raise BruteError("Failed to fetch task list") data0 = data.decode('utf-8') ths = [i.split('</th>', 1)[0] for i in data0.split('<th class="b1">')[1:]] w = len(ths) splitted = data.decode('utf-8').split('<td class="b1">')[1:] data = [x.split("</td>")[0] for x in splitted] if 'Score' not in ths: ans = {} else: ans = collections.OrderedDict(zip(data[ths.index('Short name')::w], [None if x == ' ' else int(x) for x in data[ths.index('Score')::w]])) if total != None and '<p><big>Total score: ' in data0: try: ans[total] = int(data0.split('<p><big>Total score: ', 1)[1].split('</big></p>', 1)[0]) except (ValueError, IndexError): pass return ans
def submission_source(self, id): code, headers, data = self._cache_get(self.urls['source'].format(run_id=id)) rhd = dict(headers) if 'html' in rhd['Content-Type'] and b'<input type="submit" name="action_35" value="Change!" />' in data: raise BruteError("Password change is required.") if code != 200 or 'html' in rhd['Content-Type']: return None return data
def login(url, login, password, **kwds): for i in backend_path: try: f = i.detect(url) except Exception: f = False if f: return (i(url, login, password, **kwds), True) raise BruteError("Unknown CMS")
def clar_list(self): code, headers, data = self._cache_get(self.urls['clars']) if b'<input type="submit" name="action_35" value="Change!" />' in data: raise BruteError("Password change is required.") ths = [i.split('</th>', 1)[0] for i in data.decode('utf-8').split('<th class="b1">')[1:]] w = len(ths) splitted = data.decode('utf-8').split('<td class="b1">')[1:] data = [x.split("</td>")[0] for x in splitted] return [bjtypes.clar_t(i, j) for i, j in zip(map(int, data[ths.index('Clar ID')::w]), map(html.unescape, data[ths.index('Subject')::w]))]
def do_brute(self, cmd): """ usage: brute <task> <reader> [dump_path] [max_count] Tries to retrieve the test cases using binary search. """ brutejudge.cheats.cheating(self) sp = cmd.split() if len(sp) not in range(2, 5): return self.do_help('brute') tasks = task_list(self.url, self.cookie) try: task_id = tasks.index(sp[0]) except ValueError: raise BruteError("No such task.") try: with open(sp[1]) as file: code = file.read() except FileNotFoundError: raise BruteError("File not found.") srch = Searcher(self.url, self.cookie, task_id, input_file=getattr(self, 'input_file', None), output_file=getattr(self, 'output_file', None)) max_tests = -1 if len(sp) == 4: try: max_tests = int(sp[3]) except ValueError: raise BruteError("max_tests must be a number") srch.execute(code, max_tests) if len(sp) == 2 or sp[2] == '-': from sys import stdout as file do_close = False else: try: file = open(sp[2], "a") except IOError as e: raise BruteError("Error creating output file: " + str(e)) do_close = True for i, t in enumerate(srch.tests): print("---------- test #", i, sep='', file=file) print(t, file=file)
def get_lang_id(self, lang_name, task_id): lang_id = [ i for i, j, k in compiler_list(self.url, self.cookie, task_id) if j == lang_name ] try: lang_id, = lang_id except ValueError: raise BruteError("Unknown language: " + lang_name) return lang_id
def do_control(self, cmd): """ usage: do_control <action> [args] `action` can be one of: start-virtual Start virtual contest. stop-virtual Stop virtual contest. """ cmd = cmd.strip() if cmd == 'start-virtual': if not do_action(self.url, self.cookie): raise BruteError("Failed to start virtual contest") elif cmd == 'stop-virtual': if not do_action(self.url, self.cookie): raise BruteError("Failed to stop virtual contest") else: return self.do_help('control')
def scores(self): tli = self.tasks() ans = collections.OrderedDict() for task in tli: code, headers, data = self._cache_get(self.url+'?SID=%s&EJSID=%s&action=problem-status-json&problem=%%d&json=1'%self.cookies%task.id, False) data = mb_problem_status(data) if code != 200 or not data or not data['ok']: raise BruteError("Failed to fetch task list.") try: ans[task.short_name] = data['result']['problem_status']['best_score'] except KeyError: ans[task.short_name] = None return ans
def scoreboard(self): code, headers, data = self._cache_get(self.urls['standings']) if b'<input type="submit" name="action_35" value="Change!" />' in data: raise BruteError("Password change is required.") if code != 200: raise BruteError("Failed to fetch scoreboard.") teams = data.decode('utf-8').split('<td class="st_team">')[1:] probs = data.decode('utf-8').split('<td class="st_prob')[1:] naux = 0 if not teams and b'<table border=1 cellspacing=1 celpadding=3>\n <tr >\n <th align=right>Place</th>' in data: probs = sum((i.split('<td class="st_prob') for i in data.decode('utf-8').split('<td align=center')), [])[1:] teams = data.decode('utf-8').split('<td align=left>')[1:] naux = 1 probs = [x.split("</td>", 1)[0] for x in probs] teams = [html.unescape(x.split("</td>", 1)[0]) for x in teams] try: ntasks = len(probs) // len(teams) - naux except ZeroDivisionError: return [] del teams[-3:] del probs[-3*ntasks:] probs = iter(probs) ans = [] for i in teams: ans.append(({'name': i}, [])) for j in range(ntasks): j = next(probs).split('>', 1)[1] if j == ' ': ans[-1][1].append(None) elif j[:1] in ('+', '-'): attempts = int(j[0]+'0'+j[1:]) ans[-1][1].append({'attempts': attempts}) elif j.startswith('<b>') and j.endswith('</b>') and j[3:-4].isnumeric(): score = int(j[3:-4]) attempts = float('inf') ans[-1][1].append({'score': score, 'attempts': attempts}) elif j.isnumeric(): score = int(j) attempts = float('-inf') ans[-1][1].append({'score': score, 'attempts': attempts}) else: assert False, repr(j) for j in range(naux): next(probs) return ans
def submissions(self): code, headers, data = self._cache_get(self.urls['submissions']) if b'<input type="submit" name="action_35" value="Change!" />' in data: raise BruteError("Password change is required.") if code != 200: raise BruteError("Failed to fetch submission list.") ths = [i.split('</th>', 1)[0] for i in data.decode('utf-8').split('<th class="b1">')[1:]] w = len(ths) if w == 0: return [] splitted = data.decode('utf-8').split('<td class="b1">')[1:] data = [x.split("</td>")[0] for x in splitted] run_ids = list(map(lambda x:(int(x[:-1]) if x[-1:] == '#' else int(x)), data[ths.index('Run ID')::w])) task_ids = data[ths.index('Problem')::w] if 'Result' in ths: statuses = data[ths.index('Result')::w] else: statuses = [None]*len(run_ids) if 'Score' in ths: scores = [] for i in data[ths.index('Score')::w]: i = i.strip() if i.startswith('<b>'): i = i[3:].split('</b>', 1)[0] # TODO: report score_ex in submission_stats i = i.strip() if i in ('', 'N/A', ' '): scores.append(None) else: scores.append(int(i)) else: scores = [None]*len(run_ids) if 'Tests passed' in ths: oktests = [] for i in data[ths.index('Tests passed')::w]: i = i.strip() if i.startswith('<b>'): i = i[3:] if i.endswith('</b>'): i = i[:-4] i = i.strip() if i in ('', 'N/A', ' '): oktests.append(None) else: oktests.append(int(i)) else: oktests = [None]*len(run_ids) assert len(run_ids) == len(task_ids) == len(statuses) == len(scores) == len(oktests) return [bjtypes.submission_t(i, j, k, l, m) for i, j, k, l, m in zip(run_ids, task_ids, statuses, scores, oktests)]
def __init__(self, url, login, password): Backend.__init__(self) url0 = url url = url.replace('/new-register?', '/new-client?') contest_id = url.split("contest_id=")[1].split("&")[0] self.contest_id = int(contest_id) base_url = url.split("?")[0] code, headers, data = post(url0.split("?")[0], {'contest_id': contest_id, 'locale_id': 0, 'login': login, 'password': password, 'action_213': ''}) if code != 302: raise BruteError("Login failed.") rhd = dict(headers) base_url = rhd['Location'].split('&')[0] base_url = base_url.replace('/new-register?', '/new-client?') if 'new-client?SID=' in base_url: urls = ej371.get_urls(base_url) elif any(i in base_url for i in ('/user/', '/client/', '/register/', '/register?SID=')): urls = ej373.get_urls(base_url) else: raise BruteError("Unknown ejudge version.") self.urls = urls self.cookie = rhd["Set-Cookie"].split(";")[0] self._get_cache = {}
def compiler_list(self, prob_id): code, headers, data = self._cache_get(self.urls['submission'].format(prob_id=prob_id)) if b'<input type="submit" name="action_35" value="Change!" />' in data: raise BruteError("Password change is required.") data = data.decode('utf-8') if '<input type="hidden" name="lang_id" value="' in data: data = data.split('<input type="hidden" name="lang_id" value="', 1)[1] num_id = int(data.split('"', 1)[0]) lit_id = html.unescape(data.split('</td><td class="b0">', 1)[1].split('</td>', 1)[0]) short, long = lit_id.strip().split(' - ') return [bjtypes.compiler_t(num_id, short, long)] try: data = data.split('<select name="lang_id">', 1)[1].split('</select>', 1)[0] except IndexError: raise BruteError("Failed to fetch language list") data = data.split('<option ')[1:] ans = [] for i in data: a, b = (' '+i).split(' value="', 1)[1].split('"', 1) b = b.split('>', 1)[1].split('</option>', 1)[0] if not a.isnumeric(): continue b, c = html.unescape(b).split(' - ') ans.append(bjtypes.compiler_t(int(a), b.strip(), c.strip())) return ans
def __init__(self, url, login, password): Backend.__init__(self) url, params = url.split('?') url = url.replace('+jjs', '', 1) if url.endswith('/'): url = url[:-1] params = {k: v for k, v in (i.split('=', 1) if '=' in i else (i, None) for i in params.split('&'))} contest_id = params['contest'] if params.get('auth', None) == 'token': self.cookie = 'Bearer '+password elif params.get('auth', None) == 'gettoken': sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) sock.connect('/tmp/jjs-auth-sock') token = b'' while True: chk = sock.recv(1024) token += chk if not chk: break token = token.decode('ascii').strip() if not (token.startswith('===') and token.endswith('===')): raise BruteError('Login failed: failed to get token from jjs-auth-sock') self.cookie = 'Bearer '+token[3:-3] elif params.get('auth', None) == 'guest': self.cookie = 'Bearer Guest' else: code, headers, data = json_req(url + '/auth/simple', {'login': login, 'password': password}) if code != 200: try: msg = 'Login failed: %s'%data['detail'] except Exception: msg = 'Login failed.' raise BruteError(msg) self.cookie = 'Bearer '+data['data'] self.url = url self.params = params self.contest = contest_id self.lsu_cache = {} self._get_cache = {} code, headers, data = json_req(url + '/contests/' + urlescape(self.contest), None, {'Authorization': self.cookie}) if code != 200 or data == None: raise BruteError('Login failed: unknown contest')
def __init__(self, url, login, password): Backend.__init__(self) assert url.startswith('ejfuse:http://') or url.startswith('ejfuse:https://') url = url[7:].replace('/new-register?', '/new-client?').replace('/new-client?', '/client?') contest_id = url.split("contest_id=")[1].split("&")[0] self.contest_id = int(contest_id) base_url = url.split("/client?")[0] code, headers, data = post(base_url+'/register', {'action': 'login-json', 'login': login, 'password': password, 'json': 1}) data = mbjson(data) if code != 200 or not data or not data['ok']: raise BruteError("Login failed.") self.url = base_url+'/register' self.cookies = (data['result']['SID'], data['result']['EJSID']) code, headers, data = post(self.url, {'SID': self.cookies[0], 'EJSID': self.cookies[1], 'action': 'enter-contest-json', 'contest_id': self.contest_id, 'json': 1}) data = mbjson(data) if code != 200 or not data or not data['ok']: raise BruteError("Login failed.") self.url = base_url+'/client' self.cookies = (data['result']['SID'], data['result']['EJSID']) # will fall back to normal ejudge if an unimplemented feature is encountered self.urls = get_urls(base_url+'/new-client?SID='+self.cookies[0]) self.cookie = 'EJSID='+self.cookies[1] self._get_cache = {}
def do_getsource(self, cmd): """ usage: getsource <submission_id> <shell command> Retrieve the source code of a submission. """ id, cmd = (cmd.strip()+' ').split(' ', 1) if not id.isnumeric(): return self.do_help('getsource') src = submission_source(self.url, self.cookie, int(id)) if src == None: raise BruteError('Source code is not available') p = subprocess.Popen('cat '+cmd, stdin=subprocess.PIPE, shell=True) p.stdin.write(src) p.stdin.close() p.wait()