version 0.0.1
This commit is contained in:
commit
e07c771438
|
|
@ -0,0 +1,5 @@
|
||||||
|
.idea
|
||||||
|
.run
|
||||||
|
node_modules
|
||||||
|
test
|
||||||
|
yarn.lock
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
|
||||||
|
const Fs = require('fs')
|
||||||
|
|
||||||
|
const Lotto = require('./lib/lotto')
|
||||||
|
const NTFY = require('./lib/ntfy')
|
||||||
|
const Constants = require('./lib/constants')
|
||||||
|
|
||||||
|
new class LottoAutomation {
|
||||||
|
#config = {}
|
||||||
|
|
||||||
|
#_start() {
|
||||||
|
this.#_loadConfig()
|
||||||
|
setImmediate(this.#_buyLotto.bind(this))
|
||||||
|
}
|
||||||
|
#_loadConfig() {
|
||||||
|
if(!Fs.existsSync(Constants.CONFIG_PATH))
|
||||||
|
throw new Error('Config.json 파일이 존재하지 않습니다.')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const configString = Fs.readFileSync(Constants.CONFIG_PATH)
|
||||||
|
this.#config = JSON.parse(configString)
|
||||||
|
NTFY.Init(this.#config.ntfyUrl, this.#config.ntfyToken)
|
||||||
|
} catch(e) {
|
||||||
|
throw new Error('Config.json 파일이 올바르지 않습니다.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async #_buyLotto() {
|
||||||
|
const loginResult = await Lotto.Login(this.#config.id, this.#config.password)
|
||||||
|
if(loginResult instanceof Error)
|
||||||
|
return NTFY.Send('로그인 실패 - ' + loginResult.message ?? loginResult.description, 'no_entry_sign')
|
||||||
|
|
||||||
|
const buyResult = await Lotto.Buy(this.#config.buyLottoPolicy)
|
||||||
|
if(buyResult instanceof Error)
|
||||||
|
return NTFY.Send('구입 실패 - ' + loginResult.message ?? loginResult.description, 'no_entry_sign')
|
||||||
|
|
||||||
|
const successMessageArr = [
|
||||||
|
`구매 성공`,
|
||||||
|
`ROUND: ${buyResult.round}`,
|
||||||
|
`COST : ${buyResult.cost}`,
|
||||||
|
]
|
||||||
|
successMessageArr.push(
|
||||||
|
...buyResult.numbers.map(numberStr => {
|
||||||
|
const numberType = Constants.LOTTO_BUY_TYPES[numberStr.slice(-1)]
|
||||||
|
const [idx, ...numbers] = numberStr.slice(0, -1).split('|')
|
||||||
|
return `NUMBER ${idx} [${numberType}]: ${numbers.join(', ')}`
|
||||||
|
})
|
||||||
|
)
|
||||||
|
NTFY.Send(successMessageArr.join('\n'), 'tada')
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() { setImmediate(this.#_start.bind(this)) }
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"id": "USERID",
|
||||||
|
"password": "PASSWORD",
|
||||||
|
"ntfyUrl": "URL",
|
||||||
|
"ntfyToken": "TOKEN",
|
||||||
|
"buyLottoPolicy": [
|
||||||
|
"auto",
|
||||||
|
"1,2,3,4,5,6"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
|
||||||
|
class Constants {
|
||||||
|
static BASE_URL = 'https://dhlottery.co.kr'
|
||||||
|
static API_URL = 'https://ol.dhlottery.co.kr'
|
||||||
|
static DEFAULT_SESSION_URL = `${Constants.BASE_URL}/gameResult.do?method=byWin&wiselog=H_C_1_1`
|
||||||
|
static SYSTEM_CHECK_URL = `${Constants.BASE_URL}/index_check.html`
|
||||||
|
static MAIN_PAGE_URL = `${Constants.BASE_URL}/common.do?method=main`
|
||||||
|
static LOGIN_REQUEST_URL = `${Constants.BASE_URL}/userSsl.do?method=login`
|
||||||
|
static BUY_PAGE_URL = `${Constants.API_URL}/olotto/game/game645.do`
|
||||||
|
static BUY_LOTTO_645_URL = `${Constants.API_URL}/olotto/game/execBuy.do`
|
||||||
|
static BUY_READY_SOCKET_URL = `${Constants.API_URL}/olotto/game/egovUserReadySocket.json`
|
||||||
|
static ROUND_INFO_URL = `${Constants.BASE_URL}/common.do?method=main`
|
||||||
|
|
||||||
|
static DEFAULT_TIMEOUT = 10 * 1000
|
||||||
|
|
||||||
|
static DEFAULT_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': Constants.BASE_URL,
|
||||||
|
'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': Constants.BASE_URL,
|
||||||
|
'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',
|
||||||
|
}
|
||||||
|
|
||||||
|
static LOTTO_BUY_TYPES = {
|
||||||
|
'1': '수동',
|
||||||
|
'2': '반자동',
|
||||||
|
'3': '자동'
|
||||||
|
}
|
||||||
|
|
||||||
|
static CONFIG_FILENAME = process.env.LOTTO_AUTOMATION_CONFIG_FILENAME ?? 'Config.json'
|
||||||
|
static CONFIG_PATH = `${process.env.LOTTO_AUTOMATION_CONFIG_DIR ?? '.'}/${Constants.CONFIG_FILENAME}`
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Constants
|
||||||
|
|
@ -0,0 +1,122 @@
|
||||||
|
|
||||||
|
const Axios = require('axios')
|
||||||
|
const Cheerio = require('cheerio')
|
||||||
|
|
||||||
|
const Constants = require('./constants')
|
||||||
|
|
||||||
|
class Lotto {
|
||||||
|
static #SessionID = undefined
|
||||||
|
static #LottoDirect = undefined
|
||||||
|
|
||||||
|
static #_request(method, url, data) {
|
||||||
|
const requestOpts = {
|
||||||
|
method,
|
||||||
|
url,
|
||||||
|
data,
|
||||||
|
headers: Constants.DEFAULT_HEADERS,
|
||||||
|
timeout: Constants.DEFAULT_TIMEOUT,
|
||||||
|
}
|
||||||
|
if(this.#SessionID)
|
||||||
|
requestOpts.headers.Cookie = this.#SessionID
|
||||||
|
return Axios(requestOpts).catch(err => err)
|
||||||
|
}
|
||||||
|
static async #_syncSession() {
|
||||||
|
const sessionResponse = await this.#_request('GET', Constants.DEFAULT_SESSION_URL)
|
||||||
|
if(sessionResponse instanceof Error)
|
||||||
|
return sessionResponse
|
||||||
|
|
||||||
|
if(sessionResponse.request.res.responseUrl === Constants.SYSTEM_CHECK_URL)
|
||||||
|
return new Error('동행복권 사이트가 현재 시스템 점검중입니다.')
|
||||||
|
|
||||||
|
const sessionId = sessionResponse.headers['set-cookie']
|
||||||
|
.find(cookieStr => cookieStr.indexOf('JSESSIONID') !== -1)
|
||||||
|
if(!sessionId)
|
||||||
|
return new Error('쿠키가 정상적으로 세팅되지 않았습니다.')
|
||||||
|
|
||||||
|
this.#SessionID = sessionId
|
||||||
|
}
|
||||||
|
static async #_getRound() {
|
||||||
|
const roundResponse = await this.#_request('GET', Constants.ROUND_INFO_URL)
|
||||||
|
if(roundResponse instanceof Error)
|
||||||
|
return roundResponse
|
||||||
|
|
||||||
|
const $ = Cheerio.load(roundResponse.data)
|
||||||
|
const lastDrawnRound = parseInt($("strong#lottoDrwNo").text(), 10)
|
||||||
|
const round = lastDrawnRound + 1
|
||||||
|
return round
|
||||||
|
}
|
||||||
|
|
||||||
|
static async Login(userId, userPw) {
|
||||||
|
const sessionSyncError = await this.#_syncSession()
|
||||||
|
if(sessionSyncError instanceof Error)
|
||||||
|
return sessionSyncError
|
||||||
|
|
||||||
|
const loginData = {
|
||||||
|
returnUrl: Constants.MAIN_PAGE_URL,
|
||||||
|
userId,
|
||||||
|
password: userPw,
|
||||||
|
checkSave: 'off',
|
||||||
|
newsEventYn: '',
|
||||||
|
}
|
||||||
|
const loginResult = await this.#_request('POST', Constants.LOGIN_REQUEST_URL, loginData)
|
||||||
|
if(loginResult instanceof Error)
|
||||||
|
return loginResult
|
||||||
|
|
||||||
|
const $ = Cheerio.load(loginResult.data)
|
||||||
|
const btnCommonElements = $('a.btn_common')
|
||||||
|
if(btnCommonElements.length > 0)
|
||||||
|
return new Error('로그인에 실패했습니다. 아이디 또는 비밀번호를 확인해주세요.')
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
static async Buy(lotteryInputs = []) {
|
||||||
|
if(lotteryInputs.length === 0)
|
||||||
|
return new Error('구매할 로또 정보를 입력해주세요.')
|
||||||
|
if(lotteryInputs.length > 5)
|
||||||
|
lotteryInputs = lotteryInputs.slice(0, 5)
|
||||||
|
|
||||||
|
const round = await this.#_getRound()
|
||||||
|
|
||||||
|
const readySocketResponse = await this.#_request('POST', Constants.BUY_READY_SOCKET_URL)
|
||||||
|
if(readySocketResponse instanceof Error)
|
||||||
|
return readySocketResponse
|
||||||
|
|
||||||
|
this.#LottoDirect = readySocketResponse.data.ready_ip
|
||||||
|
|
||||||
|
const requestParams = lotteryInputs.map((lotto, idx) => {
|
||||||
|
return {
|
||||||
|
genType: lotto === 'auto' ? '0' : '1',
|
||||||
|
arrGameChoiceNum: lotto === 'auto' ? null : lotto.map(num => String(num).padStart(2, 0)).join(','),
|
||||||
|
alpabet: String.fromCharCode(65 + idx)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const requestData = {
|
||||||
|
round: String(round),
|
||||||
|
direct: this.#LottoDirect,
|
||||||
|
nBuyAmount: `${requestParams.length * 1000}`,
|
||||||
|
param: JSON.stringify(requestParams),
|
||||||
|
gameCnt: `${requestParams.length}`,
|
||||||
|
}
|
||||||
|
const buyResponse = await this.#_request('POST', Constants.BUY_LOTTO_645_URL, requestData)
|
||||||
|
if(buyResponse instanceof Error)
|
||||||
|
return buyResponse
|
||||||
|
|
||||||
|
const { result } = buyResponse.data
|
||||||
|
|
||||||
|
if ((result.resultMsg || "FAILURE").toUpperCase() !== "SUCCESS")
|
||||||
|
return new Error(result.resultMsg)
|
||||||
|
|
||||||
|
return {
|
||||||
|
round: result.buyRound,
|
||||||
|
barcode: Object.keys(result)
|
||||||
|
.filter(k => k.startsWith('barCode'))
|
||||||
|
.map(k => result[k])
|
||||||
|
.join(' '),
|
||||||
|
cost: result.nBuyAmount,
|
||||||
|
numbers: result.arrGameChoiceNum,
|
||||||
|
message: result.resultMsg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Lotto
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
|
||||||
|
const Axios = require('axios')
|
||||||
|
|
||||||
|
class NTFY {
|
||||||
|
static #url
|
||||||
|
static #token
|
||||||
|
|
||||||
|
static Init(url, token) {
|
||||||
|
this.#url = url
|
||||||
|
this.#token = token
|
||||||
|
}
|
||||||
|
static Send(message, tags) {
|
||||||
|
if(!this.#url || !this.#token)
|
||||||
|
return
|
||||||
|
|
||||||
|
const requestOpts = {
|
||||||
|
method: 'POST',
|
||||||
|
url: this.#url,
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${this.#token}`,
|
||||||
|
Tags: tags,
|
||||||
|
},
|
||||||
|
data: message
|
||||||
|
}
|
||||||
|
return Axios(requestOpts).catch(err => err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = NTFY
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "lotto_automation",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Lottery Automation Service",
|
||||||
|
"main": "app.js",
|
||||||
|
"directories": {
|
||||||
|
"lib": "lib",
|
||||||
|
"test": "test"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "sunha.park (sunha321@gmail.com)",
|
||||||
|
"contributors": [
|
||||||
|
"sunha.park (sunha321@gmail.com)",
|
||||||
|
"hojin.jeong (a66764765@gmail.com)"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.11.0",
|
||||||
|
"cheerio": "^1.1.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue