모든 소스 코드는 오픈소스로, 아래 레포들에 공개되어있습니다.

자동화 설정은 다음의 글을 참고해주세요.

작년부터 하려던 사이드 프로젝트를 밍기적 거리다 드디어 올해 4월에 시작했다.
로또를 자동으로 구매하는 프로젝트, 프로젝트를 위해서 rich-automation 이라는 조직을 생성했다. (자동으로 부자가 되고싶다는 포부를 담아서)

로직은 단순하다.

  1. 동행복권 예치금이 충전된 아이디를 준비한다.
  2. 브라우저를 띄우고
  3. 로그인을 하고
  4. 로또를 구매한다.

여기에서 끝나면 재미가 없으니 어떻게 작업에 재미를 붙일 수 있을지 좀 더 고민을 해본 결과, 다음과 같이 구성을 해보기로 결정했다.

  1. 로그인 및 구매, 당첨 확인등을 할 수 있는 npm package
  2. npm package 를 이용해서 만들 수 있는 부수적인 툴들 (ex: github actions, cli program)
  3. 린트, 빌드, 테스트, 배포 모두 CI/CD 를 통해서 진행하고, 테스트 커버리지는 90% 이상으로 유지

요즘엔 워낙 툴들이 잘 나와있어서 cli 도 inquirer/yargs 등을 이용하면 쉽게 만들 수 있으나, 로그인 관리를 쿠키로 해야하고 만료가 너무 금방돼서 안하기로 했다. (인터페이스는 만들어뒀지만 귀찮음이 더 컸다.)

애초에 자동으로 로또를 구매하는게 목적이었기 때문에, github actions 만 만들기로 결정했고, 우선 lotto-module 패키지를 만들었다.


@rich-automation/lotto

아이디 패스워드를 통한 로그인과 쿠키를 통한 로그인을 지원하고, 구매, 당첨 확인, 당첨 확인 링크 생성 을 지원한다.

import { LottoService } from '@rich-automation/lotto';

const service = new LottoService({ headless: true });
await service.signIn('id', 'password');
const lottoTicket = await service.purchase(3);

내부에서는 컨트롤러 설정에 따라서 puppeteer 혹은 playwright 에 의해서 크로미움이 컨트롤 된다.

처음부터 확장성을 가지고 디자인을 하기는 했지만 puppeteer 만으로 만들었다가 테스트를 진행하는 도중 GitHub Actions 에서 구매를 진행하는데 이슈가 있어서 테스트를 위해서 playwright 도 추가했다.

두 라이브러리의 인터페이스가 몇몇 네이밍을 제외하고는 거의 다 똑같아서, playwright 를 추가하고 테스트를 작성하는데 30분정도면 충분했다.

아무튼 이슈의 원인은 동행복권 사이트가 리눅스에서 몇몇 처리를 다르게 하면서 생겼던 이슈였고, 일단 임시로 이렇게 처리했다.

이렇게 만들어진 패키지를 기반으로 커스텀 액션을 만들었다.


rich-automation/lotto-action

typescript 로 작성했고, issue-to-readme 와 같은 작업들을 하며 여러번 만들어봐서 만드는데 어려움은 크게 없었다.

아무튼 레포 secrets 에 동행복권 아이디/패스워드를 등록해주고 Settings > Actions > General > Workflow permissions > Read and write permissions 로 체크됐는지 확인한 뒤에

단 14줄짜리 파일만 생성하면 바로 자동화 세팅이 끝난다 ✨

name: Lotto purchase (cron)
on:
  schedule:
    - cron: '0 22 * * 6'
jobs:
  run-actions:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - uses: rich-automation/lotto-action@latest
        with:
          id: ${{ secrets.ID }}
          pwd: ${{ secrets.PASSWORD }}
          token: ${{ secrets.GITHUB_TOKEN }}

이렇게 등록된 액션은 정해진 시간마다 지난주에 구매한 로또 당첨결과를 확인하고 로또를 구매한다.

구매한 로또는 레포에 이슈로 등록된다.

이슈 관리는 라벨로 하는데, 위에서 등록된 모래시계 라벨은 당첨 결과를 기다리고 있다는 라벨이다.
다음주에 당첨결과를 확인할 때, 해당 라벨이 달려있는 이슈만 긁어서 처리한다.

꽝이면 이슈를 닫고, 당첨이 되면 몇등이 당첨됐는지 이슈에 라벨을 달아주고 코멘트로 봇이 나를 멘션하고 몇게임이 당첨됐는지 알려준다.
만약 깃허브 앱을 사용하고 있다면 멘션을 통해서 푸시 알림도 받을 수 있다.


이슈안에서 링크를 누르면 바로 당첨 금액도 확인할 수 있다.

아래 템플릿을 이용하면 쉽게 세팅을 할 수 있다.
https://github.com/bang9/lotto-purchase


GitHub Action 만들기

처음 만들때는 기본으로 제공되는 typescript template 를 사용해봐도 좋다.

GitHub Action 의 기본적인 실행 원리는 레포 루트에 action.yml 파일이 있으면, 레포 이름+파일 정보를 통해서 깃허브 액션으로 실행해준다.

내가 배포한 GitHub Action 의 레포의 이름은 rich-automation/lotto-action 니까 아래와 같이 쓸 수 있다. 이때 버전에는 레포의 태그가 들어갈 수 있다.

- uses: rich-automation/lotto-action@version

action.yml

action.yml 에는 이름, 설명, 입력 필드, 실행 환경과 실행될 파일 등을 지정할 수 있다.

name: 'lotto-action'
description: 'GitHub Action for lotto purchase'
branding:
  icon: 'dollar-sign'
  color: 'yellow'
author: 'bang9<gusrn1423@naver.com>'
inputs:
  id:
    required: true
    description: 'Input id'
    default: ''
runs:
  using: 'node16'
  main: 'dist/index.js'

name, description, brandingmarket place 에서 나오게 될 정보들이다.
market-place-img

마켓에 배포하는 방법은 레포에서 릴리즈를 생성할 때 체크하면 된다.
참고로 마켓에 배포하지 않아도 액션은 사용할 수 있다.

inputs 는 액션을 작성할때 with: 하위에 받을 값들을 명시할 수 있고, 실제 코드에서는 받은 값을 core.getInput('keyName'); 같이 읽어올 수 있다.
inputs-with-img
inputs-getInput-img

runs 는 실행될 파일과 실행될 환경이다. 해당 액션이 실행되면 node16 에서 dist/index.js 파일을 실행한다는 이야기이다.
때문에 dist/index.js 에 액션을 위한 파일이 존재해야 하고, 프로그램을 단일 파일로 작성한게 아니라면 이를 위해서 번들러로 단일 파일을 추출하고 레포에 포함해 주어야 한다.

typescript template 에서는 ncc 라는 툴을 사용해서 번들링을 진행하는데, 아직 esm/cjs 모듈들을 혼합해서 번들링 할 때 지원이 잘 안되는것 같아서 rollup 을 사용했다.

rollup 을 설정 할때는 puppeteer 에서 vm2 라는 패키지를 사용하는지, 해당 패키지에서 몇몇 파일을 모듈 시스템을 통한 모듈 로드(import/require) 가 아닌 방식으로 불러오면서(링크) rollup 이 해당 파일을 정상적으로 번들링 하지 못하여, 수동으로 copy 해야하는 등의 이슈가 있었다.

아무튼 번들링을 진행해서 dist/index.js 파일을 레포에 추가해주고, 레포의 모듈 타입이 esm 으로 설정이 돼있어서 dist 폴더 안의 스크립트들은 cjs 로 실행되도록 dist/package.json 을 추가해줬다.

기타 팁

액션을 사용할때 태그가 일치해야 하는데, 사용하는 입장에서 정확한 버전을 확인하고 버전을 올리는건 꽤 귀찮은 일이다.
아래처럼 특정 태그 하나를 지정해두고 계속해서 그 태그를 업데이트 해주면, 별다른걸 지정하지 않아도 업데이트된 버전을 사용할 수 있다.

- uses: rich-automation/lotto-action@latest

actions/checkout 에서 아이디어를 얻어서, 아래처럼 배포를 할 때 latest 태그를 자동으로 싱크를 맞추도록 설정을 해놨다.
https://github.com/rich-automation/lotto-action/blob/main/.github/workflows/publish.yml#L29-L32

단일 태그는 메이저 버전을 변경할때 브레이킹 체인지를 함부로 할 수 없으니, semver 에 맞춰서 v1, v2 같은 형태로 태그를 제공해줘도 좋다.


요즘은 IDE 에서 action.yml 같은 특정 라이브러리/환경/서비스에서 사용되는 파일에 대한 스키마를 검증까지 해주는데, 일반적으로 이는 schemastore 에 업로드 된 정보를 기준으로 처리가 된다.
https://github.com/SchemaStore/schemastore/blob/master/src/schemas/json/github-action.json

profile
JavaScript, TypeScript and React-Native

0개의 댓글