ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [디스코드 봇] 스팀 게임 업데이트 알림
    취미 2025. 1. 21. 15:16

     

    게임 에이펙스 레전드의 업데이트가 생기면 빠르게 알림을 받고싶어서 진행하게 됐다.

    예전에 어느 디스코드 서버에 이런 알림을 주는 채널이 있었는데 서버가 정리되면서 사라졌다.

    나름 유용했던 기능이라 직접 추가하기로 했다.

     

    깃허브에서 비슷한 스크립트를 찾아서 이걸 기반으로 작업했다.

    이 글에서 이걸 스크립트라고 부르겠다.

    https://github.com/kurokobo/game-update-notifier

     

    GitHub - kurokobo/game-update-notifier: A Python script that will let you know via Discord as soon as a new version of your favo

    A Python script that will let you know via Discord as soon as a new version of your favorite game on Steam is released. - kurokobo/game-update-notifier

    github.com

    아래는 개인적인 목적에 맞게 수정된 스크립트다.

    https://github.com/Mstone8370/ApexLegends_update_notifier

     

    GitHub - Mstone8370/ApexLegends_update_notifier: Private repo forked from https://github.com/kurokobo/game-update-notifier

    Private repo forked from https://github.com/kurokobo/game-update-notifier - Mstone8370/ApexLegends_update_notifier

    github.com

    main 브랜치에는 일부 작업만 했고, 구글 클라우드에 배포된건 google-cloud 브랜치다.

     


     

    이 글은 작업했던 것들을 잊어버리지 않기 위해 작성했다.

    게임의 업데이트 주기가 길기 때문에 나중에 다시 작업할 필요가 생길땐 무엇을 했는지 잊어버린 후일 가능성이 높기 때문이다.

    따라서 작업 과정을 순서대로 자세히 적겠다.

     

    이 프로젝트는 구글 클라우드의 다음 서비스들을 사용한다.

    • Cloud SDK
    • Cloud Storage
    • Artifact Registry
    • Cloud Build
    • Cloud Run

    이 스크립트는 작업 속도가 느려도 되며, 스토리지에는 JSON 파일 하나만 저장하고, 스케줄러 트리거를 통해 하루에 한번만 작동한다.

    가벼운 작업이므로 구글 클라우드를 무료로 사용하는 것이 주요 목적이다.

    결제 수단이 연결되어있기 때문에 제대로 알고 작업하지 않으면 비용이 부과될 가능성이 있다.

     

     

     


     

     

     

    Cloud SDK

    로컬 작업 폴더에서 구글 클라우드 명령어를 사용할 수 있게 해준다.

    설치할때 파이썬도 같이 설치하는데 나는 파이썬이 이미 설치되어있어서 제외했다.

    설치 후 아래 명령어로 초기화한 다음 로그인했다.

    gcloud init
    gcloud auth login

     

     

    Cloud Storage

    클라우드 스토리지는 두가지 목적으로 사용된다.

    1. 스크립트가 사용하기 위한 JSON 파일을 저장.

    2. Cloud Build를 통해 빌드할때 생성된 파일을 저장. (빌드된 이미지는 Artifact Registry에 저장됨.)

     

    여기에서 2번이 문제가 될 수 있다.

    스토리지에서 미리 준비를 해두지 않으면 2번 상황을 위해 자동으로 버킷이 생성된다.

    그런데 이때 생성되는 버킷이 기본값인 Multi-region으로 설정되므로, 지역간에 데이터를 옮기면서 비용이 발생될 수 있다.

    이걸 모른 상태에서 빌드를 하다보니 결제 예상 요금이 올랐다.

     

    이미 생성된 버킷의 저장 위치를 바꾸는건 불가능하므로 삭제 후 직접 생성해야한다.

    빌드하며 생긴 파일이 저장되는 버킷의 이름은 [프로젝트 ID]_cloudbuild 이므로 이 버킷을 삭제한 뒤에 다시 만들거나, 미리 만들어서 단일 region으로 설정해서 비용이 발생되지 않게 했다.

    이 프로젝트의 경우 버킷의 이름은 apex-legends-update-notifier_cloudbuild다.

     

    엑세스 설정은 아래 이미지 처럼 자동으로 생성될때의 옵션을 따라했다.

     

    이 버킷을 만들어두면 빌드할때 생성된 파일이 이곳에 저장된다.

     

     

    이 외에 스크립트가 작동하며 저장할 파일을 위한 버킷도 Multi-region이 아닌 단일 region으로 설정했다.

    이 버킷은 공개 액세스를 방지했다.

     

    이 스크립트는 작업 공간에 JSON 파일을 저장하는데, 이제 Cloud Storage로 변경해야한다.

    파이썬에서 Cloud Storage에 접근하려면 google-cloud-storage 모듈이 필요하다.

    pip로 설치하면 된다.

    pip install google-cloud-storage

    코드 수정과 관련된 내용은 생략한다.

     

     

     

    Artifact Registry

    빌드된 이미지가 저장될 곳이다.

    만약 배포할 소스를 deploy 명령어로 바로 배포한다면, 빌드 과정에서는 Artifact Registry의 저장소가 자동으로 생성된다.

    Cloud Run으로 배포하는 경우 저장소의 이름은 cloud-run-source-deploy고, Docker 형식으로 빌드된다.

     

    빌드를 직접 하는 경우에는 이미지가 저장될 저장소를 직접 지정해야하고, 이때에는 생성되어있는 저장소를 지정해야한다.

    저장소를 생성할때도 서버 위치를 지정해줘야한다.

    이 프로젝트는 Cloud Storage에서 그랬듯이 us-central1을 계속 사용했다.

     

    위는 자동으로 생성된 저장소, 아래는 직접 생성한 저장소다.

     

     

     

    Cloud Build

    Artifact Registry 저장소를 도커로 만들었으니 Dockerfile을 통해 빌드한다.

    이 스크립트는 이미 Dockerfile이 작성되어있다.

    대신 현재 작업중인 로컬의 파이썬 버전이 3.10 이므로, Dockerfile을 수정해서 버전만 바꿨다.

     

    Cloud Build로 빌드할땐 cloudbuild.yaml 파일에 빌드 명령어를 미리 적어두고 이 파일을 사용하는게 편하다.

    글 작성 시점에선 아래와 같이 작성되어있다.

    steps:
    - name: 'gcr.io/cloud-builders/docker'
      args: ['build', '-t', 'us-central1-docker.pkg.dev/$PROJECT_ID/update-check-repo/update-check-image', '.']
      automapSubstitutions: true
    images:
    - 'us-central1-docker.pkg.dev/$PROJECT_ID/update-check-repo/update-check-image'

    name의 'gcr.io/cloud-builders/docker'의 의미는 Cloud Build에서 제공하는 도커 빌더를 사용하겠다는 의미다.

     

    args는 빌드할때 지정할 인자들을 하나씩 적은거다.

    인자 중에서 'us-central1-docker.pkg.dev/$PROJECT_ID/update-check-repo/update-check-image'는 이미지 파일이 저장될 경로다.

    하나씩 풀어서 설명하면 다음과 같다.

     

    us-central1-docker.pkg.dev: Artifact Registry 위치

    $PROJECT_ID: 프로젝트의 ID로 대체될 변수명. 아래의 automapSubstitutionstrue로 하면 자동으로 대체해준다.

    update-check-repo: Artifact Registry 저장소 이름. 위에서 확인할 수 있다.

    update-check-image: 생성할 이미지 이름.

     

    cloudbuild.yaml을 이렇게 작성하고 아래의 명령어로 빌드한다.

    gcloud builds submit --config cloudbuild.yaml --region us-central1

     

    빌드가 성공하고 Artifact Registry에 들어가보면 지정한 위치에 있는 이미지를 확인할 수 있다.

     

     

    참고로 .gcloudignore 파일을 작성하면 빌드할때 제외할 파일을 선택할 수 있다.

    이 스크립트의 경우에는 다음과 같이 작성했다.

    docker-compose.yml
    README.md
    sample.env
    venv/
    assets/
    .git/
    .gitignore
    *.json

     

     

     

    Cloud Run

    Cloud Run은 요청이 들어올 때만 작동한다.

    그리고 스케줄러 트리거를 추가할 수 있어서 원할때마다 작동하게도 할 수 있다.

    주기적으로 게임 업데이트를 확인하는 이 스크립트에 사용하기 적합하다.

    하지만 이 스크립트는 기본적으로 계속 실행중인 상태에서 주기적으로 확인하므로, 수정을 통해 한번 확인하고 종료되게 했다.

     

    Cloud Run에는 "서비스"와 "작업" 두 종류가 있다.

    서비스는 실행 한 결과를 받아야하는 경우에 사용하고,

    작업은 실행만 하길 원하는 경우에 사용한다.

    이 스크립트는 자체적으로 디스코드 메시지를 보내주므로, 작업이 적합하다.

     

    참고로 작업의 종료 코드로 0을 리턴하면 작동 성공, 다른 값을 리턴하면 작동에 실패했다는 결과와 함께 종료 코드를 보여주니 잘 활용하자.

     

    작업을 생성하려면 create 명령어, 기존의 작업을 새로운 이미지로 업데이트하려면 update 명령어를 쓰면 된다.

    gcloud run jobs create [작업 이름] --image [이미지 경로] --region [지역]
    
    gcloud run jobs update [작업 이름] --image [이미지 경로] --region [지역]

     

    이미지 경로는 클라우드 콘솔에서 복사해서 썼다.

     

    경로 오른쪽의 복사 버튼

     

     

    빌드된 이미지 말고 소스를 배포할 수도 있다.

    빌드한 후에 작업을 생성하는 과정을 거치므로 지금까지 한 것과 비슷한 작업이라고 보면 될듯하다.

    gcloud run jobs deploy [작업 이름] --source . --region [지역]

     

     

    작업을 배포한 후에, 일정 주기로 트리거 하는 스케줄러를 추가했다.

    구글 클라우드 콘솔에서 Cloud Run으로 이동한 뒤에 작업을 선택한다.

     

    작업에 들어가서 스케줄러 트리거를 추가했다.

     

    빈도는 unix-cron 형식에 따라 입력해줘야한다.

    자세한 규칙은 모르겠지만, 0 19 * * * 의 의미는 매일 19시에 트리거 된다는 뜻이다.

    에이펙스 레전드의 업데이트 시간이 주로 UTC 기준 18시이므로, 넉넉하게 한 시간 뒤에 검사하게 설정했다.

     

     

    이렇게 해서 구글 클라우드 작업이 끝났다.

     

     

     


     

     

     

    다음은 스크립트를 어떻게 수정했는지를 간단하게 정리한다.

     

    CHECK_INTERVAL_SEC

    이 스크립트는 계속 실행중인 경우를 가정해서 제작되었다.

    따라서 스케줄러를 통해 한번씩 실행하는 경우에는 적합하지 않으므로 수정했다.

    .env 파일의 환경 변수 중 CHECK_INTERVAL_SEC에 음수를 넣으면 대기하는 while 루프에서 break해서 종료되게 했다.

     

     

     

    TIME_ZONE

    구글 클라우드의 region은 us-central1을 선택했다.

    따라서 디스코드 알림이 도착하면 시간이 미국 중부 기준으로 적혀있다.

     

    예시:

    확인한 시간은 한국 기준 11:59:51이지만, 02:59:51로 적혀있다.

    따라서 시간대를 선택하는 환경 변수인 TIME_ZONE을 추가해서 이 시간대를 기준으로 변환하게 수정했다.

    timezone = None
    try:
        timezone = pytz.timezone(TIME_ZONE)
    except pytz.exceptions.UnknownTimeZoneError as te:
        logger.error("Unknown Time Zone Error: %s", te)
    except Exception as e:
        logger.error(e, stack_info=True, exc_info=True)
    logger.info("Time zone: %s", timezone)

     

    타임존이 None이면 시스템의 시간대를 사용한다.

     

     

     

    디스코드 메시지

    기존 스크립트는 디스코드 메시지에 어떤 게임의 업데이트가 있는지만 알려준다.

    나는 업데이트와 관련된 정보를 더 알고싶어서 메시지에 관련 정보를 더 담기로 했다.

     

    디스코드 메시지를 다루는 객체는 Discord로, discord.py 파일에 있다.

    Discord 객체의 fire 함수에서 정보를 전달받아서 메시지를 작성해서 전송한다.

    기존에는 업데이트 된 게임인 App 객체들과 timestamp만 전달받는다.

    따라서 업데이트와 관련된 자세한 정보를 다룰 수 없다.

     

    내가 필요한건 자세한 업데이트 정보이므로, fire 함수를 수정했다.

    # discord.py
    
    # 이전
    def fire(self, updated_apps, timestamp):
    
    
    # 수정 후
    def fire(self, updated_keys, timestamp, result):

    이 변경사항은 스팀을 통해 업데이트를 확인하는 코드에만 반영되었다.

    따라서 다른 플랫폼을 통해 확인하는 경우에는 문제가 생길 수 있기에 적어둔다.

     

    수정된 함수의 인자 중 timestamp는 사용하지 않는다.

    timestamp에는 업데이트를 확인한 시간이 담기게 된다.

    하지만, 메시지가 전송된 시간과 업데이트를 확인한 시간은 거의 일치하니 의미없는 정보다.

    따라서 사용하지 않는다.

     

    result 인자에는 Steam 객체의 멤버 변수인 new_result 딕셔너리가 담긴다.

    updated_keys 리스트에 이 딕셔너리의 키가 담겨있다.

    이를 통해 업데이트 된 게임의 추가 정보를 확인할 수 있다.

     

    글을 작성하는 시점에서는 새로운 업데이트가 추가된 상황을 겪지 않아서 어떤 데이터가 의미있는지 모르는 상태다.

    일단 아래처럼 Buid ID와 업데이트가 추가된 시간을 담아봤다.

     

     

     

     

    latest_result.json

    이 JSON 파일에는 가장 최근에 수집한 데이터에서 업데이트 확인에 필요한 정보가 담겨있다.

    가장 중요한 정보다.

    이 파일의 정보는 Result 객체로 변환되어 딕셔너리로 관리된다.

    이 딕셔너리가 Steam 객체의 멤버 변수인 new_result다.

     

    Result 객체에는 많은 정보가 들어있지 않다.

    따라서 새로운 정보를 포함시키려면 다음 작업을 해야한다.

    • Result 객체에 멤버 변수 추가
    • latest_result.json 파일을 읽어서 Result 객체로 변환하는 과정에서 추가한 멤버 변수 초기화
    • 수집한 데이터를 Result 객체로 변환하는 과정에서 추가한 멤버 변수 초기화
    • models.pyjson_default 함수를 수정해서 추가한 멤버 변수를 리턴 값에 추가

    여기서 마지막에 json_default 함수는 Result 객체를 딕셔너리로 변환하는 함수다.

    이때 추가한 멤버 변수를 담지 않으면 JSON 파일을 저장할때 같이 저장되지 않으니 주의해야한다.

     

     

Designed by Tistory.