자바스크립트로 로또 자동구매하기

1

https://velog.io/@king/githubactions-lotto
어느 날 이 글을 보고 로또를 자동구매를 하고 싶어졌다.

하지만 이 글은 pythonselenium으로 브라우저를 띄워서 작업하는 것이었고,

api를 호출하면 더욱 더 간편하게 할 수 있을 것 같아 바로 진행.

api 로그인하고 쿠키 설정과 헤더구성에 있어서 많은 어려움을 겪는 도중

https://github.com/roeniss/dhlottery-api
python으로 비공식 api를 구성한 레포지토리를 아주 많이 참고해 제작했다.

사용방법
1. repository 생성
2. 예치금 충전
3. 레포 설정에서 secret 변수 설정 (USER_ID, USER_PW)

구현한 최종 결과물 :
https://github.com/youngcheon/lotto-weekly-purchasing

index.js

import { LottoClient } from "./LottoClient.js";
(async () => {
	const userId = process.env.USER_ID;
	const userPw = process.env.USER_PW;
	const client = new LottoClient();
	try {
		await client.login(userId, userPw);
		const result = await client.buyLotto645();
		client.showResult(result);
	} catch (e) {
		console.log(e);
	}
})();

LottoClient.js

import axios from "axios";
import cheerio from "cheerio";

export class LottoClient {
	constructor() {
		this._defaultSessionUrl = "https://dhlottery.co.kr/gameResult.do?method=byWin&wiselog=H_C_1_1";
		this._systemUnderCheckUrl = "https://dhlottery.co.kr/index_check.html";
		this._mainUrl = "https://dhlottery.co.kr/common.do?method=main";
		this._loginRequestUrl = "https://www.dhlottery.co.kr/userSsl.do?method=login";
		this._buyLotto645Url = "https://ol.dhlottery.co.kr/olotto/game/execBuy.do";
		this._roundInfoUrl = "https://www.dhlottery.co.kr/common.do?method=main";
		this._direct = "172.17.20.52";

		this._headers = {
			"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.77 Safari/537.36",
			Connection: "keep-alive",
			"Cache-Control": "max-age=0",
			"sec-ch-ua": '" Not;A Brand";v="99", "Google Chrome";v="91", "Chromium";v="91"',
			"sec-ch-ua-mobile": "?0",
			"Upgrade-Insecure-Requests": "1",
			Origin: "https://dhlottery.co.kr",
			"Content-Type": "application/x-www-form-urlencoded",
			Accept: "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
			Referer: "https://dhlottery.co.kr/",
			"Sec-Fetch-Site": "same-site",
			"Sec-Fetch-Mode": "navigate",
			"Sec-Fetch-User": "?1",
			"Sec-Fetch-Dest": "document",
			"Accept-Language": "ko,en-US;q=0.9,en;q=0.8,ko-KR;q=0.7",
		};
	}

	async setSession() {
		const response = await axios.get(this._defaultSessionUrl, { timeout: 10000 });
		const cookies = response.headers["set-cookie"];

		if (response.request.res.responseUrl === this._systemUnderCheckUrl) {
			throw new Error("동행복권 사이트가 현재 시스템 점검중입니다.");
		}
		const sessionId = cookies.map((cookie) => cookie.split(";")[0])[1];
		if (!sessionId.includes("JSESSIONID")) {
			throw new Error("쿠키가 정상적으로 세팅되지 않았습니다.");
		}

		this._headers.Cookie = sessionId;
	}

	async login(userId, userPw) {
		await this.setSession();
		const response = await axios.post(
			this._loginRequestUrl,
			{
				returnUrl: this._mainUrl,
				userId,
				password: userPw,
				checkSave: "off",
				newsEventYn: "",
			},
			{
				headers: this._headers,
				timeout: 10000,
			}
		);
		const $ = cheerio.load(response.data);
		const btnCommon = $("a.btn_common");

		if (btnCommon.length > 0) {
			throw new Error("로그인에 실패했습니다. 아이디 또는 비밀번호를 확인해주세요.");
		}
		return "ok";
	}

	async _getRound() {
		const response = await axios.get(this._roundInfoUrl, { timeout: 10000 });
		const $ = cheerio.load(response.data);
		const lastDrawnRound = parseInt($("strong#lottoDrwNo").text(), 10);
		const round = lastDrawnRound + 1;
		return round;
	}

	async buyLotto645() {
		const round = await this._getRound();
		const payload = {
			round: String(round),
			direct: this._direct,
			nBuyAmount: "5000",
			param: JSON.stringify([
				{ genType: "0", arrGameChoiceNum: null, alpabet: "A" },
				{ genType: "0", arrGameChoiceNum: null, alpabet: "B" },
				{ genType: "0", arrGameChoiceNum: null, alpabet: "C" },
				{ genType: "0", arrGameChoiceNum: null, alpabet: "D" },
				{ genType: "0", arrGameChoiceNum: null, alpabet: "E" },
			]),
			gameCnt: "5",
		};
		const res = await axios.post(this._buyLotto645Url, payload, {
			headers: this._headers,
			timeout: 10000,
		});

		return res.data;
	}

	showResult(body) {
		const result = body.result || {};
		if ((result.resultMsg || "FAILURE").toUpperCase() !== "SUCCESS") {
			throw new Error(`구매에 실패했습니다: ${result.resultMsg || "resultMsg is empty"}`);
		}

		console.log(
			`✅ 구매를 완료하였습니다.
  [Lotto645 Buy Response]
  ------------------
  Round:\t\t${result.buyRound}
  Barcode:\t${result.barCode1} ${result.barCode2} ${result.barCode3} ${result.barCode4} ${result.barCode5} ${result.barCode6}
  Cost:\t\t${result.nBuyAmount}
  Numbers:\n${this.formatLottoNumbers(result.arrGameChoiceNum)}
  Message:\t${result.resultMsg}
  ----------------------`
		);
	}

	formatLottoNumbers(lines) {
		const modes = {
			1: "수동",
			2: "반자동",
			3: "자동",
		};

		const tabbedLines = lines.map((line) => `\t\t${line.slice(0, -1)} (${modes[line.slice(-1)]})`);

		return tabbedLines.join("\n");
	}
}

action.yml

name: LottoBot

on:
    schedule:
        - cron: "55 23 * * 5" # UST 기준의 크론. UST 23:55 는 KST 08:55

jobs:
    build:
        runs-on: ubuntu-latest

        steps:
            - uses: actions/checkout@v2
            - uses: actions/setup-node@v1
              with:
                  node-version: 14.20.0

            - name: Install npm package
              run: |
                  npm install

            - name: exec index.js
              run: |
                  node .
              env:
                  USER_ID: ${{ secrets.USER_ID }}
                  USER_PW: ${{ secrets.USER_PW }}

0개의 댓글

관련 채용 정보