일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- 카카오 인턴
- 티스토리
- 보행자 천국
- 크레인 인형뽑기 게임
- 티스토리 open api
- 호텔 방 배정
- 징검다리 건너기
- trie
- Tistory
- CleanCode
- Spring Boot
- 알고리즘
- 프로그래머스
- bulk update
- pycon
- Python
- 트라이
- 불량 사용자
- jdbc
- Open API
- 트라이 #trie #알고리즘
- 가사 검색
- 튜플
- Today
- Total
택시짱의 개발 노트
티스토리에서 작성한 글로 github에 잔디 심기 본문
왜??
티스토리에서 개발 관련 글을 작성한지도 약3년정도가 되어가고 있으며 지금까지 티스토리에 글을 쓰면서 불편하다고 느낀적은 한번도 없었던거 같다.
그러나 한가지 단점이 있었으니 github에 기록이 남지 않는다는 것 이였다.
나는 github는 개발일기장과 같다고 생각하였고 글을 작성하는것도 개발의 일부분 이라고 생각 했기 때문이다.
그래서 github blog도 만들어 보았지만 front개발에 무지했던 나로써는.. github blog를 포기했다.
어떻게?
티스토리에 글을 올리면 github action을 통해 일정시간마다 새롭게 작성된 글이 있는지 확인하여
내가 블로그에 작성한 글을 github issue로 작성을 해주도록 하였다.
프로젝트의 아키텍쳐
사용 기술
1. github
2. github action
3. tistory openapi
위의 아키텍처를 좀더 설명 하자면 github action에 cron이라는 기능을 이용하였다. 일단 프로젝트를 진행 하기 전에 먼저 tistory
cron이란?
소프트웨어 유틸리티로써 유닉스 계열 컴퓨터 운영 체제의 시간 기반 job schedule이다. 소프트웨어 환경을 설정하고 관리하는 사람들은 작업을 고정된 시간, 날짜 간격에 주기적으로 실행할 수 있도록 스케쥴링하는데 cron을 사용한다.
즉 일정 주기 마다 프로그램을 실행 시키는것 이다.
tistory openapi를 만들어야 되는데 아래 링크를 참고하면 된다.
https://taxijjang.tistory.com/144
프로젝트 구조
프로젝트의 구조는 main, blog_info, github_utils, post로 나뉘어져 있다.
1. main은 여러개로 나뉘어 작성된 module를 실행 시킨다. issue를 작성할 repo가 유효한지, 새롭게 작성된 post가 있는지, 새롭게 작성된 post가 있다면 issue를 작성, post.json을 업데이트
아래는 main.py code
# main.py
import os
from datetime import datetime
from pytz import timezone
from github import BadCredentialsException
from github_utils import GithubUtil
from post import Post
"""
* 환경 변수 *
- github
MY_GITHUB_ACCESS_TOKEN: settings에서 발급한 access token
- tistory
APP_ID: tistory 에서 발급된 app_id
SECRET_KEY: tistory secret_key
REDIRECT_URI: 본인 tistory 주소를 입력합니다 ex) https://taxijjang.tistory.com
ACCESS_TOKEN: tistory 에서 발급 받은 access_token
USERNAME: 이슈에 남길 이름 ex)택시짱
REPO_NAME: 해당 프로젝트가 포함되어 있는 github repository의 이름 ex) AutoCommitTistory
"""
def main():
seoul_timezone = timezone('Asia/Seoul')
today = datetime.now(seoul_timezone)
today_date = today.strftime('%Y년 %m월 %d일')
today_date_eng = today.strftime('%Y/%m/%d')
issue_title = f'{os.environ.get("USERNAME")} TISTORY 새로운 포스팅 알림({today_date})'
repository_name = os.environ.get('REPO_NAME')
path = 'posts.json'
access_token = os.environ.get('MY_GITHUB_ACCESS_TOKEN')
github_util = GithubUtil(access_token=access_token)
# check collect github repo
try:
github_util.set_github_repo(os.environ.get('REPO_NAME'))
except BadCredentialsException:
print("github repository 유효하지 않습니다.")
return None
# set my repository
github_util.set_github_repo(repository_name=repository_name)
# post objects
post = Post(
access_token=os.environ.get('ACCESS_TOKEN')
)
# get new_post
new_posts, upload_issue_body = post.issue_body()
# no new posts today
if not upload_issue_body:
print(f'{today_date} 블로그 포스팅 목록이 없습니다.')
return None
# upload new issue
github_util.upload_github_issue(title=issue_title, body=upload_issue_body, labels=['new_posting'])
print(f'{today_date} 블로그 포스팅 목록 Issue 등록 성공!')
# upload new json file push
github_util.upload_github_push(message=f'Add new posting {today_date_eng}',
content=new_posts, path=path, branch='master')
print(f'{today_date} posts.json push 성공!')
return None
if __name__ == '__main__':
main()
2. python에서 제공하는 github library를 이용하여 github에서 발급한 access token으로 나의 git repo에 접근, issue작성, post.json update
아래는 github_utils.py code
# github_utils.py
import json
import os
from json import JSONDecodeError
from github import Github
from github import UnknownObjectException
class GithubUtil:
def __init__(self, access_token):
self._access_token = access_token
self._repo = None
def set_github_repo(self, repository_name):
"""
github repo object를 얻는 함수
:param repository_name: 해당 repo의 이름
:return: repo object
"""
g = Github(self._access_token)
self._repo = g.get_user().get_repo(repository_name)
def upload_github_issue(self, title, body, labels=None):
"""
해당 repo의 issue에 새롭게 작성된 post의 내용을 등록하는 함수
:param title: 이슈 제목
:param body: 이슈 내용
:param labels: 이슈의 labels
:return: None
"""
self._repo.create_issue(title=title, body=body, labels=labels)
def upload_github_push(self, message, content, path, branch):
"""
해당 repo에 변경된 json file을 push 해주는 함수
:param message: push message
:param content: push content
:param path: push 할 대상의 file 위치
:param branch: push 할 branch
:return: None
"""
try:
contents = self._repo.get_contents(path, branch)
data = json.loads(contents.decoded_content.decode('utf-8'))
data = self._check_json_username(data=data, content=content)
self._repo.update_file(contents.path, message, data, contents.sha, branch=branch)
except JSONDecodeError:
data = self._check_json_username(content=content)
self._repo.update_file(contents.path, message, data, contents.sha, branch=branch)
except UnknownObjectException:
# if old file is not exists make new file
data = self._check_json_username(content=content)
self._repo.create_file(path, message, data, branch=branch)
def _check_json_username(self, content, data=None):
data = data if data else dict()
if data.get('username') != os.environ.get('USERNAME'):
data['username'] = os.environ.get('USERNAME')
data['posts'] = content
else:
data['posts'].update(content)
data = dict(sorted(data.items(), reverse=True))
data = json.dumps(data, ensure_ascii=False, indent='\t')
return data
3. tistory openapi를 이용하여 얻는 access token을 이용하여 나의 블로그에 접근하여 정보를 획득
아래는 blog_info.py code
# blog_info.py
import json
import requests
class Tistory:
@staticmethod
def get_blog_name(access_token):
"""
tistory access_token를 이용하여
해당 유저 블로그의 이름을 반환하는 함수
:return: 해당 유저 블로그 이름
"""
blog_info_url = (
"https://www.tistory.com/apis/blog/info?"
f"access_token={access_token}"
f"&output=json"
)
req = requests.get(blog_info_url)
blog_info_json = json.loads(req.text)
return blog_info_json['tistory']['item']['blogs'][0].get('name')
4. 블로그에서 새롭게 작성된 post가 있는지 확인하고 post.json에 새롭게 작성된 post를 업데이트, issue 작성 준비
아래는 post.py code
# post.py
import os
import json
from json import JSONDecodeError
import requests
from blog_info import Tistory
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
class Post:
PRIVATE = 0
PENDING = 15
PUBLIC = 20
def __init__(self, access_token):
"""
init post class
:param access_token: tistory에서 발급 받은 access_token
"""
self._access_token = access_token
def post_list(self, page=1):
"""
Tistory open api를 이용하여
내가 작성한 블로그 글 목록을 가지고 오는 함수
:param page: 현재 보고자 하는 글 목록의 page 번호
:return: 현재 글 목록을 가지고 있는 page의 data를 dict로 변환
"""
blog_name = Tistory.get_blog_name(access_token=self._access_token)
post_list_url = (
"https://www.tistory.com/apis/post/list?"
f"access_token={self._access_token}"
f"&output=json"
f"&blogName={blog_name}"
f"&page={page}"
)
req = requests.get(post_list_url)
return json.loads(req.text)
def post_max_page(self):
"""
해당 유저의 access_token을 이용하여 post의 목록 data를 가지고 온 후에
post 목록 api에서 제공해주는 pagination 최대 page를 구하는 함수
:return: api에서 제공해주는 pagination의 최대 page
"""
post_list_json = self.post_list()
count = int(post_list_json.get('tistory').get('item').get('count'))
total_count = int(post_list_json.get('tistory').get('item').get('totalCount'))
return total_count // count if total_count % count == 0 else total_count // count + 1
def all_post_data(self):
posts = dict()
max_page = self.post_max_page()
for page_cnt in range(max_page, 0, -1):
now_page_posts = self.post_list(page_cnt).get('tistory').get('item').get('posts')
for now_page_post in now_page_posts:
if int(now_page_post.get('visibility')) != self.PUBLIC:
continue
posts[int(now_page_post.get('id'))] = now_page_post
return dict(sorted(posts.items()))
def check_new_post(self):
try:
with open(os.path.join(BASE_DIR, 'posts.json'), "r") as f:
json_data = json.load(f)
if json_data.get('username') != os.environ.get('USERNAME'):
# When you are a new author
print("기존에 작성된 posts.json의 사용자와 다른 사용자 입니다.")
raise FileNotFoundError
except (FileNotFoundError, JSONDecodeError):
# json file is empty
json_data = dict()
json_data['username'] = os.environ.get('USERNAME')
json_data['posts'] = dict()
new_posts = dict()
tistory_posts = self.all_post_data()
for post_id, data in tistory_posts.items():
# post is not visibility
if int(data.get('visibility')) != self.PUBLIC:
continue
post_id = str(post_id)
if not json_data.get('posts').get(post_id):
json_data['posts'][post_id] = data
new_posts[post_id] = data
# make now posts_data in json file
print(json_data)
with open(os.path.join(BASE_DIR,'posts.json'), 'w', encoding='utf-8') as make_file:
json.dump(json_data, make_file, ensure_ascii=False, indent="\t")
return new_posts
def issue_body(self):
new_posts = self.check_new_post()
upload_issue_body = ''
for key, value in new_posts.items():
if int(value.get('visibility')) != self.PUBLIC:
continue
id = value.get('id')
title = value.get('title')
post_url = value.get('postUrl')
create_at = value.get('date')
content = f'{id} - <a href={post_url}>{title}</a>, {create_at} <br/>\n'
upload_issue_body += content
return new_posts, upload_issue_body
github repo link -> https://github.com/taxijjang/AutoCommitTistory
개선하면 좋을 점
1. 새로운 글 판별 하는 방법 수정
새로운 글을 판별하기 위해 post.json을 이용하고 있는데 블로그의 글이 엄청 많이 작성이 되었을때는
다른 방법을 이용하여 판별 할 수 있는 방법을 찾아야 겠다. post의 정보를 db로 migration 등등
2. 프로젝트에 작성된 여러 code의 구조 개선
살짝 의식의 흐름대로 code를 작성한 부분이 없지 않아 있어 clean code를 적용하여 더 깔끔한 코드를 작성하고 싶다.
3. 비공개로 작성한 글은 새로운 글에서 제외
현재는 비공개로 작성된 글도 새로운 글로 인식하여 issue에 올리고 있는데 이것을 수정 해야된다. 수정완료
3. 여러 개발자들의 의견을 수용
내가 code를 작성했지만 여러 개발자들이 내 코드를 사용하며 느낀 부족한 부분 또는 잘못된 구조의 코드를 같이 개선하고 싶다!
마무리
위의 프로젝트를 진행하면서 tistory에 개발 관련 글을 작성했을때 github에 잔디 심기라는 목표는 달성했다!
확실히 github에 잔디가 심어지는게 블로그에 글을 꾸준하게 작성할 수 있게 하는 원동력이 된것같다.
아마 저와 같은 고민 (tistory에서 글 작성시 github에 기록을 남기고 싶은..)을 하시는분들이 조금은 있지 않을까 생각하고 있고
그 분들에게 조금이나마 도움이 되었으면 좋겠다!
아래는 현재 진행 중인 issue의 목록이다!
'기타' 카테고리의 다른 글
Docker로 배포시 발생한 오류 - no space left on device (0) | 2022.11.26 |
---|---|
객체지향 프로그래밍 (OOP) 4가지 원칙 (0) | 2022.02.24 |
티스토리 앱 등록 & Access Token 발급 (0) | 2021.10.10 |