저번 포스팅까지에서 키보드를 통한 프로그램의 동작을 결정하는 것을 구현하였다.
이를 GUI로 개선해야 할 이유를 하나 발견했었다.
바로 해당 파이썬 코드를 작동하는 동안 모든 키보드 동작이 느려지는 것이었다.
붉은 화살표가 키보드 입력이라 하였을 때 파이썬 코드가 일종의 버퍼가 되어 키보드 입력을 받아가기 때문에 모든 키보드 입력이 늦어지는 것이었다.
그리고 저번 포스팅들 다시 보며 확인해 본 결과 클래스 외부에 있는 함수를 통제하는 것이 어려웠다.
클래스에서 running 함수를 통하여 check_commit_status 함수 안의 반복문을 컨트롤 하려고 하였다.
하지만 클래스를 통해 check_commit_status를 실행시킨 순간 해당 함수는 이미 백그라운드에서 실행되고 있고 특히 폴링을 time.sleep 함수를 이용했기 때문에 튕김 현상이 발생한 것으로 추측된다.
이에 관련 함수들을 모두 class 안에 넣기로 하였다.
이후 해당 인자들을 class 내부 변수로 통제하는데 성공하였다.
import tkinter as tk
import threading
import requests
import base64
import re
import time
class MyApp:
def __init__(self, root):
self.root = root
self.root.title("GitHub 커밋 체크 및 티스토리 포스팅")
# GitHub repository 정보
....
# 티스토리 API 키 및 블로그 정보 설정
...
# 클래스 변수 초기화
self.owner = owner
self.repo = repo
self.token = token
self.access_token = access_token
self.blog_name = blog_name
self.category_id = category_id # 필요한 경우 설정
self.start_button = tk.Button(root, text="실행", command=self.start_thread)
self.start_button.pack()
self.stop_button = tk.Button(root, text="정지", command=self.stop)
self.stop_button.pack()
self.status_label = tk.Label(root, text="상태: 준비됨")
self.status_label.pack()
self.running = False
self.commit_status_label = tk.Label(root, text="커밋 상태: 대기 중")
self.commit_status_label.pack()
self.error_code = tk.Label(root, text="에러 상태: 이상 무")
self.error_code.pack()
def start_thread(self):
if not self.running:
self.running = True
self.status_label.config(text="상태: 실행 중")
self.thread = threading.Thread(target=self.check_commit_status)
self.thread.start()
def stop(self):
self.running = False
self.status_label.config(text="상태: 정지됨")
def update_commit_status(self, message):#상태 메세지 접근 및 변경
self.commit_status_label.config(text=f"커밋 상태: {message}")
self.commit_status_label.update()
def error_code_status(self, message):#상태 메세지 접근 및 변경
self.error_code.config(text=f"에러 상태: {message}")
self.error_code.update()
# GitHub에서 파일 내용 가져오기
def get_file_content(self, owner, repo, file_path, token):
api_url = f'https://api.github.com/repos/{owner}/{repo}/contents/{file_path}'
headers = {'Authorization': f'token {token}'}
response = requests.get(api_url, headers=headers)
if response.status_code == 200:
file_data = response.json()
file_content = base64.b64decode(file_data['content']).decode('utf-8')
return file_content
else:
print(f'파일 내용을 불러오지 못했습니다. 상태 코드: {response.status_code}')
return None
# 최신 커밋 SHA 가져오기
def get_repository_latest_commit_sha(self, owner, repo, token):
api_url = f'https://api.github.com/repos/{owner}/{repo}/commits'
headers = {'Authorization': f'token {token}'}
response = requests.get(api_url, headers=headers)
if response.status_code == 200:
commits = response.json()
latest_commit_sha = commits[0]['sha'] if commits else None
return latest_commit_sha
else:
self.error_code_status(f'Failed to fetch repository commits. Status code: {response.status_code}')
return None
# 커밋에서 파일 정보 가져오기
def get_commit_files(self, owner, repo, commit_sha, token):
api_url = f'https://api.github.com/repos/{owner}/{repo}/commits/{commit_sha}'
response = requests.get(api_url, headers={'Authorization': f'token {token}'})
if response.status_code == 200:
commit_info = response.json()
files_changed = commit_info.get('files', [])
MD_content = None
CC_content = None
for file_change in files_changed:
file_status = file_change['status']
file_path = file_change['filename']
if file_path.endswith('.md') and file_status == 'added':
MD_content = self.get_file_content(owner, repo, file_path, token)
elif file_path.endswith('.cc') and file_status == 'added':
CC_content = self.get_file_content(owner, repo, file_path, token)
return MD_content, CC_content
else:
self.error_code_status(f'Failed to fetch commit information. Status code: {response.status_code}')
return None, None
def parse_markdown(self, markdown_content, CC_content):
# 제목은 첫 번째 줄에서 추출
title = markdown_content.split('\n')[0].lstrip("# ")
# 문제 링크는 "[문제 링크]"가 나오는 첫 번째 문장에서 추출
problem_link_start = markdown_content.find("[문제 링크]")
if problem_link_start != -1:
problem_link_start += len("[문제 링크]")
problem_link_end = markdown_content.find("\n", problem_link_start)
link_with_brackets = markdown_content[problem_link_start:problem_link_end].strip()
problem_link = re.sub(r'[()]', '', link_with_brackets)
else:
problem_link = "No problem link found"
# 각 카테고리에 대한 정보를 추출하고 딕셔너리에 저장
parsed_data = {'title': title, 'problem_link': problem_link, 'code_content': CC_content}
current_category = None
current_content = ""
for line in markdown_content.split('\n'):
if line.startswith("### "):
if current_category:
parsed_data[current_category] = current_content
current_category = line[4:].strip()
current_content = ""
else:
current_content += line + '\n'
if current_category:
parsed_data[current_category] = current_content.strip()
return parsed_data
def convert_markdown_to_tistory_format(self, parsed_data):
tistory_formatted_content = (
f'<strong>[문제 링크] : <a href="{parsed_data["problem_link"]}" target="_blank" rel="noopener"> {parsed_data["problem_link"]} </a></strong>'
)
tistory_formatted_content += '<p></p>'
for category, content in parsed_data.items():
if category not in ["title", "problem_link", "code_content"]:
tistory_formatted_content += f'<p><strong>### {category}</strong></p>\n<p>{content}</p>\n\n'
tistory_formatted_content += f'<p><b>### 문제 풀이</b></p>\n<p></p>'
tistory_formatted_content += f'<p><b>### 작성한 코드</b></p>\n<p></p>'
tistory_formatted_content += f'<pre><code>{parsed_data["code_content"]}</code></pre>'
return tistory_formatted_content
def post_to_tistory(self, title, content, access_token, blog_name, tag, category_id=None):
api_url = f'https://www.tistory.com/apis/post/write'
data = {
'access_token': access_token,
'output': 'json',
'blogName': blog_name,
'title': title,
'content': content,
'tag': tag,
'visibility': 2, # 0: 보통, 1: 비공개, 2: 보호
}
if category_id:
data['category'] = category_id
response = requests.post(api_url, data=data)
print(response.json())
def work_process(self):
latest_commit_sha = self.get_repository_latest_commit_sha(self.owner, self.repo, self.token)
if latest_commit_sha:
MD_content, CC_content = self.get_commit_files(self.owner, self.repo, latest_commit_sha, self.token)
if MD_content and CC_content:
parsed_data = self.parse_markdown(MD_content, CC_content)
tistory_formatted_content = self.convert_markdown_to_tistory_format(parsed_data)
tags = ''
for tag in parsed_data['분류'].split(','):
tags += tag + ','
self.post_to_tistory(parsed_data['title'], tistory_formatted_content, self.access_token, self.blog_name, tags, self.category_id)
def check_commit_status(self):
# GitHub API 엔드포인트
api_url = f'https://api.github.com/repos/{self.owner}/{self.repo}/commits'
headers = {'Authorization': f'token {self.token}'}
latest_commit_sha = None
while self.running:
response = requests.get(api_url, headers=headers)
if response.status_code == 200:
commits = response.json()
current_latest_commit_sha = commits[0]['sha']
if latest_commit_sha is None:
self.update_commit_status("프로세스를 실행합니다. 최신 커밋을 받아왔습니다.")
latest_commit_sha = current_latest_commit_sha
elif current_latest_commit_sha != latest_commit_sha:
self.update_commit_status("새로운 커밋이 있습니다!")
latest_commit_sha = current_latest_commit_sha
self.work_process()
else:
self.update_commit_status("커밋이 변경되지 않았습니다.")
else:
self.error_code_status(f"Error: {response.status_code}, {response.text}")
time.sleep(10)
if __name__ == '__main__':
root = tk.Tk()
app = MyApp(root)
root.mainloop()
이게 해당 코드이며 개인정보는 생략하였다.
GUI는 tk 함수들을 이용하였다.
이후 이를 추가적으로 활용하여 프로그램의 현재 진행 상태와 에러 코드 등을 확인 할 수 있도록 설정하였다.
위와 같은 식으로 컨트롤 되며 상태를 아래와 같이 표시해주도록 만들었다.
이제 필요한 포스팅 정보들을 입력받거나 외부 파일을 이용하여 저장해 두는 기능을 추가하고자 한다.
이후 exe 파일 혹은 크롭 확장 앱으로의 포팅을 시도해보고 싶다.
'개발 기록 > 자동 포스팅 프로그램' 카테고리의 다른 글
코드 블록 개선 (0) | 2024.02.17 |
---|---|
정보 입력 UI 형성 (0) | 2024.01.19 |
자연스러운 취소 기능 추가 (0) | 2024.01.11 |
인터페이스 설정 (0) | 2023.12.26 |
몇 가지 기능 갱신 (0) | 2023.12.22 |