🚨맨날 하는 수동 업데이트가 귀찮았다.
나는 깃허브 README에 내가 최근에 작성한 블로그 글을 띄워둔다. 아무래도 블로그를 꾸준히 작성하고 싶은 마음이 커서 그런 것도 있고, 언젠가 나의 장점이 되었으면 하는 다짐을 여러 곳에서 보고자 띄워두고 있다.
최근 작성한 5개의 글을 띄워두고 있는데 `매우 불편한 점`이 한 가지 있었다. 바로 업데이트를 수동으로 해야 한다는 문제였다.
매번 글을 쓸 때마다 깃허브에 찾아가서 일일이 링크를 가져다가 최신화하는 과정이 필요했고, 한 두 번이야 괜찮겠다 싶었는데, 이러한 일이 매일 발생한다면 매우 귀찮을 것 같았다.
지난 포스팅에서도 내가 개발자인 장점을 살려서 자동화 스크립트를 만들었었다. 이번에도 비슷하게 GitHub Actions를 활용하여 자동적으로 내 블로그의 최신 글들을 띄워주는 스크립트 동작을 만들어보았다.
많은 블로그에서 RSS라는 것을 지원한다. RSS란? 웹사이트의 업데이트 정보를 사용자에게 자동으로 제공하는 XML 기반의 데이터 형식이다. 주로 뉴스나 블로그, 팟캐스트 등 자주 업데이트되는 사이트에서 주로 사용된다. 이를 통해 사용자는 일일이 사이트에 방문하지 않아도 새로 업로드된 콘텐츠의 대략적인 정보를 얻을 수 있다.
우리가 자주 사용하는 티스토리나 Velog는 RSS를 지원한다.
- 티스토리의 경우
내_티스토리_주소.com/rss 를 주소창에 입력하면 된다. `ex) https://shqpdltm.tistory.com/rss`
- 벨로그의 경우
https://v2.velog.io/rss/내_벨로그_아이디 를 주소창에 입력하면 된다. `ex) https://v2.velog.io/rss/asdfg7123`
해당 사이트를 열어보면 다음과 같은 정보가 뜬다.
XML 데이터 기반으로, 나의 블로그의 간략한 요약 정보들을 볼 수 있다. 또한 보이는 바와 같이 최신 블로그 글들이 <item>으로 감싸져서 반복되는 형태를 확인할 수 있다.
자 이제 그러면 이러한 사실을 알았으니 코드로 xml 데이터를 파싱하고 readme에 갱신하는 과정을 구현해 보도록 하자.
🛠️fetchPost.js & update-readme.yml 구현하기
일단 구현된 코드를 보면서 설명해 보도록 하자. 간단하게 주석도 달아두었다.
const axios = require('axios'); // HTTP 요청을 위해 axios 모듈을 가져온다.
const xml2js = require('xml2js'); // XML을 JavaScript 객체로 변환하기 위해 xml2js 모듈을 가져온다.
const fs = require('fs'); // 파일 시스템 접근을 위해 fs 모듈을 가져온다.
const RSS_URL = 'https://shqpdltm.tistory.com/rss'; // RSS 피드 URL을 정의한다.
(async () => {
try {
// RSS 피드를 가져오기 위해 axios를 사용하여 GET 요청을 보낸다.
const response = await axios.get(RSS_URL, {
headers: {
// 요청 헤더를 설정한다.
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
'Accept': 'application/rss+xml, application/xml; q=0.9, */*; q=0.8'
}
});
const data = response.data; // 응답 데이터 (RSS 피드 XML)를 가져온다.
const parser = new xml2js.Parser(); // XML 파서를 생성한다.
const result = await parser.parseStringPromise(data); // XML을 JavaScript 객체로 변환한다.
const items = result.rss.channel[0].item; // RSS 피드 항목을 가져온다.
const latestPosts = items.slice(0, 5); // 최신 포스트 5개를 선택한다.
// README에 추가할 내용을 생성한다.
let content = `\n`;
latestPosts.forEach((post, index) => {
// 각 포스트의 제목과 링크를 <a> 태그로 감싸고 새 창에서 열리도록 한다.
content += `${index + 1}. <a href="${post.link[0]}" target="_blank">${post.title[0]}</a>\n`;
});
const readmeContent = fs.readFileSync('README.md', 'utf8'); // 기존 README.md 파일의 내용을 읽어온다.
// 기존 주석 태그 사이의 내용을 새로운 내용으로 교체한다.
const updatedReadmeContent = readmeContent.replace(
/<!-- LATEST_POSTS -->[\s\S]*<!-- LATEST_POSTS_END -->/,
`<!-- LATEST_POSTS -->\n${content}\n<!-- LATEST_POSTS_END -->`
);
// README.md 파일의 내용이 변경된 경우에만 파일을 업데이트한다.
if (updatedReadmeContent !== readmeContent) {
console.log("Updating README.md with new content."); // 업데이트 메시지를 출력한다.
fs.writeFileSync('README.md', updatedReadmeContent); // 새로운 내용을 README.md 파일에 쓴다.
} else {
console.log("No updates to README.md needed."); // 업데이트가 필요하지 않음을 출력한다.
}
} catch (error) {
console.error('Error fetching RSS feed:', error); // RSS 피드를 가져오는 도중 발생한 오류를 출력한다.
}
})();
원리는 간단하다. 아까 본 RSS 파일을 axios를 활용한 비동기 통신으로 가져오고 xml 데이터 파싱을 도와줄 xml2js를 활용해 해당 데이터를 객체로 반환, 필요한 데이터를 추출, 가공한다.
현재 README를 가져와서 정규식을 활용해 `<!-- LATEST_POSTS -->` `<!-- LATEST_POSTS_END -->` 사이 공간을 찾고, 해당 공간에 가공된 데이터를 입력한다.
이후 새로 생성된 README와 이전에 존재하는 README를 비교하여 변경점이 있다면 업데이트 후 갱신해 주고, 아니라면 그냥 업데이트가 필요 없다는 로그와 함께 코드를 종료한다. 간단하쥬?
이어서 WorkFlow를 활용한 GitHub Actions 설정 파일도 함께 보도록 하자. 여기도 간단히 주석을 달아두었다. Actions 설정은 공식 Docs에서 참조하면 좋다. 아직 번역 안된 부분도 많긴 해서 세부적인 내용은 검색을 병행했다.
name: Update README # 워크플로우의 이름이다.
on:
schedule:
- cron: "0 0 * * *" # 매일 자정에 실행되는 크론 표현식이다.
push:
branches:
- main # main 브랜치에 푸시될 때도 이 워크플로우가 실행된다.
jobs:
update-readme:
runs-on: ubuntu-latest # 워크플로우가 Ubuntu 최신 버전에서 실행된다.
steps:
- name: Checkout repository # 리포지토리를 체크아웃하는 단계이다.
uses: actions/checkout@v4 # actions/checkout 액션을 사용한다. 리포지토리의 파일을 가져와 사용할 수 있도록 해준다.
- name: Set up Node.js # Node.js를 설정하는 단계이다.
uses: actions/setup-node@v4 # actions/setup-node 액션을 사용한다.
with:
node-version: "20" # Node.js 20 버전을 사용한다.
- name: Install dependencies # 의존성을 설치하는 단계이다.
run: npm install axios xml2js # axios와 xml2js 패키지를 설치한다.
- name: Fetch latest blog posts # 최신 블로그 포스트를 가져오는 단계이다.
run: node fetchPosts.js # fetchPosts.js 스크립트를 실행한다.
- name: Show changes # 변경된 파일을 보여주는 단계이다.
run: |
echo "Changed files:" # 변경된 파일을 출력한다.
git status # git status 명령어를 실행하여 변경 사항을 표시한다.
- name: Update README # README를 업데이트하는 단계이다.
run: |
git config --global user.name 'github-actions[bot]'
git config --global user.email 'github-actions[bot]@users.noreply.github.com'
git add README.md # README.md 파일을 스테이징한다.
git commit -m 'Update README with latest blog posts' || echo "No changes to commit" # 커밋을 시도하고, 변경 사항이 없으면 메시지를 출력한다.
git push # 변경 사항을 푸시한다.
매일 위에서 작성한 스크립트 파일을 실행하는 yml 파일이다. 설명은 코드에 다 적어놓았고, 맨 처음에 actions 버전들을 업데이트 안 했는데, 찾아보니 v4가 최신버전이라 node 버전이랑 다 업데이트해 줬다.
그리고 커밋한 user name이랑 email도 설정할 수 있었는데 어차피 봇이 하는 거라.. 나는 그냥 actions bot으로 두었다.
🏆결과
구현하면서 rss-parser를 사용하다가 406 에러를 마주쳐서 안되기도 했고, 많은 시행착오.... 가 있기도 했다. 여담이지만 위 코드에서 헤더를 따로 설정해 준 이유가 406 에러 때문이었다.
그럼에도 불구하고 결과는...!! 두둥!!
UTC 기준으로 돌아가는지 한국시간으로 오전 10시쯤 동작했다. 뭐 시간은 크게 상관없고... 결론적으로 README가 잘 업데이트된다. 새벽 1시쯤 포스팅을 하나 업로드 했는데 잘 반영이 됐다.
깃허브 리드미가 `target="_blank"`를 지원하지 않는 사실도 알 수 있었고.. 그리고 벨로그 동작도 확인했으니, 조만간 TIL도 접은 글 형태로 입력해 둬야겠다. 자세한 구현 코드 및 업데이트 사항은 여기서 볼 수 있다.
앞으로 이렇게 자동화를 통해서 자동으로 진행할 수 있는 것들은 자동으로 돌려서 생활의 효율을 늘리려고 한다. 그리고 은근히 이런 건 비즈니스로직 구현하는 거에 비견될 만큼 재미있기도 하다. 약간 소일거리 느낌으로...?ㅎㅎ
'개발 > Javascript' 카테고리의 다른 글
자바스크립트 console.log()에 대한 고찰 (0) | 2024.05.18 |
---|---|
백준 JS 코테 파일을 자동 생성 스크립트로 만들어보자! (0) | 2024.05.07 |
JS를 이용해서 HTML 태그를 반복 출력해야하는 상황일 때 내장함수 map()을 이용해서 출력하는 법 (0) | 2023.10.17 |