├── .gitignore ├── LICENSE ├── README.md ├── boj.py ├── git.py ├── main.py ├── option.json └── option.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Private Data 2 | option.json 3 | 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | MANIFEST 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | db.sqlite3 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # pyenv 79 | .python-version 80 | 81 | # celery beat schedule file 82 | celerybeat-schedule 83 | 84 | # SageMath parsed files 85 | *.sage.py 86 | 87 | # Environments 88 | .env 89 | .venv 90 | env/ 91 | venv/ 92 | ENV/ 93 | env.bak/ 94 | venv.bak/ 95 | 96 | # Spyder project settings 97 | .spyderproject 98 | .spyproject 99 | 100 | # Rope project settings 101 | .ropeproject 102 | 103 | # mkdocs documentation 104 | /site 105 | 106 | # mypy 107 | .mypy_cache/ 108 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017-2021 Minho Kim 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | BOJ-AutoCommit 2 | ========== 3 | [![](https://d2gd6pc034wcta.cloudfront.net/images/logo.png)](https://www.acmicpc.net) 4 | ---------- 5 | `BOJ-AutoCommit`은 [Baekjoon Online Judge](https://www.acmicpc.net)(이하 BOJ)의 다양한 알고리즘 문제를 풀어서 제출하고 정답을 맞추면 [Github](https://github.com)와 같은 원격저장소에 Source Code를 Push합니다. 일정한 시간마다 BOJ에서 사용자의 ID를 이용하여 정답을 맞춘 문제들을 검색 및 분석하고, Local Repository에 해당 문제 파일이 존재하지 않으면 Source Code를 다운로드 받아 Local Repository에 저장함과 동시에 `Git`을 사용하여 Add, Commit 그리고 Push를 자동으로 하며, `Python3`을 기반으로 만들어졌습니다. 6 | 7 | Installation 8 | ---------- 9 | ``` 10 | $ git clone https://github.com/ISKU/BOJ-AutoCommit 11 | ``` 12 | 13 | **Dependency** 14 | ``` 15 | $ pip3 install requests 16 | $ pip3 install bs4 17 | ``` 18 | - [Git](https://git-scm.com/) 19 | 20 | How to use 21 | ---------- 22 | - 다음과 같이 Tool을 실행하여, 올바른 정보를 입력합니다. 23 | ``` 24 | $ python3 main.py 25 | ``` 26 | 27 | - Tool 실행시 다음과 같이 BOJ의 회원 정보와 GitHub의 회원 정보 및 Repository 이름을 입력하여야 합니다. 28 | 29 | | **Input** | **Description** 30 | |:---------------------|:------------------------------------------------- 31 | | **BOJ id** | BOJ에서 사용하는 ID를 입력합니다. 32 | | **BOJ password** | 로그인을 위해 BOJ의 비밀번호를 입력합니다. 33 | | **GitHub id** | GitHub에서 사용하는 ID를 입력합니다. 34 | | **GitHub password** | 로그인을 위해 GitHub의 비밀번호를 입력합니다. 35 | | **Repository** | Source Code를 Push할 원격저장소의 Repository의 이름을 입력합니다. 36 | 37 | - 이 Tool은 대기하고 있는 시간이 길기 때문에 다음과 같이 Background에서 실행을 권장합니다. 38 | ``` 39 | $ nohup python3 main.py & 40 | ``` 41 | 42 | Default 43 | ---------- 44 | - 문제번호로 Directory를 생성 한 후, 하위에 문제번호를 제목으로 Source Code 파일이 저장됩니다. 45 | - Commit Message는 기본적으로 **"BOJ #문제번호"** 입니다. 46 | - 약 **10분**마다 맞았던 문제를 검색하고 새롭게 맞은 문제가 있으면 원격저장소에 Push합니다. 47 | - 정답을 맞춘 가장 **최근 20개**의 문제에 대해서만 분석하며, 정답을 맞춘 **모든 문제를 다루지는 않습니다.** 48 | - 정답을 맞춘 문제가 여러가지가 있을 경우 가장 **마지막에 제출한 Source Code**를 선택합니다. 49 | 50 | Extension 51 | ---------- 52 | - Tool을 확장하여 자유롭게 자신의 원격저장소를 관리할 수 있습니다. 53 | - option.json에 다음 예제와 같이 원하는 Option을 설정하세요. 54 | 55 | ``` json 56 | { 57 | "commit_message": "[NO]번 [TITLE] 문제풀이", 58 | "source_tree": "Algorithm/BOJ/Src", 59 | "dir_name": "[NO]", 60 | "mkdir": true, 61 | "private": true, 62 | "poll": 1800, 63 | "source_name": "[NO]", 64 | "lang": "Java" 65 | } 66 | ``` 67 | > :bulb: 사용하지 않는 Option은 반드시 지워야 합니다. 68 | 69 | **Key Options:** 70 | 71 | | **Key** | **Description** 72 | |:-------------------|:------------------------------------------------- 73 | | **commit_message** | Commit 내용을 지정합니다. 74 | | **source_tree** | 원하는 위치에 Source를 저장합니다. (시작 Directory는 Repository 이름과 일치하여야 합니다.) 75 | | **dir_name** | Source가 저장 되는 Directory의 이름을 지정합니다. 76 | | **mkdir** | Source가 저장 될 때 Directory를 생성 할 것인지를 결정합니다.(false: dir_name은 무시됩니다.) 77 | | **private** | BOJ에서 Source를 비공개로 설정하면 해당 문제는 무시됩니다. 78 | | **poll** | BOJ의 맞은 문제를 검색하는 주기를 초 단위로 설정합니다. (최소 5분 이상이여야 합니다.) 79 | | **source_name** | Source 파일의 이름을 설정합니다. [NO]를 사용하여 문제번호로 저장하는 것을 추천합니다. 80 | | **lang**           | 해당 언어로 제출한 Source만 원격저장소에 Push합니다. 81 | 82 | > :bulb: [NO]: 내용에 [NO]가 있으면 문제의 번호로 대체됩니다.
83 | > :bulb: [TITLE]: 내용에 [TITLE]이 있으면 문제의 제목으로 대체됩니다. 84 | 85 | Example 86 | ---------- 87 | - https://github.com/ISKU/Algorithm 88 | - 위 Repository는 [BOJ-AutoCommit](https://github.com/ISKU/BOJ-AutoCommit)을 사용하여 Source Code를 관리하고 있으며 사용하고 있는 Option은 다음과 같습니다. 89 | 90 | ``` json 91 | { 92 | "commit_message": "BOJ #[NO]: [TITLE]", 93 | "source_tree": "Algorithm/BOJ", 94 | "private": true, 95 | "poll": 1800, 96 | "source_name": "Main", 97 | "lang": "Java" 98 | } 99 | ``` 100 | 101 | License 102 | ---------- 103 | - [MIT](LICENSE) 104 | 105 | Author 106 | ---------- 107 | - Minho Kim ([ISKU](https://github.com/ISKU)) 108 | - https://www.acmicpc.net/user/isku 109 | - **E-mail:** minho.kim093@gmail.com 110 | -------------------------------------------------------------------------------- /boj.py: -------------------------------------------------------------------------------- 1 | import re 2 | import requests 3 | import json 4 | from bs4 import BeautifulSoup 5 | 6 | 7 | BOJ_URL = 'https://www.acmicpc.net' 8 | STATUS_URL = '%s/status' % (BOJ_URL) 9 | DOWNLOAD_URL = '%s/source/download' % (BOJ_URL) 10 | SOURCE_URL = '%s/source' % (BOJ_URL) 11 | 12 | 13 | class BOJ: 14 | 15 | def __init__(self): 16 | self.user_id = None 17 | self.cookie = None 18 | 19 | def login_boj(self, user_id, user_password): 20 | url = '%s/%s' % (BOJ_URL, 'signin') 21 | data = {'login_user_id': user_id, 'login_password': user_password} 22 | res = requests.post(url=url, data=data, allow_redirects=False) 23 | 24 | if res.status_code != 302: 25 | return True, str(res.status_code) 26 | if not 'Set-Cookie' in res.headers: 27 | return True, 'Cookie not found' 28 | if not 'Location' in res.headers: 29 | return True, 'Redirecting error' 30 | if 'error' in res.headers['Location']: 31 | return True, 'Login failed for user' 32 | 33 | cookie = '' 34 | flag_cfduid = False 35 | flag_oj = False 36 | for element in re.split(',| ', res.headers['Set-Cookie']): 37 | if '__cfduid' in element: 38 | cookie += element + ' ' 39 | flag_cfduid = True 40 | if 'OnlineJudge' in element: 41 | cookie += element 42 | flag_oj = True 43 | 44 | if not (flag_cfduid and flag_oj): 45 | return True, 'Invalid cookie' 46 | 47 | self.user_id = user_id 48 | self.cookie = cookie 49 | return False, 'Login succeed' 50 | 51 | def get_solved_problems(self): 52 | if self.user_id is None: 53 | return True, 'Login is required' 54 | 55 | url = STATUS_URL 56 | params = {'user_id': self.user_id, 'language_id': '-1', 'result_id': '4'} 57 | res = requests.get(url=url, params=params) 58 | 59 | if res.status_code != 200: 60 | return True, str(res.status_code) 61 | 62 | soup = BeautifulSoup(res.text, 'html.parser') 63 | selector = soup.find(id='status-table').tbody.find_all('tr') 64 | if selector is None: 65 | return True, 'Selector is none' 66 | 67 | solved_problems = [] 68 | for tr in selector: 69 | td = tr.find_all('td') 70 | 71 | problem = {} 72 | problem['submission_id'] = td[0].text 73 | problem['problem_id'] = td[2].a.text 74 | problem['problem_title'] = td[2].a['title'] 75 | problem['memory'] = td[4].text 76 | problem['time'] = td[5].text 77 | problem['language'] = td[6].text 78 | problem['length'] = td[7].text 79 | problem['date'] = td[8].a['title'] 80 | solved_problems.append(problem) 81 | 82 | return False, solved_problems 83 | 84 | def download_source(self, submission_id): 85 | if self.cookie is None: 86 | return True, 'Login is required' 87 | 88 | url = '%s/%s' % (DOWNLOAD_URL, submission_id) 89 | headers = {'Cookie': self.cookie} 90 | res = requests.get(url=url, headers=headers) 91 | 92 | if res.status_code != 200: 93 | return True, str(res.status_code) 94 | if res.text is None: 95 | return True, 'Source is none' 96 | 97 | source = res.text 98 | return False, source 99 | 100 | def get_private(self, submission_id): 101 | if self.cookie is None: 102 | return True, 'Login is required' 103 | 104 | url = '%s/%s' % (SOURCE_URL, submission_id) 105 | headers = {'Cookie': self.cookie} 106 | res = requests.get(url=url, headers=headers) 107 | 108 | if res.status_code != 200: 109 | return True, str(res.status_code) 110 | 111 | soup = BeautifulSoup(res.text, 'html.parser') 112 | selector = soup.find_all('input', {'name': 'code_open'}) 113 | if selector is None: 114 | return True, 'Selector is none' 115 | 116 | if selector[1].has_attr('checked'): 117 | return False, False 118 | return False, True 119 | 120 | def get_user_id(self): 121 | if self.user_id is None: 122 | return True, 'Login is required' 123 | return False, self.user_id 124 | -------------------------------------------------------------------------------- /git.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import subprocess 4 | 5 | 6 | REPO_URL = 'https://github.com/%s/%s' 7 | REMOTE_URL = 'https://%s:%s@github.com/%s/%s' 8 | 9 | 10 | class Git: 11 | 12 | def __init__(self): 13 | self.user_id = None 14 | self.user_password = None 15 | self.repo_name = None 16 | self.pipe = subprocess.PIPE 17 | 18 | # TODO: login verification 19 | def login_github(self, user_id, user_password): 20 | self.user_id = user_id 21 | self.user_password = user_password 22 | return False, 'Login succeed' 23 | 24 | def set_repository(self, repo_name): 25 | self.repo_name = repo_name 26 | 27 | def get_repo_name(self): 28 | return self.repo_name 29 | 30 | def clone(self): 31 | if os.path.isdir(self.repo_name): 32 | return False, "Repository '%s' already exists." % (self.repo_name) 33 | 34 | repo_url = REPO_URL % (self.user_id, self.repo_name) 35 | command = ['git', 'clone', repo_url] 36 | 37 | proc = subprocess.Popen(command, stdout=self.pipe, stderr=self.pipe) 38 | stdout, stderr = proc.communicate() 39 | 40 | if not stdout is None: 41 | print(stdout.decode()) 42 | if not stderr is None: 43 | print(stderr.decode()) 44 | if proc.returncode != 0: 45 | return True, str(proc.returncode) 46 | 47 | return False, 'done' 48 | 49 | def pull(self): 50 | repo_url = REPO_URL % (self.user_id, self.repo_name) 51 | cwd = self.repo_name 52 | command = ['git', 'pull', repo_url] 53 | 54 | proc = subprocess.Popen(command, cwd=cwd, stdout=self.pipe, stderr=self.pipe) 55 | stdout, stderr = proc.communicate() 56 | 57 | if not stdout is None: 58 | print(stdout.decode()) 59 | if not stderr is None: 60 | print(stderr.decode()) 61 | if proc.returncode != 0: 62 | return True, str(proc.returncode) 63 | 64 | return False, 'done' 65 | 66 | def add(self, file_path): 67 | cwd = self.repo_name 68 | command = ['git', 'add', file_path] 69 | 70 | proc = subprocess.Popen(command, cwd=cwd, stdout=self.pipe, stderr=self.pipe) 71 | stdout, stderr = proc.communicate() 72 | 73 | if not stdout is None: 74 | print(stdout.decode()) 75 | if not stderr is None: 76 | print(stderr.decode()) 77 | if proc.returncode != 0: 78 | return True, str(proc.returncode) 79 | 80 | return False, 'done' 81 | 82 | def commit(self, commit_message): 83 | cwd = self.repo_name 84 | command = ['git', 'commit', '-m', commit_message] 85 | 86 | proc = subprocess.Popen(command, cwd=cwd, stdout=self.pipe, stderr=self.pipe) 87 | stdout, stderr = proc.communicate() 88 | 89 | if not stdout is None: 90 | print(stdout.decode()) 91 | if not stderr is None: 92 | print(stderr.decode()) 93 | if proc.returncode != 0: 94 | return True, str(proc.returncode) 95 | 96 | return False, 'done' 97 | 98 | def push(self, branch): 99 | cwd = self.repo_name 100 | remote_url = REMOTE_URL % (self.user_id, self.user_password, self.user_id, self.repo_name) 101 | command = ['git', 'push', remote_url, branch] 102 | 103 | proc = subprocess.Popen(command, cwd=cwd, stdout=self.pipe, stderr=self.pipe) 104 | stdout, stderr = proc.communicate() 105 | 106 | """ 107 | This code is for debugging. 108 | If you print the log, there is a risk of password exposure. 109 | 110 | if not stdout is None: 111 | print(stdout.decode()) 112 | if not stderr is None: 113 | print(stderr.decode()) 114 | """ 115 | if proc.returncode != 0: 116 | return True, str(proc.returncode) 117 | 118 | return False, 'done' 119 | 120 | def all(self, file_path, commit_message): 121 | error, result = self.add(file_path) 122 | if error: 123 | return True, '%s: %s' % ('add', result) 124 | 125 | error, result = self.commit(commit_message) 126 | if error: 127 | return True, '%s: %s' % ('commit', result) 128 | 129 | error, result = self.push('master') 130 | if error: 131 | return True, '%s: %s' % ('push', result) 132 | 133 | return False, 'done' 134 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import getpass 4 | import time 5 | import json 6 | from boj import BOJ 7 | from git import Git 8 | from option import Option 9 | 10 | 11 | ERROR_FORMAT = '\n* ERROR: [%s] [%s]\n' 12 | PRINT_FORMAT = '* %s\n' 13 | DEFAULT_OPTION_FILE = 'option.json' 14 | 15 | 16 | class Main: 17 | 18 | def __init__(self, boj, git, option): 19 | self.boj = boj 20 | self.git = git 21 | self.option = option 22 | 23 | self.candidate_problems = [] 24 | 25 | def run(self): 26 | error, result = self.git.clone() 27 | if error: 28 | sys.exit(ERROR_FORMAT % ('git_clone', result)) 29 | 30 | error, result = self.git.pull() 31 | if error: 32 | sys.exit(ERROR_FORMAT % ('git_pull', result)) 33 | 34 | while True: 35 | error = self.analyze_solved_problems() 36 | if error: 37 | self.idle() 38 | continue 39 | 40 | self.push_solved_problems() 41 | self.idle() 42 | 43 | def analyze_solved_problems(self): 44 | error, solved_problems = self.boj.get_solved_problems() 45 | if error: 46 | print(ERROR_FORMAT % ('get_solved_problmes', solved_problems)) 47 | return True 48 | 49 | solved_set = set() 50 | self.candidate_problems = [] 51 | 52 | for problem in solved_problems: 53 | if problem['problem_id'] in solved_set: 54 | continue 55 | 56 | solved_set.add(problem['problem_id']) 57 | self.candidate_problems.append(problem) 58 | 59 | return False 60 | 61 | def push_solved_problems(self): 62 | for problem in self.candidate_problems: 63 | submission_id = problem['submission_id'] 64 | problem_id = problem['problem_id'] 65 | problem_title = problem['problem_title'] 66 | language = problem['language'] 67 | 68 | commit_message = self.option.commit_message(problem) 69 | source_tree = self.option.source_tree(problem, self.git.get_repo_name()) 70 | source_name = self.option.source_name(problem) 71 | error, ext = self.option.get_ext(language) 72 | if error: 73 | print(ERROR_FORMAT % ('get_ext', ext)) 74 | continue 75 | file_path = '%s/%s%s' % (source_tree, source_name, ext) 76 | 77 | support, same = self.option.lang(problem) 78 | if support: 79 | if not same: 80 | print(PRINT_FORMAT % ("'%s' language is not supported (submission: %s, problem: %s)" % (language, submission_id, problem_id))) 81 | continue 82 | 83 | private = self.option.private() 84 | if private: 85 | error, source_open = self.boj.get_private(submission_id) 86 | if error: 87 | print(ERROR_FORMAT % ('get_private', source_open)) 88 | continue 89 | if not source_open: 90 | print(PRINT_FORMAT % ('The source is private (submission: %s, problem: %s)' % (submission_id, problem_id))) 91 | continue 92 | 93 | if os.path.exists(file_path): 94 | print(PRINT_FORMAT % ('The source already exists (submission: %s, problem: %s)' % (submission_id, problem_id))) 95 | continue 96 | 97 | print(PRINT_FORMAT % ('Download the source (submission: %s, problem: %s)' % (submission_id, problem_id))) 98 | error, source = self.boj.download_source(submission_id) 99 | if error: 100 | print(ERROR_FORMAT % ('download_source', source)) 101 | continue 102 | print(source) 103 | 104 | self.save_source(source_tree, file_path, source) 105 | print(PRINT_FORMAT % ("Successfully saved the '%s'" % (file_path))) 106 | 107 | file_path = file_path.replace(self.git.get_repo_name(), '.', 1) 108 | error, result = self.git.all(file_path, commit_message) 109 | if error: 110 | sys.exit(ERROR_FORMAT % ('git_all', result)) 111 | print(PRINT_FORMAT % ('Successfully pushed the source (submission: %s, problem: %s)' % (submission_id, problem_id))) 112 | 113 | def save_source(self, source_tree, file_path, source): 114 | if not os.path.isdir(source_tree): 115 | os.makedirs(source_tree) 116 | 117 | f = open(file_path, 'w') 118 | f.write(source) 119 | f.close() 120 | 121 | def idle(self): 122 | poll = self.option.poll() 123 | 124 | print(PRINT_FORMAT % ('Wait %d seconds... \n' % (poll))) 125 | time.sleep(poll) 126 | print(PRINT_FORMAT % ('Restart work')) 127 | 128 | 129 | def set_options(): 130 | if not os.path.isfile(DEFAULT_OPTION_FILE): 131 | sys.exit(ERROR_FORMAT % ('set_options', '%s not found' % (DEFAULT_OPTION_FILE))) 132 | 133 | try: 134 | option_info = json.loads(open(DEFAULT_OPTION_FILE, 'r').read()) 135 | except json.decoder.JSONDecodeError as e: 136 | sys.exit(ERROR_FORMAT % ('set_options', e)) 137 | 138 | return Option(option_info) 139 | 140 | def login_boj(): 141 | boj = BOJ() 142 | 143 | for i in range(3): 144 | user_id = input('* BOJ id: ') 145 | user_password = getpass.getpass('* BOJ password: ') 146 | 147 | error, result = boj.login_boj(user_id, user_password) 148 | if error: 149 | print(PRINT_FORMAT % (result)) 150 | continue 151 | 152 | print(PRINT_FORMAT % (result)) 153 | return boj 154 | 155 | sys.exit(ERROR_FORMAT % ('login_boj', 'Login failed')) 156 | 157 | def login_github(): 158 | git = Git() 159 | 160 | for i in range(3): 161 | user_id = input('* GitHub id: ') 162 | user_password = getpass.getpass('* GitHub password: ') 163 | 164 | error, result = git.login_github(user_id, user_password) 165 | if error: 166 | print(PRINT_FORMAT % (result)) 167 | continue 168 | 169 | print(PRINT_FORMAT % (result)) 170 | repo_name = input('* Repository: ') 171 | git.set_repository(repo_name) 172 | return git 173 | 174 | sys.exit(ERROR_FORMAT % ('login_github', 'Login failed')) 175 | 176 | 177 | if __name__ == '__main__': 178 | option = set_options() 179 | boj = login_boj() 180 | git = login_github() 181 | 182 | try: 183 | Main(boj, git, option).run() 184 | except KeyboardInterrupt: 185 | print('\n* bye\n') 186 | -------------------------------------------------------------------------------- /option.json: -------------------------------------------------------------------------------- 1 | { 2 | } 3 | -------------------------------------------------------------------------------- /option.py: -------------------------------------------------------------------------------- 1 | DEFAULT_COMMIT_MESSAGE = 'BOJ #[NO]' 2 | DEFAULT_DIR_NAME = '[NO]' 3 | DEFAULT_POLL = 600 4 | DEFAULT_SOURCE_NAME = '[NO]' 5 | 6 | 7 | class Option: 8 | 9 | def __init__(self, option): 10 | self.option = option 11 | 12 | def commit_message(self, problem): 13 | if not 'commit_message' in self.option: 14 | return self.replace_msg(DEFAULT_COMMIT_MESSAGE, problem) 15 | 16 | return self.replace_msg(self.option['commit_message'], problem) 17 | 18 | def source_tree(self, problem, repo_name): 19 | if not 'source_tree' in self.option: 20 | if self.mkdir(): 21 | return '%s/%s' % (repo_name, self.dir_name(problem)) 22 | return '%s' % self.repo_name 23 | 24 | if self.option['source_tree'][-1] == '/': 25 | if self.mkdir(): 26 | return '%s%s' % (self.option['source_tree'], self.dir_name(problem)) 27 | return '%s' % self.option['source_tree'][:-1] 28 | 29 | if self.mkdir(): 30 | return '%s/%s' % (self.option['source_tree'], self.dir_name(problem)) 31 | return '%s' % self.option['source_tree'] 32 | 33 | def dir_name(self, problem): 34 | if not 'dir_name' in self.option: 35 | return self.replace_msg(DEFAULT_DIR_NAME, problem) 36 | 37 | return self.replace_msg(self.option['dir_name'], problem) 38 | 39 | def mkdir(self): 40 | if not 'mkdir' in self.option: 41 | return True 42 | 43 | return self.option['mkdir'] 44 | 45 | def private(self): 46 | if not 'private' in self.option: 47 | return False 48 | 49 | return self.option['private'] 50 | 51 | def poll(self): 52 | if not 'poll' in self.option: 53 | return DEFAULT_POLL 54 | 55 | return self.option['poll'] 56 | 57 | def source_name(self, problem): 58 | if not 'source_name' in self.option: 59 | return self.replace_msg(DEFAULT_SOURCE_NAME, problem) 60 | 61 | return self.replace_msg(self.option['source_name'], problem) 62 | 63 | def lang(self, problem): 64 | if not 'lang' in self.option: 65 | return False, None 66 | if problem['language'] != self.option['lang']: 67 | return True, False 68 | 69 | return True, True 70 | 71 | def replace_msg(self, msg, problem): 72 | msg = msg.replace('[NO]', problem['problem_id']) 73 | msg = msg.replace('[TITLE]', problem['problem_title']) 74 | return msg 75 | 76 | def get_ext(self, language): 77 | extensions = { 78 | 'C': '.c', 79 | 'C++': '.cpp', 80 | 'C++11': '.cpp', 81 | 'C++14': '.cpp', 82 | 'C++17': '.cpp', 83 | 'Java': '.java', 84 | 'Java (OpenJDK)': '.java', 85 | 'C11': '.c', 86 | 'Python 2': '.py', 87 | 'Python 3': '.py', 88 | 'PyPy2': '.py', 89 | 'PyPy3': '.py', 90 | 'Ruby2.5': '.rb', 91 | 'Kotlin': '.kt', 92 | 'Swift': '.swift', 93 | 'C# 6.0': '.cs', 94 | 'Text': '.txt', 95 | 'node.js': 'js', 96 | 'Go': '.go', 97 | 'F#': '.fs', 98 | 'PHP': '.php', 99 | 'Pascal': '.pas', 100 | 'Lua': '.lua', 101 | 'Perl': '.pl', 102 | 'Objective-C': '.m', 103 | 'Objective-C++': '.mm', 104 | 'C (Clang)': '.c', 105 | 'C++11 (Clang)': '.cpp', 106 | 'C++14 (Clang)': '.cpp', 107 | 'C++17 (Clang)': '.cpp', 108 | 'Golfscript': '.gs', 109 | 'Bash': '.sh', 110 | 'Fortran': '.f95', 111 | 'Scheme': '.scm', 112 | 'Ada': '.ada', 113 | 'awk': '.awk', 114 | 'OCaml': '.ml', 115 | 'Brainfuck': '.bf', 116 | 'Whitespace': '.ws', 117 | 'Tcl': '.tcl', 118 | 'Assembly (32bit)': '.asm', 119 | 'Assembly (32bit)': '.asm', 120 | 'D': '.d', 121 | 'Clojure': '.clj', 122 | 'Rhino': '.js', 123 | 'Cobol': '.cob', 124 | 'SpiderMonkey': '.js', 125 | 'Pike': '.pike', 126 | 'sed': '.sed', 127 | 'Rust': '.rs', 128 | 'Boo': '.boo', 129 | 'Intercal': '.i', 130 | 'bc': '.bc', 131 | 'Nemerle': '.n', 132 | 'Cobra': '.cobra', 133 | 'Algol 68': '.a68', 134 | 'Befunge': '.bf', 135 | 'Haxe': '.hx', 136 | 'LOLCODE': '.lol', 137 | 'VB.NET 4.0': '.vb', 138 | '아희': '.aheui' 139 | } 140 | 141 | if not language in extensions: 142 | return True, 'Unknown language' 143 | return False, extensions[language] 144 | --------------------------------------------------------------------------------