@rich-automation/lotto 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -0
- package/lib/apis/dhlottery/getWinningNumbers.d.ts +1 -0
- package/lib/apis/dhlottery/getWinningNumbers.js +29 -0
- package/lib/apis/dhlottery/requestBuy.d.ts +0 -0
- package/lib/apis/dhlottery/requestBuy.js +11 -0
- package/lib/constants/dhlottery.d.ts +6 -0
- package/lib/constants/dhlottery.js +6 -0
- package/lib/constants/index.d.ts +10 -0
- package/lib/constants/index.js +10 -0
- package/lib/constants/selectors.d.ts +11 -0
- package/lib/constants/selectors.js +11 -0
- package/lib/constants/urls.d.ts +6 -0
- package/lib/constants/urls.js +6 -0
- package/lib/controllers/factory.d.ts +3 -0
- package/lib/controllers/factory.js +7 -0
- package/lib/controllers/puppeteer/index.d.ts +14 -0
- package/lib/controllers/puppeteer/index.js +71 -0
- package/lib/controllers/puppeteer/puppeteer.page.d.ts +16 -0
- package/lib/controllers/puppeteer/puppeteer.page.js +78 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/logger.d.ts +25 -0
- package/lib/logger.js +48 -0
- package/lib/lottoError.d.ts +36 -0
- package/lib/lottoError.js +68 -0
- package/lib/lottoService.d.ts +19 -0
- package/lib/lottoService.js +126 -0
- package/lib/types.d.ts +69 -0
- package/lib/types.js +1 -0
- package/lib/utils/checkWinning.d.ts +4 -0
- package/lib/utils/checkWinning.js +33 -0
- package/lib/utils/deferred.d.ts +8 -0
- package/lib/utils/deferred.js +23 -0
- package/lib/utils/getCheckWinningLink.d.ts +1 -0
- package/lib/utils/getCheckWinningLink.js +5 -0
- package/lib/utils/getCurrentLottoRound.d.ts +1 -0
- package/lib/utils/getCurrentLottoRound.js +7 -0
- package/lib/utils/invertObject.d.ts +6 -0
- package/lib/utils/invertObject.js +7 -0
- package/lib/utils/lazyRun.d.ts +1 -0
- package/lib/utils/lazyRun.js +11 -0
- package/lib/utils/seconds.d.ts +1 -0
- package/lib/utils/seconds.js +1 -0
- package/lib/utils/validateLottoNumber.d.ts +1 -0
- package/lib/utils/validateLottoNumber.js +12 -0
- package/lib/utils/validatePurchaseAvailability.d.ts +1 -0
- package/lib/utils/validatePurchaseAvailability.js +19 -0
- package/package.json +120 -0
package/README.md
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
# @rich-automation/lotto
|
|
2
|
+
|
|
3
|
+
[](https://github.com/rich-automation/lotto-module/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/gh/rich-automation/lotto-module)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getWinningNumbers: (volume: number) => Promise<number[]>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import axios from 'axios';
|
|
11
|
+
import LottoError from '../../lottoError';
|
|
12
|
+
export const getWinningNumbers = (volume) => __awaiter(void 0, void 0, void 0, function* () {
|
|
13
|
+
let res;
|
|
14
|
+
try {
|
|
15
|
+
res = yield axios.get(`https://www.dhlottery.co.kr/common.do?method=getLottoNumber&drwNo=${volume}`);
|
|
16
|
+
}
|
|
17
|
+
catch (_a) {
|
|
18
|
+
throw LottoError.NetworkError();
|
|
19
|
+
}
|
|
20
|
+
if (res.data.returnValue == 'success') {
|
|
21
|
+
return toOrderedWinningNumbers(res.data);
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
throw LottoError.InvalidRound();
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
function toOrderedWinningNumbers(data) {
|
|
28
|
+
return [data.drwtNo1, data.drwtNo2, data.drwtNo3, data.drwtNo4, data.drwtNo5, data.drwtNo6, data.bnusNo];
|
|
29
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// POST
|
|
3
|
+
// https://ol.dhlottery.co.kr/olotto/game/execBuy.do
|
|
4
|
+
// form
|
|
5
|
+
// {
|
|
6
|
+
// round: 1066,
|
|
7
|
+
// direct: 172.17.20.52,
|
|
8
|
+
// nBuyAmount: 1000,
|
|
9
|
+
// param: [{"genType": "0","arrGameChoiceNum": null, "alpabet": "A"}]
|
|
10
|
+
// gameCnt:1
|
|
11
|
+
// }
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const CONST: {
|
|
2
|
+
LAZY_RUN_DEFAULT: number;
|
|
3
|
+
BROWSER_INIT_RETRY_COUNT: number;
|
|
4
|
+
BROWSER_INIT_RETRY_TIMEOUT: number;
|
|
5
|
+
BROWSER_DESTROY_SAFE_TIMEOUT: number;
|
|
6
|
+
BROWSER_PAGE_POPUP_WAIT: number;
|
|
7
|
+
BROWSER_PAGE_DIALOG_WAIT: number;
|
|
8
|
+
WEEK_TO_MILLISECOND: number;
|
|
9
|
+
THOUSAND_ROUND_DATE: string;
|
|
10
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const CONST = {
|
|
2
|
+
LAZY_RUN_DEFAULT: 1000,
|
|
3
|
+
BROWSER_INIT_RETRY_COUNT: 10,
|
|
4
|
+
BROWSER_INIT_RETRY_TIMEOUT: 1000,
|
|
5
|
+
BROWSER_DESTROY_SAFE_TIMEOUT: 1000,
|
|
6
|
+
BROWSER_PAGE_POPUP_WAIT: 1500,
|
|
7
|
+
BROWSER_PAGE_DIALOG_WAIT: 10000,
|
|
8
|
+
WEEK_TO_MILLISECOND: 604800000,
|
|
9
|
+
THOUSAND_ROUND_DATE: '2022-01-29T11:50:00'
|
|
10
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const SELECTORS: {
|
|
2
|
+
ID_INPUT: string;
|
|
3
|
+
PWD_INPUT: string;
|
|
4
|
+
LOGIN_BUTTON: string;
|
|
5
|
+
PURCHASE_TYPE_RANDOM_BTN: string;
|
|
6
|
+
PURCHASE_AMOUNT_SELECT: string;
|
|
7
|
+
PURCHASE_AMOUNT_CONFIRM_BTN: string;
|
|
8
|
+
PURCHASE_BTN: string;
|
|
9
|
+
PURCHASE_CONFIRM_BTN: string;
|
|
10
|
+
PURCHASE_NUMBER_LIST: string;
|
|
11
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const SELECTORS = {
|
|
2
|
+
ID_INPUT: '#userId',
|
|
3
|
+
PWD_INPUT: '#article > div:nth-child(2) > div > form > div > div.inner > fieldset > div.form > input[type=password]:nth-child(2)',
|
|
4
|
+
LOGIN_BUTTON: '#article > div:nth-child(2) > div > form > div > div.inner > fieldset > div.form > a',
|
|
5
|
+
PURCHASE_TYPE_RANDOM_BTN: '#tabWay2Buy a#num2',
|
|
6
|
+
PURCHASE_AMOUNT_SELECT: 'select#amoundApply',
|
|
7
|
+
PURCHASE_AMOUNT_CONFIRM_BTN: '#btnSelectNum',
|
|
8
|
+
PURCHASE_BTN: '#btnBuy',
|
|
9
|
+
PURCHASE_CONFIRM_BTN: '#popupLayerConfirm .btns > input[value="확인"]',
|
|
10
|
+
PURCHASE_NUMBER_LIST: '#reportRow .nums'
|
|
11
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { BrowserConfigs, BrowserControllerInterface } from '../../types';
|
|
2
|
+
import { Browser } from 'puppeteer';
|
|
3
|
+
import { PuppeteerPage } from './puppeteer.page';
|
|
4
|
+
import { type LoggerInterface } from '../../logger';
|
|
5
|
+
export declare class PuppeteerController implements BrowserControllerInterface {
|
|
6
|
+
configs: BrowserConfigs;
|
|
7
|
+
logger: LoggerInterface;
|
|
8
|
+
browser: Browser;
|
|
9
|
+
constructor(configs: BrowserConfigs, logger: LoggerInterface);
|
|
10
|
+
private getBrowser;
|
|
11
|
+
focus: (pageIndex?: number) => Promise<PuppeteerPage>;
|
|
12
|
+
close: () => Promise<void>;
|
|
13
|
+
cleanPages: (remainingPageIndex: number[]) => Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import puppeteer, { Browser } from 'puppeteer';
|
|
11
|
+
import { deferred } from '../../utils/deferred';
|
|
12
|
+
import { PuppeteerPage } from './puppeteer.page';
|
|
13
|
+
import { CONST } from '../../constants';
|
|
14
|
+
import {} from '../../logger';
|
|
15
|
+
export class PuppeteerController {
|
|
16
|
+
constructor(configs, logger) {
|
|
17
|
+
this.getBrowser = () => __awaiter(this, void 0, void 0, function* () {
|
|
18
|
+
const p = deferred();
|
|
19
|
+
if (this.browser) {
|
|
20
|
+
p.resolve(this.browser);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
let retry = 0;
|
|
24
|
+
const interval = setInterval(() => {
|
|
25
|
+
if (CONST.BROWSER_INIT_RETRY_COUNT < retry) {
|
|
26
|
+
clearInterval(interval);
|
|
27
|
+
p.reject(new Error('Browser is not initialized'));
|
|
28
|
+
}
|
|
29
|
+
else if (this.browser) {
|
|
30
|
+
clearInterval(interval);
|
|
31
|
+
p.resolve(this.browser);
|
|
32
|
+
}
|
|
33
|
+
retry++;
|
|
34
|
+
}, CONST.BROWSER_INIT_RETRY_TIMEOUT);
|
|
35
|
+
}
|
|
36
|
+
return p.promise;
|
|
37
|
+
});
|
|
38
|
+
this.focus = (pageIndex = -1) => __awaiter(this, void 0, void 0, function* () {
|
|
39
|
+
const browser = yield this.getBrowser();
|
|
40
|
+
const pages = yield browser.pages();
|
|
41
|
+
if (pages.length === 0) {
|
|
42
|
+
const page = yield browser.newPage();
|
|
43
|
+
return new PuppeteerPage(page);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
const isWithinRange = Math.max(0, Math.min(pageIndex, pages.length - 1)) === pageIndex;
|
|
47
|
+
const page = pages.at(isWithinRange ? pageIndex : -1);
|
|
48
|
+
if (!page)
|
|
49
|
+
throw new Error('Page is not found');
|
|
50
|
+
return new PuppeteerPage(page);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
this.close = () => __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
const browser = yield this.getBrowser();
|
|
55
|
+
return browser.close();
|
|
56
|
+
});
|
|
57
|
+
this.cleanPages = (remainingPageIndex) => __awaiter(this, void 0, void 0, function* () {
|
|
58
|
+
const browser = yield this.getBrowser();
|
|
59
|
+
const pages = yield browser.pages();
|
|
60
|
+
const promises = pages.map((page, index) => __awaiter(this, void 0, void 0, function* () {
|
|
61
|
+
if (!remainingPageIndex.includes(index)) {
|
|
62
|
+
return page.close();
|
|
63
|
+
}
|
|
64
|
+
}));
|
|
65
|
+
yield Promise.all(promises);
|
|
66
|
+
});
|
|
67
|
+
this.configs = configs;
|
|
68
|
+
this.logger = logger;
|
|
69
|
+
puppeteer.launch(this.configs).then(browser => (this.browser = browser));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BrowserPageEvents, BrowserPageInterface, FakeDOMElement, StringifiedCookies } from '../../types';
|
|
2
|
+
import { Page } from 'puppeteer';
|
|
3
|
+
export declare class PuppeteerPage implements BrowserPageInterface {
|
|
4
|
+
page: Page;
|
|
5
|
+
constructor(page: Page);
|
|
6
|
+
url(): Promise<string>;
|
|
7
|
+
goto(url: string): Promise<void>;
|
|
8
|
+
fill(selector: string, value: string | number): Promise<void>;
|
|
9
|
+
click(selector: string): Promise<void>;
|
|
10
|
+
select(selector: string, value: string): Promise<void>;
|
|
11
|
+
querySelectorAll<T>(selector: string, callback: (elems: FakeDOMElement[]) => T): Promise<T>;
|
|
12
|
+
getCookies(): Promise<string>;
|
|
13
|
+
setCookies(cookies: StringifiedCookies): Promise<void>;
|
|
14
|
+
wait(param: 'idle' | 'load' | number): Promise<void>;
|
|
15
|
+
on(event: BrowserPageEvents, callback: (...args: unknown[]) => void): () => import("puppeteer").EventEmitter;
|
|
16
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import { Page } from 'puppeteer';
|
|
11
|
+
import { deferred } from '../../utils/deferred';
|
|
12
|
+
import { lazyRun } from '../../utils/lazyRun';
|
|
13
|
+
export class PuppeteerPage {
|
|
14
|
+
constructor(page) {
|
|
15
|
+
this.page = page;
|
|
16
|
+
}
|
|
17
|
+
url() {
|
|
18
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
19
|
+
return this.page.url();
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
goto(url) {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
yield this.page.goto(url, { waitUntil: 'load' });
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
fill(selector, value) {
|
|
28
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
29
|
+
yield this.page.type(selector, value.toString());
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
click(selector) {
|
|
33
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
34
|
+
yield this.page.click(selector);
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
select(selector, value) {
|
|
38
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
39
|
+
yield this.page.select(selector, value);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
querySelectorAll(selector, callback) {
|
|
43
|
+
return this.page.$$eval(selector, callback);
|
|
44
|
+
}
|
|
45
|
+
getCookies() {
|
|
46
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
47
|
+
const cookies = yield this.page.cookies();
|
|
48
|
+
return JSON.stringify(cookies);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
setCookies(cookies) {
|
|
52
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
53
|
+
const cookieParams = JSON.parse(cookies);
|
|
54
|
+
yield this.page.setCookie(...cookieParams);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
wait(param) {
|
|
58
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
59
|
+
const p = deferred();
|
|
60
|
+
if (param === 'idle') {
|
|
61
|
+
yield this.page.waitForNavigation({ waitUntil: 'networkidle0' });
|
|
62
|
+
p.resolve();
|
|
63
|
+
}
|
|
64
|
+
if (param === 'load') {
|
|
65
|
+
yield this.page.waitForNavigation({ waitUntil: 'load' });
|
|
66
|
+
p.resolve();
|
|
67
|
+
}
|
|
68
|
+
if (typeof param === 'number') {
|
|
69
|
+
yield lazyRun(() => __awaiter(this, void 0, void 0, function* () { return p.resolve(); }), param);
|
|
70
|
+
}
|
|
71
|
+
return p.promise;
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
on(event, callback) {
|
|
75
|
+
this.page.on(event, callback);
|
|
76
|
+
return () => this.page.off(event, callback);
|
|
77
|
+
}
|
|
78
|
+
}
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LottoService } from './lottoService';
|
package/lib/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { LottoService } from './lottoService';
|
package/lib/logger.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export declare enum LogLevel {
|
|
2
|
+
NONE = -1,
|
|
3
|
+
ERROR = 0,
|
|
4
|
+
WARN = 1,
|
|
5
|
+
INFO = 2,
|
|
6
|
+
DEBUG = 3
|
|
7
|
+
}
|
|
8
|
+
export interface LoggerInterface {
|
|
9
|
+
setLogLevel(logLevel: LogLevel): void;
|
|
10
|
+
error(...args: unknown[]): void;
|
|
11
|
+
warn(...args: unknown[]): void;
|
|
12
|
+
info(...args: unknown[]): void;
|
|
13
|
+
debug(...args: unknown[]): void;
|
|
14
|
+
}
|
|
15
|
+
export default class Logger implements LoggerInterface {
|
|
16
|
+
private _logLevel;
|
|
17
|
+
private _prefix;
|
|
18
|
+
constructor(logLevel?: LogLevel, prefix?: string);
|
|
19
|
+
setLogLevel(logLevel: LogLevel): void;
|
|
20
|
+
get logLevel(): LogLevel;
|
|
21
|
+
error(...args: unknown[]): void;
|
|
22
|
+
warn(...args: unknown[]): void;
|
|
23
|
+
info(...args: unknown[]): void;
|
|
24
|
+
debug(...args: unknown[]): void;
|
|
25
|
+
}
|
package/lib/logger.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import utc from 'dayjs/plugin/utc';
|
|
3
|
+
import timezone from 'dayjs/plugin/timezone';
|
|
4
|
+
dayjs.extend(utc);
|
|
5
|
+
dayjs.extend(timezone);
|
|
6
|
+
export var LogLevel;
|
|
7
|
+
(function (LogLevel) {
|
|
8
|
+
LogLevel[LogLevel["NONE"] = -1] = "NONE";
|
|
9
|
+
LogLevel[LogLevel["ERROR"] = 0] = "ERROR";
|
|
10
|
+
LogLevel[LogLevel["WARN"] = 1] = "WARN";
|
|
11
|
+
LogLevel[LogLevel["INFO"] = 2] = "INFO";
|
|
12
|
+
LogLevel[LogLevel["DEBUG"] = 3] = "DEBUG";
|
|
13
|
+
})(LogLevel || (LogLevel = {}));
|
|
14
|
+
export default class Logger {
|
|
15
|
+
constructor(logLevel = LogLevel.INFO, prefix = '') {
|
|
16
|
+
this._logLevel = logLevel;
|
|
17
|
+
this._prefix = prefix;
|
|
18
|
+
}
|
|
19
|
+
setLogLevel(logLevel) {
|
|
20
|
+
this._logLevel = logLevel;
|
|
21
|
+
}
|
|
22
|
+
get logLevel() {
|
|
23
|
+
return this._logLevel;
|
|
24
|
+
}
|
|
25
|
+
error(...args) {
|
|
26
|
+
if (this._logLevel >= LogLevel.ERROR) {
|
|
27
|
+
console.error(printNow(), this._prefix, ...args);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
warn(...args) {
|
|
31
|
+
if (this._logLevel >= LogLevel.WARN) {
|
|
32
|
+
console.warn(printNow(), this._prefix, ...args);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
info(...args) {
|
|
36
|
+
if (this._logLevel >= LogLevel.INFO) {
|
|
37
|
+
console.info(printNow(), this._prefix, ...args);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
debug(...args) {
|
|
41
|
+
if (this._logLevel >= LogLevel.DEBUG) {
|
|
42
|
+
console.debug(printNow(), this._prefix, ...args);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function printNow() {
|
|
47
|
+
return dayjs.tz(Date.now(), 'Asia/Seoul').format('YYYY/MM/DD HH:mm:ss');
|
|
48
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
declare const ErrorCode: {
|
|
2
|
+
INVALID_ROUND: 300001;
|
|
3
|
+
INVALID_LOTTO_NUMBER: 300002;
|
|
4
|
+
NOT_AUTHENTICATED: 300003;
|
|
5
|
+
PURCHASE_UNAVAILABLE: 300004;
|
|
6
|
+
CREDENTIALS_INCORRECT: 200001;
|
|
7
|
+
INVALID_COOKIE: 200002;
|
|
8
|
+
NETWORK_ERROR: 100001;
|
|
9
|
+
UNKNOWN_ERROR: 199999;
|
|
10
|
+
};
|
|
11
|
+
type ErrorCodeNumber = (typeof ErrorCode)[keyof typeof ErrorCode];
|
|
12
|
+
export default class LottoError extends Error {
|
|
13
|
+
static NetworkError(): LottoError;
|
|
14
|
+
static UnknownError(): LottoError;
|
|
15
|
+
static CredentialsIncorrect(): LottoError;
|
|
16
|
+
static InvalidCookies(): LottoError;
|
|
17
|
+
static InvalidRound(): LottoError;
|
|
18
|
+
static InvalidLottoNumber(): LottoError;
|
|
19
|
+
static NotAuthenticated(): LottoError;
|
|
20
|
+
static PurchaseUnavailable(): LottoError;
|
|
21
|
+
static get code(): {
|
|
22
|
+
INVALID_ROUND: 300001;
|
|
23
|
+
INVALID_LOTTO_NUMBER: 300002;
|
|
24
|
+
NOT_AUTHENTICATED: 300003;
|
|
25
|
+
PURCHASE_UNAVAILABLE: 300004;
|
|
26
|
+
CREDENTIALS_INCORRECT: 200001;
|
|
27
|
+
INVALID_COOKIE: 200002;
|
|
28
|
+
NETWORK_ERROR: 100001;
|
|
29
|
+
UNKNOWN_ERROR: 199999;
|
|
30
|
+
};
|
|
31
|
+
static getMessage(code: ErrorCodeNumber): string;
|
|
32
|
+
static getName(code: ErrorCodeNumber): "INVALID_ROUND" | "INVALID_LOTTO_NUMBER" | "NOT_AUTHENTICATED" | "PURCHASE_UNAVAILABLE" | "CREDENTIALS_INCORRECT" | "INVALID_COOKIE" | "NETWORK_ERROR" | "UNKNOWN_ERROR";
|
|
33
|
+
code: number;
|
|
34
|
+
constructor(code: ErrorCodeNumber, message?: string);
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { invertObject } from './utils/invertObject';
|
|
2
|
+
const BaseErrorCode = {
|
|
3
|
+
NETWORK_ERROR: 100001,
|
|
4
|
+
UNKNOWN_ERROR: 199999
|
|
5
|
+
};
|
|
6
|
+
const LoginErrorCode = {
|
|
7
|
+
CREDENTIALS_INCORRECT: 200001,
|
|
8
|
+
INVALID_COOKIE: 200002
|
|
9
|
+
};
|
|
10
|
+
const LottoErrorCode = {
|
|
11
|
+
INVALID_ROUND: 300001,
|
|
12
|
+
INVALID_LOTTO_NUMBER: 300002,
|
|
13
|
+
NOT_AUTHENTICATED: 300003,
|
|
14
|
+
PURCHASE_UNAVAILABLE: 300004
|
|
15
|
+
};
|
|
16
|
+
const ErrorCode = Object.assign(Object.assign(Object.assign({}, BaseErrorCode), LoginErrorCode), LottoErrorCode);
|
|
17
|
+
const ErrorName = invertObject(ErrorCode);
|
|
18
|
+
const ErrorMessage = {
|
|
19
|
+
[ErrorCode.NETWORK_ERROR]: '네트워크 에러가 발생했습니다.',
|
|
20
|
+
[ErrorCode.UNKNOWN_ERROR]: '알 수 없는 오류가 발생했습니다.',
|
|
21
|
+
[ErrorCode.CREDENTIALS_INCORRECT]: '아이디 혹은 비밀번호가 일치하지 않습니다.',
|
|
22
|
+
[ErrorCode.INVALID_COOKIE]: '쿠키가 만료됐거나 유효하지 않습니다.',
|
|
23
|
+
[ErrorCode.INVALID_ROUND]: '로또 회차가 올바르지 않습니다.',
|
|
24
|
+
[ErrorCode.INVALID_LOTTO_NUMBER]: '로또 번호가 올바르지 않습니다.',
|
|
25
|
+
[ErrorCode.NOT_AUTHENTICATED]: '인증되지 않았습니다.',
|
|
26
|
+
[ErrorCode.PURCHASE_UNAVAILABLE]: '현재는 로또 구매가 불가능합니다.'
|
|
27
|
+
};
|
|
28
|
+
export default class LottoError extends Error {
|
|
29
|
+
static NetworkError() {
|
|
30
|
+
return new LottoError(ErrorCode.NETWORK_ERROR);
|
|
31
|
+
}
|
|
32
|
+
static UnknownError() {
|
|
33
|
+
return new LottoError(ErrorCode.UNKNOWN_ERROR);
|
|
34
|
+
}
|
|
35
|
+
static CredentialsIncorrect() {
|
|
36
|
+
return new LottoError(ErrorCode.CREDENTIALS_INCORRECT);
|
|
37
|
+
}
|
|
38
|
+
static InvalidCookies() {
|
|
39
|
+
return new LottoError(ErrorCode.INVALID_COOKIE);
|
|
40
|
+
}
|
|
41
|
+
static InvalidRound() {
|
|
42
|
+
return new LottoError(ErrorCode.INVALID_ROUND);
|
|
43
|
+
}
|
|
44
|
+
static InvalidLottoNumber() {
|
|
45
|
+
return new LottoError(ErrorCode.INVALID_LOTTO_NUMBER);
|
|
46
|
+
}
|
|
47
|
+
static NotAuthenticated() {
|
|
48
|
+
return new LottoError(ErrorCode.NOT_AUTHENTICATED);
|
|
49
|
+
}
|
|
50
|
+
static PurchaseUnavailable() {
|
|
51
|
+
return new LottoError(ErrorCode.PURCHASE_UNAVAILABLE);
|
|
52
|
+
}
|
|
53
|
+
static get code() {
|
|
54
|
+
return ErrorCode;
|
|
55
|
+
}
|
|
56
|
+
static getMessage(code) {
|
|
57
|
+
return ErrorMessage[code];
|
|
58
|
+
}
|
|
59
|
+
static getName(code) {
|
|
60
|
+
return ErrorName[code];
|
|
61
|
+
}
|
|
62
|
+
constructor(code, message) {
|
|
63
|
+
var _a;
|
|
64
|
+
super(message !== null && message !== void 0 ? message : ErrorMessage[code]);
|
|
65
|
+
this.name = (_a = ErrorName[code]) !== null && _a !== void 0 ? _a : 'LottoError';
|
|
66
|
+
this.code = code;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { BrowserConfigs, BrowserControllerInterface, LottoServiceInterface } from './types';
|
|
2
|
+
import { type LoggerInterface } from './logger';
|
|
3
|
+
export declare class LottoService implements LottoServiceInterface {
|
|
4
|
+
context: {
|
|
5
|
+
authenticated: boolean;
|
|
6
|
+
};
|
|
7
|
+
browserController: BrowserControllerInterface;
|
|
8
|
+
logger: LoggerInterface;
|
|
9
|
+
constructor(configs?: BrowserConfigs);
|
|
10
|
+
destroy: () => Promise<void>;
|
|
11
|
+
signInWithCookie: (cookies: string) => Promise<string>;
|
|
12
|
+
signIn: (id: string, password: string) => Promise<string>;
|
|
13
|
+
purchase: (amount?: number) => Promise<number[][]>;
|
|
14
|
+
check: (numbers: number[], round?: number) => Promise<{
|
|
15
|
+
rank: number;
|
|
16
|
+
matchedNumbers: number[];
|
|
17
|
+
}>;
|
|
18
|
+
getCheckWinningLink: (round: number, numbers: number[][]) => string;
|
|
19
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
import LottoError from './lottoError';
|
|
11
|
+
import { SELECTORS } from './constants/selectors';
|
|
12
|
+
import { createBrowserController } from './controllers/factory';
|
|
13
|
+
import { URLS } from './constants/urls';
|
|
14
|
+
import { deferred } from './utils/deferred';
|
|
15
|
+
import { CONST } from './constants';
|
|
16
|
+
import { lazyRun } from './utils/lazyRun';
|
|
17
|
+
import Logger, {} from './logger';
|
|
18
|
+
import { getCurrentLottoRound } from './utils/getCurrentLottoRound';
|
|
19
|
+
import { validateLottoNumber } from './utils/validateLottoNumber';
|
|
20
|
+
import { getWinningNumbers } from './apis/dhlottery/getWinningNumbers';
|
|
21
|
+
import { checkWinning } from './utils/checkWinning';
|
|
22
|
+
import { validatePurchaseAvailability } from './utils/validatePurchaseAvailability';
|
|
23
|
+
import { getCheckWinningLink } from './utils/getCheckWinningLink';
|
|
24
|
+
export class LottoService {
|
|
25
|
+
constructor(configs) {
|
|
26
|
+
this.context = {
|
|
27
|
+
authenticated: false
|
|
28
|
+
};
|
|
29
|
+
this.destroy = () => __awaiter(this, void 0, void 0, function* () {
|
|
30
|
+
return lazyRun(this.browserController.close, CONST.BROWSER_DESTROY_SAFE_TIMEOUT);
|
|
31
|
+
});
|
|
32
|
+
this.signInWithCookie = (cookies) => __awaiter(this, void 0, void 0, function* () {
|
|
33
|
+
// 쿠키 설정 & 페이지 이동
|
|
34
|
+
const page = yield this.browserController.focus(0);
|
|
35
|
+
this.logger.debug('[signInWithCookie]', 'setCookies');
|
|
36
|
+
yield page.setCookies(cookies);
|
|
37
|
+
this.logger.debug('[signInWithCookie]', 'goto', 'login page');
|
|
38
|
+
yield page.goto(URLS.LOGIN);
|
|
39
|
+
this.logger.debug('[signInWithCookie]', 'page url', yield page.url());
|
|
40
|
+
// 팝업 제거용
|
|
41
|
+
this.logger.debug('[signInWithCookie]', 'clear popups');
|
|
42
|
+
yield page.wait(CONST.BROWSER_PAGE_POPUP_WAIT);
|
|
43
|
+
yield this.browserController.cleanPages([0]);
|
|
44
|
+
// 로그인이 되지 않았으면 에러처리
|
|
45
|
+
if (URLS.LOGIN === (yield page.url())) {
|
|
46
|
+
this.logger.info('[signInWithCookie]', 'failure');
|
|
47
|
+
throw LottoError.InvalidCookies();
|
|
48
|
+
}
|
|
49
|
+
this.logger.info('[signInWithCookie]', 'success');
|
|
50
|
+
this.context.authenticated = true;
|
|
51
|
+
return page.getCookies();
|
|
52
|
+
});
|
|
53
|
+
this.signIn = (id, password) => __awaiter(this, void 0, void 0, function* () {
|
|
54
|
+
const p = deferred();
|
|
55
|
+
queueMicrotask(() => __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
// 페이지 이동
|
|
57
|
+
const page = yield this.browserController.focus(0);
|
|
58
|
+
this.logger.debug('[signIn]', 'goto', 'login page');
|
|
59
|
+
yield page.goto(URLS.LOGIN);
|
|
60
|
+
this.logger.debug('[signIn]', 'page url', yield page.url());
|
|
61
|
+
const unsubscribe = page.on('response', (response) => __awaiter(this, void 0, void 0, function* () {
|
|
62
|
+
const url = response.url();
|
|
63
|
+
switch (true) {
|
|
64
|
+
// 로그인 실패
|
|
65
|
+
case url.includes(URLS.LOGIN.replace('https://', '')): {
|
|
66
|
+
this.logger.info('[signIn]', 'fallback to login page', 'failure');
|
|
67
|
+
unsubscribe();
|
|
68
|
+
p.reject(LottoError.CredentialsIncorrect());
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
// 로그인 성공
|
|
72
|
+
case url.includes(URLS.MAIN.replace('https://', '')): {
|
|
73
|
+
this.logger.info('[signIn]', 'fallback to main page', 'success');
|
|
74
|
+
this.context.authenticated = true;
|
|
75
|
+
unsubscribe();
|
|
76
|
+
this.logger.debug('[signIn]', 'clear popups');
|
|
77
|
+
yield page.wait(CONST.BROWSER_PAGE_POPUP_WAIT);
|
|
78
|
+
yield this.browserController.cleanPages([0]);
|
|
79
|
+
const cookies = yield page.getCookies();
|
|
80
|
+
p.resolve(cookies);
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}));
|
|
85
|
+
// 로그인 시도
|
|
86
|
+
this.logger.debug('[signIn]', 'try login');
|
|
87
|
+
yield page.fill(SELECTORS.ID_INPUT, id);
|
|
88
|
+
yield page.fill(SELECTORS.PWD_INPUT, password);
|
|
89
|
+
yield page.click(SELECTORS.LOGIN_BUTTON);
|
|
90
|
+
}));
|
|
91
|
+
return p.promise;
|
|
92
|
+
});
|
|
93
|
+
this.purchase = (amount = 5) => __awaiter(this, void 0, void 0, function* () {
|
|
94
|
+
if (!this.context.authenticated)
|
|
95
|
+
throw LottoError.NotAuthenticated();
|
|
96
|
+
validatePurchaseAvailability();
|
|
97
|
+
// move
|
|
98
|
+
const page = yield this.browserController.focus(0);
|
|
99
|
+
yield page.goto(URLS.LOTTO_645);
|
|
100
|
+
// click auto button
|
|
101
|
+
yield page.click(SELECTORS.PURCHASE_TYPE_RANDOM_BTN);
|
|
102
|
+
// set and confirm amount
|
|
103
|
+
const amountString = String(Math.max(1, Math.min(5, amount)));
|
|
104
|
+
yield page.select(SELECTORS.PURCHASE_AMOUNT_SELECT, amountString);
|
|
105
|
+
yield page.click(SELECTORS.PURCHASE_AMOUNT_CONFIRM_BTN);
|
|
106
|
+
// click purchase button
|
|
107
|
+
yield page.click(SELECTORS.PURCHASE_BTN);
|
|
108
|
+
yield page.click(SELECTORS.PURCHASE_CONFIRM_BTN);
|
|
109
|
+
yield page.wait(1000);
|
|
110
|
+
// game result
|
|
111
|
+
return page.querySelectorAll(SELECTORS.PURCHASE_NUMBER_LIST, elems => {
|
|
112
|
+
return elems.map(it => Array.from(it.children).map(child => Number(child.innerHTML)));
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
this.check = (numbers, round = getCurrentLottoRound()) => __awaiter(this, void 0, void 0, function* () {
|
|
116
|
+
validateLottoNumber(numbers);
|
|
117
|
+
const winningNumbers = yield getWinningNumbers(round);
|
|
118
|
+
return checkWinning(numbers, winningNumbers);
|
|
119
|
+
});
|
|
120
|
+
this.getCheckWinningLink = (round, numbers) => {
|
|
121
|
+
return getCheckWinningLink(round, numbers);
|
|
122
|
+
};
|
|
123
|
+
this.logger = new Logger(configs === null || configs === void 0 ? void 0 : configs.logLevel, '[LottoService]');
|
|
124
|
+
this.browserController = createBrowserController('puppeteer', Object.assign({ headless: false, defaultViewport: { width: 1080, height: 1024 } }, configs), this.logger);
|
|
125
|
+
}
|
|
126
|
+
}
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { LogLevel } from './logger';
|
|
2
|
+
export interface LottoServiceInterface {
|
|
3
|
+
destroy(): Promise<void>;
|
|
4
|
+
signIn(id: string, password: string): Promise<string>;
|
|
5
|
+
signInWithCookie(cookie: string): Promise<string>;
|
|
6
|
+
check(numbers: number[], volume?: number): Promise<{
|
|
7
|
+
rank: number;
|
|
8
|
+
matchedNumbers: number[];
|
|
9
|
+
}>;
|
|
10
|
+
purchase(amount: number): Promise<number[][]>;
|
|
11
|
+
getCheckWinningLink(round: number, numbers: number[][]): string;
|
|
12
|
+
}
|
|
13
|
+
export interface BrowserConfigs {
|
|
14
|
+
logLevel?: LogLevel;
|
|
15
|
+
headless?: boolean;
|
|
16
|
+
defaultViewport?: {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
[key: string]: unknown;
|
|
21
|
+
args?: string[];
|
|
22
|
+
}
|
|
23
|
+
export interface BrowserControllerInterface {
|
|
24
|
+
configs: BrowserConfigs;
|
|
25
|
+
focus(pageIndex?: number): Promise<BrowserPageInterface>;
|
|
26
|
+
cleanPages(remainingPageIndex: number[]): Promise<void>;
|
|
27
|
+
close(): Promise<void>;
|
|
28
|
+
}
|
|
29
|
+
export interface BrowserPageInterface {
|
|
30
|
+
url(): Promise<string>;
|
|
31
|
+
goto(url: string): Promise<void>;
|
|
32
|
+
fill(selector: string, value: string | number): Promise<void>;
|
|
33
|
+
click(selector: string): Promise<void>;
|
|
34
|
+
select(selector: string, value: string): Promise<void>;
|
|
35
|
+
querySelectorAll<T>(selector: string, callback: (elems: FakeDOMElement[]) => T): Promise<T>;
|
|
36
|
+
wait(time: number): Promise<void>;
|
|
37
|
+
wait(type: 'load'): Promise<void>;
|
|
38
|
+
wait(type: 'idle'): Promise<void>;
|
|
39
|
+
getCookies(): Promise<StringifiedCookies>;
|
|
40
|
+
setCookies(cookies: StringifiedCookies): Promise<void>;
|
|
41
|
+
on(event: BrowserPageEvents, callback: (...args: any[]) => void): Unsubscribe;
|
|
42
|
+
}
|
|
43
|
+
export type FakeDOMElement = {
|
|
44
|
+
className: string;
|
|
45
|
+
innerHTML: string;
|
|
46
|
+
children: FakeDOMElement[];
|
|
47
|
+
};
|
|
48
|
+
export type BrowserPageEvents = 'load' | 'close' | 'dialog' | 'response';
|
|
49
|
+
export type Unsubscribe = () => void;
|
|
50
|
+
export type StringifiedCookies = string;
|
|
51
|
+
export type GetWinningNumbersResponse = {
|
|
52
|
+
returnValue: 'success' | 'fail';
|
|
53
|
+
drwtNo1: number;
|
|
54
|
+
drwtNo2: number;
|
|
55
|
+
drwtNo3: number;
|
|
56
|
+
drwtNo4: number;
|
|
57
|
+
drwtNo5: number;
|
|
58
|
+
drwtNo6: number;
|
|
59
|
+
bnusNo: number;
|
|
60
|
+
};
|
|
61
|
+
declare global {
|
|
62
|
+
namespace NodeJS {
|
|
63
|
+
interface ProcessEnv {
|
|
64
|
+
LOTTO_ID: string;
|
|
65
|
+
LOTTO_PWD: string;
|
|
66
|
+
LOTTO_COOKIE: string;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
package/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
export const checkWinning = (myNumber, winningNumbers) => __awaiter(void 0, void 0, void 0, function* () {
|
|
11
|
+
const mainWinningNumbers = winningNumbers.slice(0, 6);
|
|
12
|
+
const bonusNumber = winningNumbers.at(-1);
|
|
13
|
+
const matchedNumbers = myNumber.filter(n => mainWinningNumbers.includes(n));
|
|
14
|
+
const matchingNumbersCount = matchedNumbers.length;
|
|
15
|
+
let rank = 0;
|
|
16
|
+
if (matchingNumbersCount === 6) {
|
|
17
|
+
rank = 1;
|
|
18
|
+
}
|
|
19
|
+
else if (matchingNumbersCount === 5 && myNumber.includes(bonusNumber)) {
|
|
20
|
+
matchedNumbers.push(bonusNumber);
|
|
21
|
+
rank = 2;
|
|
22
|
+
}
|
|
23
|
+
else if (matchingNumbersCount === 5) {
|
|
24
|
+
rank = 3;
|
|
25
|
+
}
|
|
26
|
+
else if (matchingNumbersCount === 4) {
|
|
27
|
+
rank = 4;
|
|
28
|
+
}
|
|
29
|
+
else if (matchingNumbersCount === 3) {
|
|
30
|
+
rank = 5;
|
|
31
|
+
}
|
|
32
|
+
return { rank, matchedNumbers };
|
|
33
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const deferred = () => {
|
|
2
|
+
let state = 'pending';
|
|
3
|
+
let resolve;
|
|
4
|
+
let reject;
|
|
5
|
+
const promise = new Promise((res, rej) => {
|
|
6
|
+
resolve = (value) => {
|
|
7
|
+
state = 'fulfilled';
|
|
8
|
+
res(value);
|
|
9
|
+
};
|
|
10
|
+
reject = (reason) => {
|
|
11
|
+
state = 'rejected';
|
|
12
|
+
rej(reason);
|
|
13
|
+
};
|
|
14
|
+
});
|
|
15
|
+
return {
|
|
16
|
+
get state() {
|
|
17
|
+
return state;
|
|
18
|
+
},
|
|
19
|
+
promise,
|
|
20
|
+
resolve,
|
|
21
|
+
reject
|
|
22
|
+
};
|
|
23
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function getCheckWinningLink(round: number, numbers: number[][]): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const getCurrentLottoRound: () => number;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CONST } from '../constants';
|
|
2
|
+
export const getCurrentLottoRound = () => {
|
|
3
|
+
const standardDate = new Date(CONST.THOUSAND_ROUND_DATE);
|
|
4
|
+
const now = new Date(global.Date.now());
|
|
5
|
+
const ADDITIONAL_ROUND = Math.floor((now.getTime() - standardDate.getTime()) / CONST.WEEK_TO_MILLISECOND);
|
|
6
|
+
return 1000 + ADDITIONAL_ROUND;
|
|
7
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function lazyRun<T extends () => Promise<void>>(callback: T, timeout?: number): Promise<void>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { CONST } from '../constants';
|
|
2
|
+
import { deferred } from './deferred';
|
|
3
|
+
export function lazyRun(callback, timeout = CONST.LAZY_RUN_DEFAULT) {
|
|
4
|
+
const p = deferred();
|
|
5
|
+
setTimeout(() => {
|
|
6
|
+
callback()
|
|
7
|
+
.then(() => p.resolve())
|
|
8
|
+
.catch((e) => p.reject(e));
|
|
9
|
+
}, timeout);
|
|
10
|
+
return p.promise;
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const seconds: (n: number) => number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const seconds = (n) => 1000 * n;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const validateLottoNumber: (numbers: number[]) => void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import LottoError from '../lottoError';
|
|
2
|
+
export const validateLottoNumber = (numbers) => {
|
|
3
|
+
const isValid = numbers
|
|
4
|
+
.filter(number => {
|
|
5
|
+
return typeof number === 'number' && Math.max(number, 45) === 45 && Math.min(number, 1) === 1;
|
|
6
|
+
})
|
|
7
|
+
.filter((number, index) => numbers.indexOf(number) === index)
|
|
8
|
+
.filter(number => Number.isInteger(number)).length === 6;
|
|
9
|
+
if (!isValid) {
|
|
10
|
+
throw LottoError.InvalidLottoNumber();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const validatePurchaseAvailability: () => void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
import utc from 'dayjs/plugin/utc';
|
|
3
|
+
import timezone from 'dayjs/plugin/timezone';
|
|
4
|
+
import LottoError from '../lottoError';
|
|
5
|
+
dayjs.extend(utc);
|
|
6
|
+
dayjs.extend(timezone);
|
|
7
|
+
const getSeoulTime = () => dayjs.tz(Date.now(), 'Asia/Seoul');
|
|
8
|
+
const resetTime = (dayjs) => dayjs.hour(0).minute(0).second(0).millisecond(0);
|
|
9
|
+
// 평일/일요일: 06:00 ~ 24:00
|
|
10
|
+
// 토요일: 06:00 ~ 20:00
|
|
11
|
+
export const validatePurchaseAvailability = () => {
|
|
12
|
+
const now = getSeoulTime();
|
|
13
|
+
const isSaturday = now.day() === 6;
|
|
14
|
+
const openingTime = resetTime(getSeoulTime()).hour(6);
|
|
15
|
+
const closingTime = isSaturday ? resetTime(getSeoulTime()).hour(20) : resetTime(getSeoulTime()).hour(24);
|
|
16
|
+
if (now.isBefore(openingTime) || now.isAfter(closingTime)) {
|
|
17
|
+
throw LottoError.PurchaseUnavailable();
|
|
18
|
+
}
|
|
19
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@rich-automation/lotto",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Lotto module",
|
|
5
|
+
"publishConfig": {
|
|
6
|
+
"access": "public"
|
|
7
|
+
},
|
|
8
|
+
"contributors": [
|
|
9
|
+
{
|
|
10
|
+
"name": "bang9",
|
|
11
|
+
"url": "https://github.com/bang9"
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
"name": "DongGukMon",
|
|
15
|
+
"url": "https://github.com/DongGukMon"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"type": "module",
|
|
19
|
+
"main": "lib/index.js",
|
|
20
|
+
"types": "lib/index.d.ts",
|
|
21
|
+
"files": [
|
|
22
|
+
"lib",
|
|
23
|
+
"package.json",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"packageManager": "yarn@1.22.19",
|
|
27
|
+
"scripts": {
|
|
28
|
+
"start": "ts-node ./example/app",
|
|
29
|
+
"build": "rm -rf lib && tsc --build",
|
|
30
|
+
"test": "jest --forceExit --detectOpenHandles",
|
|
31
|
+
"install:chrome": "node ./node_modules/puppeteer/install.js",
|
|
32
|
+
"fix": "yarn fix:eslint && yarn fix:prettier",
|
|
33
|
+
"fix:eslint": "eslint --fix src --ext js,jsx,ts,tsx ",
|
|
34
|
+
"fix:prettier": "prettier --write \"src/**/*.{ts,tsx,js}\"",
|
|
35
|
+
"lint": "yarn lint:ts && yarn lint:eslint && yarn lint:prettier",
|
|
36
|
+
"lint:ts": "tsc --noEmit",
|
|
37
|
+
"lint:eslint": "eslint src --ext js,jsx,ts,tsx ",
|
|
38
|
+
"lint:prettier": "prettier --check \"src/**/*.{ts,tsx,js}\""
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"axios": "^1.3.5",
|
|
42
|
+
"dayjs": "^1.11.7",
|
|
43
|
+
"puppeteer": "^19.8.5"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@babel/core": "^7.21.4",
|
|
47
|
+
"@babel/preset-env": "^7.21.4",
|
|
48
|
+
"@babel/preset-typescript": "^7.21.4",
|
|
49
|
+
"@types/jest": "^29.5.0",
|
|
50
|
+
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
|
51
|
+
"@typescript-eslint/parser": "^5.58.0",
|
|
52
|
+
"babel-jest": "^29.5.0",
|
|
53
|
+
"dotenv": "^16.0.3",
|
|
54
|
+
"eslint": "^8.38.0",
|
|
55
|
+
"eslint-config-prettier": "^8.8.0",
|
|
56
|
+
"eslint-plugin-prettier": "^4.2.1",
|
|
57
|
+
"jest": "^29.5.0",
|
|
58
|
+
"prettier": "^2.8.7",
|
|
59
|
+
"ts-node": "^10.9.1",
|
|
60
|
+
"typescript": "^5.0.4"
|
|
61
|
+
},
|
|
62
|
+
"eslintConfig": {
|
|
63
|
+
"root": true,
|
|
64
|
+
"extends": [
|
|
65
|
+
"eslint:recommended",
|
|
66
|
+
"plugin:@typescript-eslint/recommended",
|
|
67
|
+
"eslint-config-prettier"
|
|
68
|
+
],
|
|
69
|
+
"plugins": [
|
|
70
|
+
"@typescript-eslint",
|
|
71
|
+
"eslint-plugin-prettier"
|
|
72
|
+
],
|
|
73
|
+
"parser": "@typescript-eslint/parser",
|
|
74
|
+
"parserOptions": {
|
|
75
|
+
"ecmaVersion": 2018,
|
|
76
|
+
"sourceType": "module"
|
|
77
|
+
},
|
|
78
|
+
"rules": {
|
|
79
|
+
"@typescript-eslint/no-unused-vars": [
|
|
80
|
+
"error",
|
|
81
|
+
{
|
|
82
|
+
"argsIgnorePattern": "^_",
|
|
83
|
+
"varsIgnorePattern": "^_"
|
|
84
|
+
}
|
|
85
|
+
],
|
|
86
|
+
"@typescript-eslint/no-namespace": "off",
|
|
87
|
+
"prettier/prettier": [
|
|
88
|
+
"error",
|
|
89
|
+
{
|
|
90
|
+
"printWidth": 120,
|
|
91
|
+
"tabWidth": 2,
|
|
92
|
+
"useTabs": false,
|
|
93
|
+
"semi": true,
|
|
94
|
+
"singleQuote": true,
|
|
95
|
+
"trailingComma": "none",
|
|
96
|
+
"bracketSpacing": true,
|
|
97
|
+
"arrowParens": "avoid",
|
|
98
|
+
"bracketSameLine": false,
|
|
99
|
+
"proseWrap": "never"
|
|
100
|
+
}
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
"ignorePatterns": [
|
|
104
|
+
"node_modules/",
|
|
105
|
+
"lib/"
|
|
106
|
+
]
|
|
107
|
+
},
|
|
108
|
+
"prettier": {
|
|
109
|
+
"printWidth": 120,
|
|
110
|
+
"tabWidth": 2,
|
|
111
|
+
"useTabs": false,
|
|
112
|
+
"semi": true,
|
|
113
|
+
"singleQuote": true,
|
|
114
|
+
"trailingComma": "none",
|
|
115
|
+
"bracketSpacing": true,
|
|
116
|
+
"arrowParens": "avoid",
|
|
117
|
+
"bracketSameLine": false,
|
|
118
|
+
"proseWrap": "never"
|
|
119
|
+
}
|
|
120
|
+
}
|