본문 바로가기

# 블로그/프로젝트

[프로젝트] 티스토리에 컨트리뷰션 그래프를 달아보자

[Change Log]
2020-07-09 : 설치 설명서 개선, FQA 추가.
2020-05-14 : 최초 작성.

개요

작은 티스토리 플러그인 느낌의 프로젝트.

아래처럼 GitHub스럽게 Tistory Contribition을 보여준다. 😀


이 블로그의 메인화면에서 라이브로 볼 수 있고,

전체 코드는 깃허브에서 확인할 수 있다.


개발 과정

사용된 기술스택


프론트엔드

프론트엔드는 설명할 것이 별로 없다.


토대는 bachvtuan/Github-Contribution-Graph를 사용했지만,

이대로 사용하기엔 2% 부족한 느낌이 들어 🤔 몇 군데를 손본게 전부이다.


수정사항 :

  • 박스에 경계선을 주고 헤더와 푸터 추가.
  • 현재 사용중인 스킨에 잘 녹아들도록 반응형 지원.

데모 페이지블로그 메인화면을 좌우로 늘렸다 줄였다 해보면 차이를 쉽게 알 수 있다.

before
after


백엔드

위의 프론트엔드 코드는 단순히 데이터를 렌더링하는 것에 불과하기 때문에.

데이터를 수집하고, 저장하고, 꺼내오는 무언가가 필요해진다.


1. 수집 단계

기본적으로는 티스토리에서 제공하는 Open API을 활용했다.


하지만 API를 사용하기 위한 AccessToken을 얻으려고 할 때 마다,

사용자가 직접 브라우저에서 동의버튼을 눌러야 했는데,

매번 손으로 누를수는 없기에 자동으로 AccessToken을 얻어올 수 있는 방법이 필요했다.


혹시 누군가가 npmjs에 해결책을 올려놨을까 싶어 찾아봤지만...


결국 바닥에서 직접 쌓아올리는 수 밖에 없었다.



사용자의 아이디비밀번호를 넣으면 HTTP Response에서 AccessToken을 얻어내도록 만들었는데,

구체적인 해결 방법은 다음과 같다.

  1. 사용자의 아이디와 비밀번호를 담아서 Login API에 전송한다.
    이 단계의 응답에서 Session을 추출할 수 있다.
  2. 이렇게 얻어진 세션값을 담아서 AccessToken과 교환가능한 Code 획득 페이지에 전달한다.
    동의버튼이 담긴 Response가 반환되었다면,
    정규식을 통해 동의버튼 스크립트에 있는 승인링크를 추출한다.
  3. 다시 세션값을 담아서 승인링크에 접속한다.
    이 때, 응답에서 AccessToken과 교환가능한 Code를 얻을 수 있다.
  4. 얻어진 Code를 사용하여 AccessToken과 교환한다.

내친김에 좀 더 가다듬어서 npm에도 배포했지만..

어째 배보다 배꼽이 더 커진 것 같은 느낌이 든다. 😥


2. 저장 단계

이렇게 수집된 데이터를 적당히 가공하고,

가공된 데이터를 이제 어딘가에 저장해야 했다.


제일 먼저 떠오른 방법은 데이터베이스였지만,

플러그인같은 것에 데이터베이스까지 붙이기엔 낭비같고...

차라리 게시글에 저장할까?

라는 생각으로 아래처럼 데이터만 저장할 게시글을 따로 작성하고,

그 게시글에만 아래처럼 데이터를 저장하기로 했다.

각 데이터는 `yyyy-mm-dd` : `cnt` 형태이다


3. 추출 단계

블로그 게시글에 저장을 하니 데이터를 가져오는 작업도 쉬워졌다.

서버까지 갈 필요없이 클라이언트에서 fetch를 사용하면 금방이였다.


Origin도 같으므로 CORS에 막힐 위험도 없다.

//
// 스토리지 게시글의 URL을 조합하고,
// 그 게시글의 HTML 문자열을 가져온다.
const url = `${window.location.origin}/${storagePostId}`;
const res = await fetch(storagePostUrl);
const html = await storagePostRes.text();

이렇게 추출된 데이터를 렌더링 함수에 전달하면,

멋진 컨트리뷰션 그래프가 그려진다.


4. 수집 자동화 단계

사용자가 매번 수집기를 실행시키는 것은 번거롭기 때문에

일정 시간마다 수집기가 알아서 동작하는 편이 좋다고 생각했다.


하지만 블로거의 컴퓨터에 계속 상주하는 것은 조금 꺼림칙하므로,

아마존에서 제공하는 LambdaCloudWatch를 사용하여 자동화를 진행했다.


수집기 함수를 Lambda로 등록하고,

1분마다 CloudWatchLambda를 트리거하도록 하면,

새 게시글이 RealTime은 아니더라도 NearTime으로 반영되도록 할 수 있었다.


추가적으로 Lambda에 수집기 함수를 deploy하는 과정도 간소화하기 위해,

serverless 프레임워크를 함께 사용했다.


5. 자동화 비용 계산

단순히 만들었다! 정도로만 끝내면 안된다.

실제로 적용해도 괜찮은지 경제성 측면에서 검토가 필요했다.


첫 수집은 전수조사를 하기때문에 대략 6000ms 정도의 소요시간을 보였지만,

두 번째 부터는 이전 수집의 결과도 참고하기 때문에 3000ms정도로 낮아졌다.


첫 수집만 테스트를 겸해서 로컬에서 진행하도록 하고,

그 이후로는 람다로 진행한다고 가정하고 계산기를 두들겨보았다.


1분마다 실행시키면 31일인 달을 기준으로 44640번이고,

메모리를 그닥 사용하지 않는 연산이므로 128MB으로 놓고 사용하면

이 경우엔 $0.29 정도만 지불하면 된다.


이번에는 좀 더 나쁜 케이스를 생각해보자.

각 함수의 최대시간을 10초로 설정해놓은 상태에서,

모든 함수호출이 최대치를 찍는다고 가정하면.

이 경우에도 $1.00 정도만 지불하면 된다.

물론 프리티어라면 이마저도 다 깍여서 면제된다.


이 정도 비용이라면 라이브로 적용해도 괜찮지 않을까?


How to install?

티스토리

레포지터리에서 client폴더에 있는 jscss 파일들을 업로드한다.


스토리지 게시글 생성

수집된 데이터가 저장될 게시글을 생성하고 PostID를 메모해두자.

예를들어 이 블로그에서 게시글의 URL은 aerocode.net/352 이므로 PostID352가 된다.


기존의 게시글을 재활용할 수 있지만,

본문의 html을 전부 깨끗하게 비워야 사용할 수 있다.


HEAD 태그

스킨의 head 태그안에 다음 스크립트를 붙여넣자.

<head>
    <!-- 티스토리 컨트리뷰션 라이브러리 시작 -->
    <script src="./images/github_contribution.js"></script>
    <script src="./images/github_contribution_tistory_plugin.js"></script>
    <link href="./images/github_contribution.css" rel="stylesheet" />
    <!-- 티스토리 컨트리뷰션 라이브러리 종료 -->
</head>

만약 자신의 스킨이 jQuery를 사용하지 않는다면,

추가적으로 다음 스크립트도 맨 위에 삽입해야 한다.

<script
    src="https://code.jquery.com/jquery-3.5.1.min.js"
    integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0="
    crossorigin="anonymous"
></script>

자신의 스킨이 jQuery를 지원하는지 잘 모르겠다면,

블로그에서 F12를 누르고 콘솔에 jQuery를 찍어보면 된다.

Support
Not Support


BODY 태그

다음에는 렌더링된 그래프가 담길 홀더를 만들고,

그 아래에서 렌더링 함수를 호출한다.


대충 모양새는 다음과 같으며,

렌더링 함수의 두 번째 인자는 PostID이다.

<body>
    <!-- 티스토리 컨트리뷰션이 그려질 홀더 시작 -->
    <div id="your_graph"></div>
    <!-- 티스토리 컨트리뷰션이 그려질 홀더 종료 -->

    <script>
        //
        // 티스토리 컨트리뷰션 그래프를 렌더링한다.
        loadTistoryContributionGraph("#your_graph", 352);
    </script>
</body>

이 블로그에서 적용한 것과 똑같은 장소에 적용하고 싶다면, 아래처럼 cover-list의 최상단에 렌더링 코드를 추가한다. 이 때, 기존 코드를 삭제하지 말아야 한다.

<s_cover name='cover-list'>
  <!-- 티스토리 컨트리뷰션이 그려질 홀더 시작 -->
  <div id="your_graph"></div>
  <script>
    //
    // 티스토리 컨트리뷰션 그래프를 렌더링한다.
    loadTistoryContributionGraph("#your_graph", 352); 
  </script>
  <!-- 티스토리 컨트리뷰션이 그려질 홀더 종료 -->

  <!-- 기존 코드 시작 -->
  ...
</s_cover>

디버깅

현재 디자인은 포스터스킨의 CoverPage를 기준으로 작성되었기 때문에,

다른 곳에 집어넣으면 문제가 있을 가능성이 매우 높다. 😱


그런 경우에는 자신의 블로그에 맞게 스타일을 수정해야 하는데,

각 스킨의 경우마다 스타일이 전부 다르기 때문에,

다른 스킨에서의 적용문의는 힘들다. 😔


이런 특이한 것을 블로그에 적용하려고 하는 분들은,

어느 정도 CSS에 능통하실 것이라고 믿고있다.


로컬

사전 준비

수집기를 사용하기 위해서는 다음 항목이 필요하다.

각 항목을 클릭하면 해당 페이지로 이동한다.


티스토리 키를 생성할 때는 아래의 규칙을 지켜야 한다.

  • 서비스 권한 : PC 어플리케이션
  • 서비스 권한 : 읽기 및 쓰기
  • 콜백 : https://tistory.com

환경변수 파일

server/.env 경로로 아래 예시처럼 환경변수 파일을 작성해야 한다.

기본 템플릿을 이미 server 폴더에 넣어뒀으니, 여러분은 빈칸만 채우면 된다.

targetBlogName=aerolabs
storageBlogName=aerolabs
storagePostId=352
client=티스토리_클라이언트_키
secret=티스토리_시크릿_키
id=티스토리_아이디
pw=티스토리_비밀번호
updateRange=3
includeMask=001

  • targetBlogName (문자열, 필수)

이력을 조사할 블로그의 식별자이다.
그 블로그의 주소가 xxx.tistory.com이라면 xxx에 해당한다.


  • storageBlogName (문자열, 필수)

이력을 저장할 블로그의 식별자이다.
내 블로그의 주소가 xxx.tistory.com이라면 xxx에 해당한다.


  • storageBlogId (숫자, 필수)

이력을 저장할 게시글의 식별자이다.
그 게시글의 주소가 ~.tistory.com/xxx 이라면 xxx에 해당한다.


  • client (문자열, 필수)

티스토리 개발자 센터에서 발급받은 AppKey와 동일하다.


  • secret (문자열, 필수)

티스토리 개발자 센터에서 발급받은 SecretKey와 동일하다.


  • id (문자열, 필수)

티스토리 로그인에 사용되는 아이디.


  • pw (문자열, 필수)

티스토리 로그인에 사용되는 비밀번호.


  • updateRange (숫자)

최근을 기준으로 몇 달 전의 게시글까지 수집할 것인지 가르킨다.

1이상 12이하의 정수이고, 기본 값은 1이다.

첫 수집때는 12로 고정된다.


  • includeMask (문자열)

3글자 이진수 문자열이며, 수집할 게시글의 공개여부 상태를 결정한다.

이진수 문자열 xyz 에서,

x가 1로 설정되면 비공개를 포함하여 수집한다.

y가 1로 설정되면 보호됨을 포함하여 수집한다.

z가 1로 설정되면 공개됨을 포함하여 수집한다.


실행 (Via NPM)

node.js가 설치되어 있다면 npm을 통해 수집기를 실행할 수 있다.

먼저 의존 라이브러리를 설치하기 위해 server 디렉터리에서 아래 커맨드를 입력한다.

# 의존 라이브러리 설치
$ npm install

수집기는 2가지 명령어를 지원한다.

마찬가지로 server 디렉터리에서 아래 커맨드를 입력한다.

# 수집
$ npx ts-node ./src collect

# 초기화
$ npx ts-node ./src clear

데이터를 수집해보자.

collect 실행로그


데이터가 성공적으로 수집되었다면 스토리지 게시글이 다음과 같은 형태로 변경된다.

collect 실행결과


데이터를 초기화해보자.

clear 실행로그


데이터가 성공적으로 초기화되었다면 스토리지 게시글의 내용이 비어있는 상태로 변경된다.

clear 실행결과


실행 (Via Docker)

node.js를 설치하기가 그렇다면 docker 이미지를 사용할 수 있다.

# 도커 이미지 다운로드
$ docker pull myaerocode/tistory-collector


# 수집
$ docker run \
    --rm \
    --env-file 환경변수파일_경로 \
    myaerocode/tistory-collector ./start.sh collect


# 초기화
$ docker run \
    --rm \
    --env-file 환경변수파일_경로 \
    myaerocode/tistory-collector ./start.sh clear 

자동화

배포 (Via NPM)

수집기를 람다에 배포하려면 AWS 인증서를 위한 환경설정이 필요하다.

AWS-CLI를 이용하거나 환경변수를 직접 설정한다.


AWS-CLI Configuration :


환경변수 설정 :

  • AWS_ACCESS_KEY_ID=xxx
  • AWS_SECRET_ACCESS_KEY=xxx
  • AWS_DEFAULT_REGION=ap-northeast-2

환경변수 설정이 끝났다면 server에서 다음 명령어를 실행하여 배포할 수 있다.

# 배포
$ npx serverless deploy

# 제거
$ npx serverless remove

배포 (Via Docker)

수집기를 람다에 배포하려면 AWS 인증서를 위한 환경설정이 필요하다.

환경변수 파일에 맨 아래의 값을 덧붙인다.

  • AWS_ACCESS_KEY_ID=xxx
  • AWS_SECRET_ACCESS_KEY=xxx

환경변수 설정이 끝났다면 다음 명령어를 실행하여 배포할 수 있다.

# 배포
$ docker run \
    --rm \
    --env-file 환경변수파일_경로 \
    myaerocode/tistory-collector ./start.sh deploy 

# 제거
$ docker run \
    --rm \
    --env-file 환경변수파일_경로 \
    myaerocode/tistory-collector ./start.sh remove 

테스트 실행

성공적으로 배포되면 AWS Lambda Console에서 수집기 함수들을 확인할 수 있다.


각 함수이름을 클릭하고 테스트 버튼을 누르면 람다를 실행해볼 수 있다.

테스트 버튼은 상단에 위치한다


주기적으로 실행되도록 설정

CloudWatch를 사용하여 일정시간마다 Lambda를 호출하도록 설정할 수 있다.

CloudWatch 콘솔에서 이벤트 - 규칙 항목으로 들어간 뒤 규칙생성 버튼을 누른다.


이벤트 발생조건은 1분마다로 설정한다.


만약 크론 표현식에 능통하다면

매주 월수금, 10일에 가장 가까운 평일같은 복잡한 조건들도 표현할 수 있다.


이벤트가 발생했을 때 트리거할 대상은 collect로 설정하고,

세부 정보 구성을 누르자.


마지막으로 규칙의 이름을 정하고 설정을 마무리한다.


설정 완료!


수집기 기록 조회

CloudWatch를 통해 수집기의 이력을 조회할 수 있다.


FQA

수집기 실행 관련

  • 아이디와 비밀번호를 올바르게 입력했는데 로그인이 실패했어요.

네트워크가 불안정하거나 로그인이 차단된 경우에 발생합니다.

네트워크 연결상태 또는 새로운 기기연결로 인한 차단 가능성을 확인해주세요.


  • 티스토리 키를 올바르게 입력했는데 키가 올바르지 않대요.

How to Install? -> 로컬 -> 사전준비에서 티스토리 API를 생성할 때 지켜야 할 규칙을 다시한번 확인해주세요.


수집기 배포 관련


후기

직접 사용할 프로그램을 만들었다는 경험은 꽤 특별했다. 😃


지금은 500줄도 안되는 토이 프로젝트였지만,

나중에 이것저것 살을 붙여서 좀 더 커다랗게 만들고 싶다.


TODO List

  • 잔디를 클릭하면 커버페이지의 리스트가 바뀌게끔 하고싶다.

  • 도커를 사용해서 실행과정과 배포과정을 더 짧게 줄이고 싶다.
    도커를 이용한 아래의 4개 명령어만 지원하고 싶다.
    (완료 2020-05-15)
# 수집
$ docker run COLLETOR_IMAGE collect 

# 초기화
$ docker run COLLETOR_IMAGE clear

# 람다에 배포
$ docker run COLLETOR_IMAGE deploy

# 람다에서 제거
$ docker run COLLETOR_IMAGE remove

그럴려면 컨테이너 내부에서 AWS CLI를 사용해야 할텐데...

환경변수를 사용해서 AWS Shared Credentials을 설정할 수 있는 방법에 대해 찾아봐야겠다. 🤔~


Special thanks to

영감을 주신 dailyheumsi님 감사합니다 😍


조공입니다


dailyheumsi님의 1년 컨트리뷰션

'# 블로그 > 프로젝트' 카테고리의 다른 글

[프로젝트] 소설위키  (0) 2020.05.25
  • 흠시 2020.05.16 15:49 신고

    헉 언젠가 해야지 해야지 했던건데, 딱 제가 생각했던 이상향을 구현하셨군요!
    개발 관련 블로깅 하시는 분들에게 잘 쓰일 수 있을 거 같아요 ㅎㅎ
    포스팅도 잘 정리해주셔서 나중에 시간잡고 저도 해봐야겠어요!
    마지막 조공 ㅋㅋㅋㅋㅋㅋㅋ 감사합니다!

    • AeroCode 2020.05.17 03:22 신고

      마음에 드셨다니 기쁩니다아 🤗
      이걸로 톡으로 받은 은혜를 좀 갚았을까요ㅋㅋ..

      만약 막힌 곳이 있다면 언제든 불러주세요.
      쏜살같이 달려오겠습니다!

  • 밤슬 2020.07.07 14:30 신고

    안녕하세요 글 잘 읽었습니다 :) 참고해서 진행하려고 하는데 서버 디렉토리에서 수집 명령어 커멘드하면 계속해서 환경변수가 주어지지 않았다고 나오는데 저는 .env파일에 다 작성 했거든요.. 파일명이 정해져 있는건가요? 아니면 파일 위치가 잘못되어 못 읽고 있는건지 모르겠어요. 파일은 server폴더 바로 안에 있고 파일명은 제가 임의로 설정했습니다.

    • AeroCode 2020.07.07 16:36 신고

      파일 풀네임이 ".env" 입니다 :) 관례적으로 파일 이름이 없어요.
      전체 경로가 ./server/.env로 저장되어 있어야 해요!

    • 밤슬 2020.07.07 16:55 신고

      감사합니다! 근데 로그인에 실패했다고 뜨네요 다 제대로 입력했는데 왜일까요...

    • AeroCode 2020.07.07 23:20 신고

      id와 pw로 로그인에 실패했거나, 로컬에서 로그인 요청이 아예 블럭된 경우에 발생합니다... 이외의 다른 예외는 짐작가는 것이 없네요; 관련 코드를 함께 첨부합니다. 206번째 라인에서 해당 에러가 발생합니다.

      https://github.com/MyAeroCode/tistory-js/blob/master/src/v1/tistory-api.ts

    • 밤슬 2020.07.08 03:34 신고

      새로운 기기로 로그인하는 것으로 인식해서 티스토리 자체에서 차단을 했었나봐요. 제가 커멘드 한 만큼 인증메일이 와있네요 ㅎㅎ 메일에서 인증처리 했더니 로그인 문제는 해결되었습니다. 그런데 이번엔 클라이언트 키와 시크릿 키가 정확한지 에러가 뜨는데 티스토리 API에서 얻은 clientID와 secret key 문자열 처리해서 파일에 저장했는데 왜 안되는지 모르겠습니다.. 혹시 이것 관련 무엇이 문제인지 아시나요? 번거롭게 해드려 죄송합니다!

    • AeroCode 2020.07.08 15:23 신고

      티스토리 API 설정 때문에 발생한 것 같습니다. 게시글에서 설명했었어야 했는데, 해당 내용이 생략되어 있었네요; 곧 게시글에 반영하겠습니다.

      티스토리 API 생성 시, 다음 규칙을 지켜서 생성해주세요.
      + 서비스 형태 : PC어플리케이션
      + 서비스 권한 : 읽기 및 쓰기
      + 콜백 : https://tistory.com

      문제가 있다면 언제든지 알려주세요 :)

    • 밤슬 2020.07.08 17:00 신고

      감사합니다! 설명해주신 대로 따라했더니 바로 해결되네요! 근데 이번엔 aws관련 환경변수 추가하고 람다에 배포하려고 하니 또 막히네요😂😂😂 aws 엑세스키 관련해서는 별다르게 권한 설정해야한다거나 하는게 있나요?

    • AeroCode 2020.07.08 18:25 신고

      넵, 추가적인 설정은 없으며 AWS 보안자격증명에서 받은 클라이언트와 시크릿키를 그대로 사용합니다. 아직도 에러가 발생한다면, 에러 메시지를 meet_met_met@daum.net 으로 보내주세요.