Profile picture

[Python] 유저 팔로워(follower) 조회하기 - github API 사용법

JaehyoJJAng2024년 02월 01일

◾️ 개요

내 깃허브 계정의 팔로워 수를 실시간으로 알림 받아보고 싶다는 쓸데없는 생각이 들어

이 기회에 Github api도 겸사겸사 공부해볼겸 기록을 해보려고 한다.

최종 목표는 discord webhook을 사용하여 관리자가 지정한 특정 시간에 팔로워 알림 메시지를 전송하는 것이고

팔로워 증가가 없는 경우에도 매번 메시지를 보내게 되면 헷갈릴 수도 있으니 해당 분기의 경우에는 출력문만 따로 남겨두도록 했다.


◾️ github API로 팔로워 알림 구현하기

1) 토큰 발급

팔로워 수를 확인하려면 "사용자의 공개 프로필 정보"에 대한 읽기 액세스 권한이 필요하다.

아래의 권한을 가진 토큰은 필수로 생성해주도록 하자.

토큰 옵션 옵션 설명
workflow actions 실행 권한
user.read:user 유저 프로필 읽기 권한
user.user:follow 유저 팔로워 & 언팔로워 권한

2) Github API로 팔로워 수 가져오기

  • 토큰을 획득하였고 github api를 사용하여 필요한 정보에 접근할 수 있게 되었음.
  • 이제 유저의 팔로워 현황 정보를 알아내기 위해 github api를 본격적으로 사용해보자.
  • 큰 흐름은 아래와 같다.

|순서|설명| |1|github api로 유저(me)의 팔로워 정보 목록을 가져온다| |2|followers.txt 라는 파일에서 팔로워 목록을 읽어온 후, 추출된 팔로워 목록과 대조 후 변경점이 있는지 체크한다| |3|변경점이 있는 경우 파일을 업데이트하고 discord webhook으로 알림을 보내준다|


2-1) 팔로워 추출 코드 작성 (파이썬)

가장 먼저 유저의 팔로워를 github api로 어떻게 가져올 것인지 실습해보자.

requests 라이브러리를 사용하여 Github API에 요청을 보내고, 인증을 위해 토큰 발급에서 만들어놨던 액세스 토큰을 사용할 것이다.

그런 다음, 간단한 루프를 사용하여 주기적으로 팔로워 수를 가져오고 출력하는 스크립트이다.

현재 이 코드는 결과를 위한 테스트 코드이다.

main.py

import config.config as conf
import requests as rq
import json
import os

class GithubAPI():
    def __init__(self) -> None:
        self._TOKEN: str = conf.token

    @staticmethod
    def check_file_or_new_file(filename: str) -> None:
        if not os.path.exists(filename):
            with open(filename,'w') as fp:
                pass
    
    def get_follower_data(self,username: str, token: str) -> list[dict[str,str|int]]:
        headers: dict[str,str] = {
            'Authorization': f'token {token}',
            'Accept': 'application/vnd.github.v3+json'
        }
        
        url: str = f'https://api.github.com/users/{username}/followers'
        resp: rq.Response = rq.get(url=url,headers=headers)
        
        data = resp.json()
        followers_data: list[dict[str,str|int]] = [{'name': follower['login'], 'id': follower['id']} for follower in data]
        return followers_data

    def update_followers_json(self,current_followers: dict[str,str|int],filename: str) -> None:
        with open(filename,'w') as file:
            json.dump(current_followers,file)

    def check_new_follower(self,username: str, token: str, filename: str) -> None:
        # filename check
        self.check_file_or_new_file(filename=filename)
        
        # Load current followers from file
        with open(filename,'r') as file:
            try:
                current_followers = json.load(file)
            except:
                current_followers = {}
        
        # Get current followers from github api
        followers: list[dict[str,str|int]] = self.get_follower_data(username=username,token=token)
        
        # Find new follwers
        for follower in followers:
            if follower['id'] not in current_followers.values():
                print(f'New follower found: {follower["name"]}')
                current_followers[follower['name']] = follower['id']

        # Update current followers
        self.update_followers_json(current_followers=current_followers,filename=filename)
        
def main() -> None:
    # Set Token
    _TOKEN: str = conf.token
    
    # Create GithubAPI instance
    gapi: GithubAPI = GithubAPI()
    
    
    # find new followers
    gapi.check_new_follower(username='JaehyoJJAng', token=_TOKEN, filename='followers.json')
    
if __name__ == '__main__':
    main()

congig/config.py

import configparser

properties: configparser.ConfigParser = configparser.ConfigParser()
properties.read('config/config.ini')
github: configparser.SectionProxy = properties['GITHUB']
token: str = github['token']
webhook_url: str = github['webhook_url']

위 코드를 실행하면 기존 follower.json 파일에서 변경점이 있는지 체크하고 변경점이 있는 경우 아래와 같은 출력문을 보여주고 파일을 업데이트 함.

$ python3 main.py

New follower found: gamemann
New follower found: AYIDouble
New follower found: kmjune1535
New follower found: positivehun
New follower found: leemin-jae
New follower found: tulna07
New follower found: JubayerRiyad
New follower found: goodsforyou
New follower found: simpsonpd
New follower found: cumsoft
New follower found: Hoonology
New follower found: omss2006
New follower found: mulkingJ
New follower found: yavis1218
New follower found: kevinssic
New follower found: assistem77
$ cat followers.txt

{"gamemann": xxx, "AYIDouble": xxx, "kmjune1535": xxx, "positivehun": xxx, "leemin-jae": xxx, "tulna07": xxx, "JubayerRiyad": xxx, "goodsforyou": xxx, "simpsonpd": xxx, "cumsoft": xxx, "Hoonology": xxx, "omss2006": xxx, "mulkingJ": xxx, "yavis1218": xxx, "kevinssic": xxx, "assistem77": xxx}

최초 실행의 경우 followers.json 파일에 팔로워 데이터가 없기 때문에 위처럼 모든 팔로워가 저장되는 것을 확인할 수 있다.


◾️ Discord Webhook 적용하기

1) 디스코드 웹후크 주소 확인

디스코드 채널에 웹 후크로 메시지를 보내려면 해당 채널의 웹후크 URL을 알아야한다.
웹후크 접근 권한이 있는 유저 또는 어드민이라면 아래 포스팅을 참고하여 웹 후크 URL을 확인해도록 하자.

web-hook-생성


2) 웹후크로 메시지 보내기

이제 코드(파이썬)에서 작성했던 코드에 DiscordWebhook이라는 클래스를 하나 만들고 메시지 전송 기능을 하는 메서드를 만들기만 하면 된다.

간단한 메시지를 보내는 샘플은 아래 게시글을 참고해보도록 하자.
Webhook 간단한 메시지 보내기


위 샘플 코드를 기반으로 아래와 같이 코드를 수정해주자.

main.py

class DiscordWebhook():
    def __init__(self) -> None:
        self.discord_webhook_url: str = conf.webhook_url
    
    def send(self,message: str) -> None:
        message: dict[str,str] = {
            'content': message
        }
        resp: rq.Response = rq.post(url=self.discord_webhook_url,data=message)

class GithubAPI(DiscordWebhook):
    ...

    def check_new_follower(self,username: str, token: str, filename: str) -> None:
        # Find new follwers
        for follower in followers:
            if follower['id'] not in current_followers.values():
                print(f'New follower found: {follower["name"]}')
                current_followers[follower['name']] = follower['id']
                
                # 메시지 전송
                message: str = f'[알람] {follower["name"]}님이 {username}님을 팔로우 하셨습니다!\n'
                self.send(message=message)

    ...

◾️ Github Actions 적용하기

매일 특정 시간마다 위 스크립트를 실행하도록 Github Actions을 사용해보자.

Actions를 사용하면 "매일 특정 시간에 메시지 전송" 이라는 조건을 쉽게 달성할 수 있다.

1) github actions의 스케줄링 기능

  • Github Actions는 지정된 cron에 따라 워크플로우를 자동화할 수 있는 예약 기능을 제공한다.
  • 이 스케줄링을 사용하면 일정 시간마다 스크립트를 실행할 수 있지만, 유의할 점은 cron이 UTC 기준이라는 것이다
    • UTC 기준 0:00이 한국 시간으로 9:00이므로, 만약 매일 오전 9시에 스크립트를 실행하고 싶다면 cron을 0 0 * * *로 설정해야 한다.

{% include codeHeader.html name=".github/workflows/test.yaml" %}

name: cron schedule
on:
  schedule:
    - cron: "0 0 * * *"
...

2) Github actions로 메시지 자동 전송 코드 작성하기

2-1) 액션에 사용할 환경 변수 지정하기

자. 작성한 코드를 다시 살펴보자.

class DiscordWebhook():
    def __init__(self) -> None:
        self.discord_webhook_url: str = conf.webhook_url
    ...


class GithubAPI():
    def __init__(self) -> None:
        self._TOKEN: str = conf.token
    ...

코드상에서 token 값과 discord webhook url 값이 전부 conf 모듈에서 가져오게 되어있다.

conf 모듈은 아래와 같고
{% include codeHeader.html name="config/config.py" %}

import configparser


properties: configparser.ConfigParser = configparser.ConfigParser()
properties.read('config/config.ini')
github: configparser.SectionProxy = properties['GITHUB']
token: str = github['token']
webhook_url: str = github['webhook_url']

config.ini 파일 내용은 아래와 같다.

config/config.ini

[GITHUB]
token=xxxx
webhook_url=xxxx

깃허브로 프로젝트 배포 시 .env 파일 같은 환경변수 파일(=이 예제에서는 config/config.ini이 해당)들은 노출되어서는 안되기 때문에

보통 .gitignore에 같이 배포되지 않도록 환경변수 파일은 아래와 같이 명시해둔다.
{% include codeHeader.html name=".gitignore" %}

.env

하지만 위 처럼 환경변수 파일을 제외시키고 github actions 워크플로우를 실행시키게 되면 무조건 에러가 난다.

이유는 당연하게도 읽어올 환경 변수 파일을 VM에서 찾지 못하기 때문이다.

그러면 환경변수를 VM에 안전하게 등록 시켜줘야할텐데 그건 어떻게 할까?

바로 레포 > [Settings] > [Secrets and Variables] > [Actions]에 들어가서 .env(=이 예제에서는 config/config.ini이 해당)에 저장했던 환경변수 값을 secrets에 추가해주면 된다.
image


코드도 아래와 같이 conf 모듈에서 가져오는 것이 아닌 os 패키지를 사용하여 서버에 등록된 환경변수를 가져오도록 변경해주어야 한다.

import os

class DiscordWebhook():
    def __init__(self) -> None:
        self.discord_webhook_url: str = os.getenv('DISCORD_WEBHOOK_URL')
    ...


class GithubAPI():
    def __init__(self) -> None:
        self._TOKEN: str = os.getenv('GH_TOKEN')
    ...

2-2) 스케줄링 코드 작성

  • 매일 오전 9시에 discord로 메시지 전송
  • schedule 이벤트에서 cron 스케줄을 UTC 기준 매일 0:00에 실행되도록 지정하면 된다.
  • cron 스케줄은 UTC 기준이므로 실제 대한민국 시간으로는 0:00이 09:00로 보면된다.

.github/workflows/deploy.yaml

name: Run discord bottt
on:
  schedule:
    - cron: "0 0 * * *"        # utc time
...

그 다음 실행하고 싶은 스크립트를 추가해주자.
각 코드에 대한 설명은 코드 밑 테이블을 참고하자.

.github/workflows/deploy.yaml

name: Run discord bottt
on:
  schedule:
    - cron: "0 0 * * *"        # utc time

jobs:
  bot:
    runs-on: ubuntu-latest        # (1) ubuntu 환경에서 실행
    timeout-minutes: 3            # (2) job의 시간 제한을 3분으로 지정 
    steps:
      - name: "1. Checkout repo"  # (3) 레포의 최신 코드 체크아웃
        uses: actions/checkout@v2
      
      - name: "2. Setup Python"   # (4) 파이썬 환경 설정
        uses: actions/setup-python@v2
        with:
          python-version: 3.x
      
      - name: "3. Install Python dependencies"  # (5) 파이썬 의존성 패키지 설치
        run: |
          pip install --upgrade pip && pip install -r requirements/requirements.txt
      
      - name: "4. Run script"   # (6) 스크립트 실행 및 코드 내에서 쓰일 환경변수 정의
        run: |
          python3 main.py
        env:
          GH_TOKEN: {% raw %} ${{ secrets.GH_TOKEN }}  {% endraw %}
          DISCORD_WEBHOOK_URL: {% raw %} ${{ secrets.DISCORD_WEBHOOK_URL }}  {% endraw %}
순서 설명
(1) 액션 구동 환경을 ubuntu로 지정
(2) job의 시간 제한을 3분으로 설정. job 실행 시간이 3분이 넘어갈 시 액션 실행 중단
(3) actions/checkout@v2를 사용하여 레포지토리의 최신 코드를 체크아웃
(4) actions/setup-python@v2를 사용하여 파이썬 환경을 설정
(5) 파이썬 의존성 패키지 설치
(6) 파이썬 코드를 실행하고 환경 변수를 정의

이렇게 액션을 작성하면 매일 9시경에 스크립트가 실행되고, 내 계정에 팔로워가 새로 생긴 경우 디스코드로 메시지가 오게될 것이다!
image


Loading script...