@rich-automation/lotto 1.0.0 → 2.0.0

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 CHANGED
@@ -1,15 +1,14 @@
1
1
  # @rich-automation/lotto
2
2
 
3
- [![npm](https://img.shields.io/npm/v/@rich-automation/lotto.svg?style=popout&colorB=yellow)](https://www.npmjs.com/package/@rich-automation/lotto)
4
- [![ci](https://github.com/rich-automation/lotto-module/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/rich-automation/lotto-module/actions/workflows/ci.yml)
3
+ [![npm](https://img.shields.io/npm/v/@rich-automation/lotto.svg?style=popout&colorB=yellow)](https://www.npmjs.com/package/@rich-automation/lotto)
4
+ [![ci](https://github.com/rich-automation/lotto-module/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/rich-automation/lotto-module/actions/workflows/ci.yml)
5
5
  [![codecov](https://codecov.io/gh/rich-automation/lotto-module/branch/main/graph/badge.svg?token=18IAW1OW77)](https://codecov.io/gh/rich-automation/lotto-module)
6
6
 
7
- `@rich-automation/lotto` 패키지는 headless browser를 이용해 js환경에서 자동으로 로또를 구매할 수 있는 인터페이스를 제공합니다.
7
+ `@rich-automation/lotto`는 headless browser를 활용해 JS 환경에서 로또를 자동으로 구매할 수 있는 인터페이스를 제공합니다.
8
8
 
9
+ ## 설치
9
10
 
10
-
11
- ## Installation
12
- ```shell
11
+ ```bash
13
12
  # npm
14
13
  npm install @rich-automation/lotto
15
14
 
@@ -17,105 +16,119 @@ npm install @rich-automation/lotto
17
16
  yarn add @rich-automation/lotto
18
17
  ```
19
18
 
20
- ## Preparation
21
- 1. 내부적으로 playwright/puppeteer를 사용하므로 머신에 chromium 을 미리 설치해주셔야 합니다. ([link](https://github.com/rich-automation/lotto-module/blob/main/package.json#L38-L39))
22
- 2. [동행복권](https://dhlottery.co.kr/common.do?method=main) 사이트를 통해 구매가 이루어지며 예치금 충전은 지원하지 않으므로 미리 동행복권 계정에 예치금을 충전해두어야합니다.
19
+ ## 준비 사항
20
+
21
+ 1. 내부적으로 Playwright 또는 Puppeteer를 사용하므로, Chromium이 사전에 설치되어 있어야 합니다. ([링크](https://github.com/rich-automation/lotto-module/blob/main/package.json#L38-L39))
22
+ 2. 구매는 [동행복권](https://dhlottery.co.kr/common.do?method=main) 사이트에서 진행되며, 예치금 충전 기능은 없으므로 미리 계정에 예치금을 충전해두어야 합니다.
23
+
24
+ ## 사용법
25
+
26
+ ### 기본 설정
23
27
 
24
- ## Usage
25
- ### 공통
26
- lottoService 객체 인스턴스를 생성합니다.
27
28
  ```js
28
29
  import { LottoService } from '@rich-automation/lotto';
30
+ import { chromium } from 'playwright';
31
+ import puppeteer from 'puppeteer';
29
32
 
30
- const lottoService = new LottoService();
31
- //...
33
+ // playwright 시작, 모든 기능 사용 가능
34
+ const lottoService = new LottoService({
35
+ controller: chromium // puppeteer
36
+ });
37
+
38
+ // puppeteer 로 시작, 모든 기능 사용 가능
39
+ const lottoService = new LottoService({
40
+ controller: puppeteer
41
+ });
42
+
43
+ // API 모드로 시작, check / getCheckWinningLink 기능만 사용 가능
44
+ const lottoService = new LottoService({
45
+ controller: 'api'
46
+ });
32
47
  ```
33
- LottoService 클래스의 생성자는 BrowserConfigs를 인수로 받을 수 있습니다. 인수로 전달된 configs는 다음과 같은 속성을 가질 수 있습니다:
34
48
 
35
- - `controller(default: 'playwright')`: 내부적으로 어떤 컨트롤러(puppeteer/playwright)를 사용할 지 지정할 수 있습니다.
36
- - `headless(default: false)`: 브라우저의 헤드리스 모드 여부를 설정합니다.
37
- - `defaultViewport(default: { width: 1080, height: 1024 })`: 브라우저의 기본 뷰포트 크기를 설정합니다.
38
- - `logLevel(default:2)`:콘솔 로그 수준을 설정합니다. (NONE = -1, ERROR = 0, WARN = 1, INFO = 2, DEBUG = 3)
49
+ 옵션 설명:
50
+
51
+ - `controller`: (필수) `puppeteer`, `playwright` 모듈 또는 `"api"`
52
+ - `headless`: 기본값 `false`
53
+ - `defaultViewport`: 기본값 `{ width: 1080, height: 1024 }`
54
+ - `logLevel`: 기본값 `2` (NONE = -1, ERROR = 0, WARN = 1, INFO = 2, DEBUG = 3)
55
+
39
56
  ```js
40
- //example
41
- import {LottoService, LogLevel} from '@rich-automation/lotto'
57
+ import { LottoService, LogLevel } from '@rich-automation/lotto';
42
58
 
43
59
  const lottoService = new LottoService({
60
+ controller: chromium,
44
61
  headless: true,
45
- defaultViewport: { width: 1280, height: 720 },
62
+ defaultViewport: { width: 1280, height: 720 },
46
63
  logLevel: LogLevel.DEBUG
47
64
  });
48
65
  ```
49
- ### 구매
50
- ```js
51
- import {LottoService} from "@rich-automation/lotto";
52
66
 
53
- const ID = "<YOUR_ID>";
54
- const PWD = "<YOUR_PASSWORD>";
67
+ ### 로또 구매
55
68
 
56
- const lottoService = new LottoService({
57
- headless:true
58
- });
69
+ ```js
70
+ const ID = '<YOUR_ID>';
71
+ const PWD = '<YOUR_PASSWORD>';
59
72
 
60
- await lottoService.signIn(ID,PWD);
73
+ await lottoService.signIn(ID, PWD);
61
74
 
62
75
  const numbers = await lottoService.purchase(5);
63
- console.log(numbers); //[[ 1, 14, 21, 27, 30, 44 ],[ 4, 5, 27, 29, 40, 44 ],[ 9, 18, 19, 24, 38, 42 ],[ 4, 6, 13, 20, 38, 39 ],[ 8, 9, 10, 19, 32, 40 ]]
64
-
65
-
66
76
 
77
+ console.log(numbers); // [[ 1, 14, 21, 27, 30, 44 ],[ 4, 5, 27, 29, 40, 44 ],[ 9, 18, 19, 24, 38, 42 ],[ 4, 6, 13, 20, 38, 39 ],[ 8, 9, 10, 19, 32, 40 ]]
67
78
  ```
68
79
 
69
80
  ### 당첨 확인
70
- 이번 회차 당첨 확인
71
- ```js
72
- import {getLastLottoRound,LottoService} from "@rich-automation/lotto";
73
81
 
74
- const numbers = "[[1,2,3,4,5,6],[5,6,7,8,9,10]";
82
+ 이번 회차 확인:
75
83
 
76
- const lottoService = new LottoService({
77
- headless:true
78
- });
84
+ ```js
85
+ import { getLastLottoRound } from '@rich-automation/lotto';
86
+
87
+ const numbers = [
88
+ [1, 2, 3, 4, 5, 6],
89
+ [5, 6, 7, 8, 9, 10]
90
+ ];
79
91
 
80
92
  const currentRound = getLastLottoRound();
81
93
 
82
- const result = await lottoService.check(numbers,currentRound)
83
- console.log(result) //[{rank:1,matchedNumbers:[1,2,3,4,5,6]},{rank:5,matchedNumbers:[5,6]]
84
- ```
85
- 다음 회차 당첨 링크 생성
86
- ```js
87
- import {getNextLottoRound,LottoService} from "@rich-automation/lotto";
94
+ const result = await lottoService.check(numbers, currentRound);
88
95
 
89
- const numbers = "[[1,2,3,4,5,6],[5,6,7,8,9,10]";
96
+ console.log(result); // [{rank:1,matchedNumbers:[1,2,3,4,5,6]},{rank:5,matchedNumbers:[5,6]]
97
+ ```
90
98
 
99
+ 다음 회차 링크 생성:
91
100
 
92
- const lottoService = new LottoService({
93
- headless:true
94
- });
101
+ ```js
102
+ import { getNextLottoRound } from '@rich-automation/lotto';
95
103
 
96
104
  const nextRound = getNextLottoRound();
97
- const link = lottoService.getCheckWinningLink(numbers,nextRound);
98
- console.log(link) //"https://dhlottery.co.kr/qr.do?method=winQr&v=1071q010203040506q050607080910";
105
+ const link = lottoService.getCheckWinningLink(numbers, nextRound);
99
106
 
107
+ console.log(link); // "https://dhlottery.co.kr/qr.do?method=winQr&v=1071q010203040506q050607080910";
100
108
  ```
101
109
 
110
+ ## API
111
+
112
+ ### `signIn(id: string, password: string): Promise<string>`
113
+
114
+ 동행복권 로그인, 성공 시 로그인 쿠키 반환
115
+
116
+ ### `signInWithCookie(cookies: string): Promise<string>`
117
+
118
+ 쿠키 기반 로그인
119
+
120
+ ### `purchase(amount?: number): Promise<number[][]>`
121
+
122
+ 로또 번호 자동 구매 (1~5 게임)
123
+
124
+ ### `check(numbers: number[][], round?: number): Promise<{ rank:number; matchedNumbers:number[] }[]>`
125
+
126
+ (API) 당첨 결과 확인
127
+
128
+ ### `getCheckWinningLink(numbers: number[][], round?: number): string`
129
+
130
+ (API) 당첨 확인 링크 생성
131
+
132
+ ### `destroy(): Promise<void>`
102
133
 
103
- ## Method
104
- ### signIn
105
- - `(id: string, password: string) => Promise<string>`
106
- - description: id와 pwd를 입력받아 동행복권에 로그인합니다. 성공할경우 로그인 쿠키를 반환합니다.
107
- ### signInWithCookie
108
- - `(cookies: string) => Promise<string>`
109
- - description: 로그인 쿠키를 입력받아 동행복권에 로그인합니다. 성공할경우 로그인 쿠키를 반환합니다.
110
- ### purchase
111
- - `(amount?: number) => Promise<number[][]>`
112
- - description: 구매할 게임 횟수를 입력받아 로또를 구매하고, 구매한 번호를 이차원 배열 형태로 반환합니다. amount는 1~5사이 값을 가집니다.
113
- ### check
114
- - `(numbers: number[][], round?: number) => Promise<{ rank:number; matchedNumbers:number[] }[]>`
115
- - description: 회차와 해당 회차에 구매한 로또번호를 입력받아 당첨 등수(rank)와 맞춘 번호(matchedNumbers)목록을 반환합니다. 회차를 지정하지 않으면 최신 회차를 기준으로 확인합니다.
116
- ### getCheckWinningLink
117
- - `(numbers: number[][], round?: number) => string`
118
- - description: 회차와 구매한 로또 번호를 입력받아 당첨 확인 링크를 생성합니다. 로또 번호는 해당 회차에 구매한 모든 게임을 이차원 배열 형태로 입력받습니다. 회차를 지정하지 않으면 다음 회차를 기준으로 링크를 생성합니다.
119
- ### destroy
120
- - `() => Promise<void>`
121
- - description: LottoService 인스턴스에서 사용한 브라우저 컨트롤러를 종료합니다.
134
+ 브라우저 인스턴스 종료
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.APIModeController = void 0;
13
+ class APIModeController {
14
+ constructor(configs, logger) {
15
+ this.focus = () => __awaiter(this, void 0, void 0, function* () {
16
+ throw new Error('APIModeController does not support focus method');
17
+ });
18
+ this.close = () => __awaiter(this, void 0, void 0, function* () {
19
+ this.logger.warn('APIModeController does not support close method');
20
+ });
21
+ this.cleanPages = () => __awaiter(this, void 0, void 0, function* () {
22
+ this.logger.warn('APIModeController does not support cleanPages method');
23
+ });
24
+ this.logger = logger;
25
+ this.configs = configs;
26
+ }
27
+ }
28
+ exports.APIModeController = APIModeController;
@@ -3,12 +3,26 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createBrowserController = void 0;
4
4
  const puppeteer_1 = require("./puppeteer");
5
5
  const playwright_1 = require("./playwright");
6
- function createBrowserController(name, configs, logger) {
7
- switch (name) {
8
- case 'puppeteer':
9
- return new puppeteer_1.PuppeteerController(configs, logger);
10
- case 'playwright':
11
- return new playwright_1.PlaywrightController(configs, logger);
6
+ const api_1 = require("./api");
7
+ function createBrowserController(configs, logger) {
8
+ if (isAPIMode(configs)) {
9
+ return new api_1.APIModeController(configs, logger);
12
10
  }
11
+ if (isPlaywright(configs)) {
12
+ return new playwright_1.PlaywrightController(configs, logger);
13
+ }
14
+ if (isPuppeteer(configs)) {
15
+ return new puppeteer_1.PuppeteerController(configs, logger);
16
+ }
17
+ throw new Error('Invalid browser controller');
13
18
  }
14
19
  exports.createBrowserController = createBrowserController;
20
+ function isAPIMode(configs) {
21
+ return configs.controller === 'api';
22
+ }
23
+ function isPlaywright(configs) {
24
+ return typeof configs.controller !== 'string' && 'connectOverCDP' in configs.controller;
25
+ }
26
+ function isPuppeteer(configs) {
27
+ return typeof configs.controller !== 'string' && 'executablePath' in configs.controller;
28
+ }
@@ -10,7 +10,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.PlaywrightController = void 0;
13
- const playwright_1 = require("playwright");
14
13
  const deferred_1 = require("../../utils/deferred");
15
14
  const playwright_page_1 = require("./playwright.page");
16
15
  const constants_1 = require("../../constants");
@@ -69,7 +68,7 @@ class PlaywrightController {
69
68
  });
70
69
  this.configs = configs;
71
70
  this.logger = logger;
72
- playwright_1.chromium.launch(this.configs).then((browser) => __awaiter(this, void 0, void 0, function* () {
71
+ this.configs.controller.launch(this.configs).then((browser) => __awaiter(this, void 0, void 0, function* () {
73
72
  this.browser = browser;
74
73
  this.context = yield browser.newContext();
75
74
  }));
@@ -8,12 +8,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  step((generator = generator.apply(thisArg, _arguments || [])).next());
9
9
  });
10
10
  };
11
- var __importDefault = (this && this.__importDefault) || function (mod) {
12
- return (mod && mod.__esModule) ? mod : { "default": mod };
13
- };
14
11
  Object.defineProperty(exports, "__esModule", { value: true });
15
12
  exports.PuppeteerController = void 0;
16
- const puppeteer_1 = __importDefault(require("puppeteer"));
17
13
  const deferred_1 = require("../../utils/deferred");
18
14
  const puppeteer_page_1 = require("./puppeteer.page");
19
15
  const constants_1 = require("../../constants");
@@ -71,8 +67,8 @@ class PuppeteerController {
71
67
  });
72
68
  this.configs = configs;
73
69
  this.logger = logger;
74
- puppeteer_1.default
75
- .launch(Object.assign(Object.assign({}, this.configs), { headless: this.configs.headless === true ? 'new' : this.configs.headless }))
70
+ configs.controller
71
+ .launch(Object.assign(Object.assign({}, this.configs), { headless: this.configs.headless === true }))
76
72
  .then((browser) => __awaiter(this, void 0, void 0, function* () {
77
73
  this.browser = browser;
78
74
  }));
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const invertObject_1 = require("./utils/invertObject");
4
4
  const BaseErrorCode = {
5
5
  NETWORK_ERROR: 100001,
6
+ NOT_SUPPORTED: 110000,
6
7
  UNKNOWN_ERROR: 199999
7
8
  };
8
9
  const LoginErrorCode = {
@@ -25,7 +26,8 @@ const ErrorMessage = {
25
26
  [ErrorCode.INVALID_ROUND]: '로또 회차가 올바르지 않습니다.',
26
27
  [ErrorCode.INVALID_LOTTO_NUMBER]: '로또 번호가 올바르지 않습니다.',
27
28
  [ErrorCode.NOT_AUTHENTICATED]: '인증되지 않았습니다.',
28
- [ErrorCode.PURCHASE_UNAVAILABLE]: '현재는 로또 구매가 불가능합니다.'
29
+ [ErrorCode.PURCHASE_UNAVAILABLE]: '현재는 로또 구매가 불가능합니다.',
30
+ [ErrorCode.NOT_SUPPORTED]: '지원되지 않는 기능입니다.'
29
31
  };
30
32
  class LottoError extends Error {
31
33
  static NetworkError() {
@@ -52,6 +54,9 @@ class LottoError extends Error {
52
54
  static PurchaseUnavailable() {
53
55
  return new LottoError(ErrorCode.PURCHASE_UNAVAILABLE);
54
56
  }
57
+ static NotSupported(message) {
58
+ return new LottoError(ErrorCode.NOT_SUPPORTED, message);
59
+ }
55
60
  static get code() {
56
61
  return ErrorCode;
57
62
  }
@@ -30,14 +30,19 @@ const getCheckWinningLink_1 = require("./utils/getCheckWinningLink");
30
30
  const getNextLottoRound_1 = require("./utils/getNextLottoRound");
31
31
  class LottoService {
32
32
  constructor(configs) {
33
- var _a;
34
33
  this.context = {
35
34
  authenticated: false
36
35
  };
37
36
  this.destroy = () => __awaiter(this, void 0, void 0, function* () {
37
+ if (this.browserController.configs.controller === 'api') {
38
+ throw lottoError_1.default.NotSupported('API mode does not support destroy.');
39
+ }
38
40
  return (0, lazyRun_1.lazyRun)(this.browserController.close, constants_1.CONST.BROWSER_DESTROY_SAFE_TIMEOUT);
39
41
  });
40
42
  this.signInWithCookie = (cookies) => __awaiter(this, void 0, void 0, function* () {
43
+ if (this.browserController.configs.controller === 'api') {
44
+ throw lottoError_1.default.NotSupported('API mode does not support signInWithCookie.');
45
+ }
41
46
  // 쿠키 설정 & 페이지 이동
42
47
  const page = yield this.browserController.focus(0);
43
48
  this.logger.debug('[signInWithCookie]', 'setCookies');
@@ -59,6 +64,9 @@ class LottoService {
59
64
  return page.getCookies();
60
65
  });
61
66
  this.signIn = (id, password) => __awaiter(this, void 0, void 0, function* () {
67
+ if (this.browserController.configs.controller === 'api') {
68
+ throw lottoError_1.default.NotSupported('API mode does not support signIn.');
69
+ }
62
70
  const p = (0, deferred_1.deferred)();
63
71
  queueMicrotask(() => __awaiter(this, void 0, void 0, function* () {
64
72
  // 페이지 이동
@@ -99,6 +107,9 @@ class LottoService {
99
107
  return p.promise;
100
108
  });
101
109
  this.purchase = (amount = 5) => __awaiter(this, void 0, void 0, function* () {
110
+ if (this.browserController.configs.controller === 'api') {
111
+ throw lottoError_1.default.NotSupported('API mode does not support purchase.');
112
+ }
102
113
  if (!this.context.authenticated)
103
114
  throw lottoError_1.default.NotAuthenticated();
104
115
  this.logger.debug('[purchase]', 'validatePurchaseAvailability');
@@ -142,8 +153,8 @@ class LottoService {
142
153
  this.logger.debug('[getCheckWinningLink]', 'getCheckWinningLink');
143
154
  return (0, getCheckWinningLink_1.getCheckWinningLink)(numbers, round);
144
155
  };
145
- this.logger = new logger_1.default(configs === null || configs === void 0 ? void 0 : configs.logLevel, '[LottoService]');
146
- this.browserController = (0, factory_1.createBrowserController)((_a = configs === null || configs === void 0 ? void 0 : configs.controller) !== null && _a !== void 0 ? _a : 'playwright', Object.assign({ defaultViewport: { width: 1080, height: 1024 } }, configs), this.logger);
156
+ this.logger = new logger_1.default(configs.logLevel, '[LottoService]');
157
+ this.browserController = (0, factory_1.createBrowserController)(Object.assign({ defaultViewport: { width: 1080, height: 1024 } }, configs), this.logger);
147
158
  }
148
159
  }
149
160
  exports.LottoService = LottoService;
@@ -0,0 +1,25 @@
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 {} from '../../logger';
11
+ export class APIModeController {
12
+ constructor(configs, logger) {
13
+ this.focus = () => __awaiter(this, void 0, void 0, function* () {
14
+ throw new Error('APIModeController does not support focus method');
15
+ });
16
+ this.close = () => __awaiter(this, void 0, void 0, function* () {
17
+ this.logger.warn('APIModeController does not support close method');
18
+ });
19
+ this.cleanPages = () => __awaiter(this, void 0, void 0, function* () {
20
+ this.logger.warn('APIModeController does not support cleanPages method');
21
+ });
22
+ this.logger = logger;
23
+ this.configs = configs;
24
+ }
25
+ }
@@ -1,10 +1,24 @@
1
1
  import { PuppeteerController } from './puppeteer';
2
2
  import { PlaywrightController } from './playwright';
3
- export function createBrowserController(name, configs, logger) {
4
- switch (name) {
5
- case 'puppeteer':
6
- return new PuppeteerController(configs, logger);
7
- case 'playwright':
8
- return new PlaywrightController(configs, logger);
3
+ import { APIModeController } from './api';
4
+ export function createBrowserController(configs, logger) {
5
+ if (isAPIMode(configs)) {
6
+ return new APIModeController(configs, logger);
9
7
  }
8
+ if (isPlaywright(configs)) {
9
+ return new PlaywrightController(configs, logger);
10
+ }
11
+ if (isPuppeteer(configs)) {
12
+ return new PuppeteerController(configs, logger);
13
+ }
14
+ throw new Error('Invalid browser controller');
15
+ }
16
+ function isAPIMode(configs) {
17
+ return configs.controller === 'api';
18
+ }
19
+ function isPlaywright(configs) {
20
+ return typeof configs.controller !== 'string' && 'connectOverCDP' in configs.controller;
21
+ }
22
+ function isPuppeteer(configs) {
23
+ return typeof configs.controller !== 'string' && 'executablePath' in configs.controller;
10
24
  }
@@ -7,7 +7,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { chromium } from 'playwright';
11
10
  import { deferred } from '../../utils/deferred';
12
11
  import { PlaywrightPage } from './playwright.page';
13
12
  import { CONST } from '../../constants';
@@ -67,7 +66,7 @@ export class PlaywrightController {
67
66
  });
68
67
  this.configs = configs;
69
68
  this.logger = logger;
70
- chromium.launch(this.configs).then((browser) => __awaiter(this, void 0, void 0, function* () {
69
+ this.configs.controller.launch(this.configs).then((browser) => __awaiter(this, void 0, void 0, function* () {
71
70
  this.browser = browser;
72
71
  this.context = yield browser.newContext();
73
72
  }));
@@ -7,7 +7,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import puppeteer, { Browser } from 'puppeteer';
11
10
  import { deferred } from '../../utils/deferred';
12
11
  import { PuppeteerPage } from './puppeteer.page';
13
12
  import { CONST } from '../../constants';
@@ -66,8 +65,8 @@ export class PuppeteerController {
66
65
  });
67
66
  this.configs = configs;
68
67
  this.logger = logger;
69
- puppeteer
70
- .launch(Object.assign(Object.assign({}, this.configs), { headless: this.configs.headless === true ? 'new' : this.configs.headless }))
68
+ configs.controller
69
+ .launch(Object.assign(Object.assign({}, this.configs), { headless: this.configs.headless === true }))
71
70
  .then((browser) => __awaiter(this, void 0, void 0, function* () {
72
71
  this.browser = browser;
73
72
  }));
@@ -7,7 +7,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
7
7
  step((generator = generator.apply(thisArg, _arguments || [])).next());
8
8
  });
9
9
  };
10
- import { Page } from 'puppeteer';
11
10
  import { deferred } from '../../utils/deferred';
12
11
  import { lazyRun } from '../../utils/lazyRun';
13
12
  export class PuppeteerPage {
@@ -1,6 +1,7 @@
1
1
  import { invertObject } from './utils/invertObject';
2
2
  const BaseErrorCode = {
3
3
  NETWORK_ERROR: 100001,
4
+ NOT_SUPPORTED: 110000,
4
5
  UNKNOWN_ERROR: 199999
5
6
  };
6
7
  const LoginErrorCode = {
@@ -23,7 +24,8 @@ const ErrorMessage = {
23
24
  [ErrorCode.INVALID_ROUND]: '로또 회차가 올바르지 않습니다.',
24
25
  [ErrorCode.INVALID_LOTTO_NUMBER]: '로또 번호가 올바르지 않습니다.',
25
26
  [ErrorCode.NOT_AUTHENTICATED]: '인증되지 않았습니다.',
26
- [ErrorCode.PURCHASE_UNAVAILABLE]: '현재는 로또 구매가 불가능합니다.'
27
+ [ErrorCode.PURCHASE_UNAVAILABLE]: '현재는 로또 구매가 불가능합니다.',
28
+ [ErrorCode.NOT_SUPPORTED]: '지원되지 않는 기능입니다.'
27
29
  };
28
30
  export default class LottoError extends Error {
29
31
  static NetworkError() {
@@ -50,6 +52,9 @@ export default class LottoError extends Error {
50
52
  static PurchaseUnavailable() {
51
53
  return new LottoError(ErrorCode.PURCHASE_UNAVAILABLE);
52
54
  }
55
+ static NotSupported(message) {
56
+ return new LottoError(ErrorCode.NOT_SUPPORTED, message);
57
+ }
53
58
  static get code() {
54
59
  return ErrorCode;
55
60
  }
@@ -24,14 +24,19 @@ import { getCheckWinningLink } from './utils/getCheckWinningLink';
24
24
  import { getNextLottoRound } from './utils/getNextLottoRound';
25
25
  export class LottoService {
26
26
  constructor(configs) {
27
- var _a;
28
27
  this.context = {
29
28
  authenticated: false
30
29
  };
31
30
  this.destroy = () => __awaiter(this, void 0, void 0, function* () {
31
+ if (this.browserController.configs.controller === 'api') {
32
+ throw LottoError.NotSupported('API mode does not support destroy.');
33
+ }
32
34
  return lazyRun(this.browserController.close, CONST.BROWSER_DESTROY_SAFE_TIMEOUT);
33
35
  });
34
36
  this.signInWithCookie = (cookies) => __awaiter(this, void 0, void 0, function* () {
37
+ if (this.browserController.configs.controller === 'api') {
38
+ throw LottoError.NotSupported('API mode does not support signInWithCookie.');
39
+ }
35
40
  // 쿠키 설정 & 페이지 이동
36
41
  const page = yield this.browserController.focus(0);
37
42
  this.logger.debug('[signInWithCookie]', 'setCookies');
@@ -53,6 +58,9 @@ export class LottoService {
53
58
  return page.getCookies();
54
59
  });
55
60
  this.signIn = (id, password) => __awaiter(this, void 0, void 0, function* () {
61
+ if (this.browserController.configs.controller === 'api') {
62
+ throw LottoError.NotSupported('API mode does not support signIn.');
63
+ }
56
64
  const p = deferred();
57
65
  queueMicrotask(() => __awaiter(this, void 0, void 0, function* () {
58
66
  // 페이지 이동
@@ -93,6 +101,9 @@ export class LottoService {
93
101
  return p.promise;
94
102
  });
95
103
  this.purchase = (amount = 5) => __awaiter(this, void 0, void 0, function* () {
104
+ if (this.browserController.configs.controller === 'api') {
105
+ throw LottoError.NotSupported('API mode does not support purchase.');
106
+ }
96
107
  if (!this.context.authenticated)
97
108
  throw LottoError.NotAuthenticated();
98
109
  this.logger.debug('[purchase]', 'validatePurchaseAvailability');
@@ -136,7 +147,7 @@ export class LottoService {
136
147
  this.logger.debug('[getCheckWinningLink]', 'getCheckWinningLink');
137
148
  return getCheckWinningLink(numbers, round);
138
149
  };
139
- this.logger = new Logger(configs === null || configs === void 0 ? void 0 : configs.logLevel, '[LottoService]');
140
- this.browserController = createBrowserController((_a = configs === null || configs === void 0 ? void 0 : configs.controller) !== null && _a !== void 0 ? _a : 'playwright', Object.assign({ defaultViewport: { width: 1080, height: 1024 } }, configs), this.logger);
150
+ this.logger = new Logger(configs.logLevel, '[LottoService]');
151
+ this.browserController = createBrowserController(Object.assign({ defaultViewport: { width: 1080, height: 1024 } }, configs), this.logger);
141
152
  }
142
153
  }
@@ -0,0 +1,13 @@
1
+ import type { BrowserConfigs, BrowserControllerInterface } from '../../types';
2
+ import type { Browser, BrowserContext } from 'playwright';
3
+ import { type LoggerInterface } from '../../logger';
4
+ export declare class APIModeController implements BrowserControllerInterface {
5
+ configs: BrowserConfigs;
6
+ logger: LoggerInterface;
7
+ browser: Browser;
8
+ context: BrowserContext;
9
+ constructor(configs: BrowserConfigs, logger: LoggerInterface);
10
+ focus: () => Promise<never>;
11
+ close: () => Promise<void>;
12
+ cleanPages: () => Promise<void>;
13
+ }
@@ -1,3 +1,3 @@
1
- import type { BrowserConfigs, BrowserControllerInterface } from '../types';
1
+ import type { BrowserConfigs, BrowserController, BrowserControllerInterface } from '../types';
2
2
  import type { LoggerInterface } from '../logger';
3
- export declare function createBrowserController(name: 'puppeteer' | 'playwright', configs: BrowserConfigs, logger: LoggerInterface): BrowserControllerInterface;
3
+ export declare function createBrowserController<T extends BrowserController>(configs: BrowserConfigs<T>, logger: LoggerInterface): BrowserControllerInterface;
@@ -1,13 +1,14 @@
1
1
  import type { BrowserConfigs, BrowserControllerInterface } from '../../types';
2
2
  import type { Browser, BrowserContext } from 'playwright';
3
+ import type { BrowserType } from 'playwright-core';
3
4
  import { PlaywrightPage } from './playwright.page';
4
5
  import { type LoggerInterface } from '../../logger';
5
- export declare class PlaywrightController implements BrowserControllerInterface {
6
- configs: BrowserConfigs;
6
+ export declare class PlaywrightController implements BrowserControllerInterface<BrowserType> {
7
+ configs: BrowserConfigs<BrowserType>;
7
8
  logger: LoggerInterface;
8
9
  browser: Browser;
9
10
  context: BrowserContext;
10
- constructor(configs: BrowserConfigs, logger: LoggerInterface);
11
+ constructor(configs: BrowserConfigs<BrowserType>, logger: LoggerInterface);
11
12
  private getBrowserContext;
12
13
  focus: (pageIndex?: number) => Promise<PlaywrightPage>;
13
14
  close: () => Promise<void>;
@@ -1,12 +1,12 @@
1
1
  import type { BrowserConfigs, BrowserControllerInterface } from '../../types';
2
- import { Browser } from 'puppeteer';
3
2
  import { PuppeteerPage } from './puppeteer.page';
4
3
  import { type LoggerInterface } from '../../logger';
5
- export declare class PuppeteerController implements BrowserControllerInterface {
6
- configs: BrowserConfigs;
4
+ import type { PuppeteerNode, Browser } from 'puppeteer';
5
+ export declare class PuppeteerController implements BrowserControllerInterface<PuppeteerNode> {
6
+ configs: BrowserConfigs<PuppeteerNode>;
7
7
  logger: LoggerInterface;
8
8
  browser: Browser;
9
- constructor(configs: BrowserConfigs, logger: LoggerInterface);
9
+ constructor(configs: BrowserConfigs<PuppeteerNode>, logger: LoggerInterface);
10
10
  private getBrowser;
11
11
  focus: (pageIndex?: number) => Promise<PuppeteerPage>;
12
12
  close: () => Promise<void>;
@@ -1,5 +1,5 @@
1
1
  import type { BrowserPageEvents, BrowserPageInterface, FakeDOMElement, StringifiedCookies } from '../../types';
2
- import { Page } from 'puppeteer';
2
+ import type { Page } from 'puppeteer';
3
3
  import type { LoggerInterface } from '../../logger';
4
4
  export declare class PuppeteerPage implements BrowserPageInterface {
5
5
  page: Page;
@@ -6,6 +6,7 @@ declare const ErrorCode: {
6
6
  CREDENTIALS_INCORRECT: 200001;
7
7
  INVALID_COOKIE: 200002;
8
8
  NETWORK_ERROR: 100001;
9
+ NOT_SUPPORTED: 110000;
9
10
  UNKNOWN_ERROR: 199999;
10
11
  };
11
12
  type ErrorCodeNumber = (typeof ErrorCode)[keyof typeof ErrorCode];
@@ -18,6 +19,7 @@ export default class LottoError extends Error {
18
19
  static InvalidLottoNumber(): LottoError;
19
20
  static NotAuthenticated(): LottoError;
20
21
  static PurchaseUnavailable(): LottoError;
22
+ static NotSupported(message?: string): LottoError;
21
23
  static get code(): {
22
24
  INVALID_ROUND: 300001;
23
25
  INVALID_LOTTO_NUMBER: 300002;
@@ -26,10 +28,11 @@ export default class LottoError extends Error {
26
28
  CREDENTIALS_INCORRECT: 200001;
27
29
  INVALID_COOKIE: 200002;
28
30
  NETWORK_ERROR: 100001;
31
+ NOT_SUPPORTED: 110000;
29
32
  UNKNOWN_ERROR: 199999;
30
33
  };
31
34
  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";
35
+ static getName(code: ErrorCodeNumber): "INVALID_ROUND" | "INVALID_LOTTO_NUMBER" | "NOT_AUTHENTICATED" | "PURCHASE_UNAVAILABLE" | "CREDENTIALS_INCORRECT" | "INVALID_COOKIE" | "NETWORK_ERROR" | "NOT_SUPPORTED" | "UNKNOWN_ERROR";
33
36
  code: number;
34
37
  constructor(code: ErrorCodeNumber, message?: string);
35
38
  }
@@ -6,7 +6,7 @@ export declare class LottoService implements LottoServiceInterface {
6
6
  };
7
7
  browserController: BrowserControllerInterface;
8
8
  logger: LoggerInterface;
9
- constructor(configs?: BrowserConfigs);
9
+ constructor(configs: BrowserConfigs);
10
10
  destroy: () => Promise<void>;
11
11
  signInWithCookie: (cookies: string) => Promise<string>;
12
12
  signIn: (id: string, password: string) => Promise<string>;
@@ -1,4 +1,6 @@
1
1
  import type { LogLevel } from './logger';
2
+ import type { PuppeteerNode } from 'puppeteer';
3
+ import type { BrowserType } from 'playwright-core';
2
4
  export interface LottoServiceInterface {
3
5
  destroy(): Promise<void>;
4
6
  signIn(id: string, password: string): Promise<string>;
@@ -10,8 +12,9 @@ export interface LottoServiceInterface {
10
12
  purchase(amount: number): Promise<number[][]>;
11
13
  getCheckWinningLink(numbers: number[][], round: number): string;
12
14
  }
13
- export interface BrowserConfigs {
14
- controller?: 'puppeteer' | 'playwright';
15
+ export type BrowserController = PuppeteerNode | BrowserType | 'api';
16
+ export interface BrowserConfigs<T extends BrowserController = BrowserController> {
17
+ controller: T;
15
18
  logLevel?: LogLevel;
16
19
  headless?: boolean;
17
20
  defaultViewport?: {
@@ -21,8 +24,8 @@ export interface BrowserConfigs {
21
24
  [key: string]: unknown;
22
25
  args?: string[];
23
26
  }
24
- export interface BrowserControllerInterface {
25
- configs: BrowserConfigs;
27
+ export interface BrowserControllerInterface<T extends BrowserController = BrowserController> {
28
+ configs: BrowserConfigs<T>;
26
29
  focus(pageIndex?: number): Promise<BrowserPageInterface>;
27
30
  cleanPages(remainingPageIndex: number[]): Promise<void>;
28
31
  close(): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rich-automation/lotto",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Lotto module",
5
5
  "publishConfig": {
6
6
  "access": "public",
@@ -35,7 +35,7 @@
35
35
  "build:esm": "tsc --project tsconfig.esm.json && echo '{\"type\": \"module\"}' > lib/esm/package.json",
36
36
  "build:dts": "tsc --project tsconfig.json --emitDeclarationOnly --declaration --declarationDir lib/typescript",
37
37
  "test": "jest --forceExit --detectOpenHandles",
38
- "install:puppeteer": "node ./node_modules/puppeteer/install.js",
38
+ "install:puppeteer": "node ./node_modules/puppeteer/install.mjs",
39
39
  "install:playwright": "npx playwright install chromium --with-deps",
40
40
  "fix": "yarn fix:eslint && yarn fix:prettier",
41
41
  "fix:eslint": "eslint --fix src --ext js,jsx,ts,tsx ",
@@ -46,11 +46,21 @@
46
46
  "lint:prettier": "prettier --check \"src/**/*.{ts,tsx,js}\""
47
47
  },
48
48
  "dependencies": {
49
- "axios": "^1.3.5",
50
- "dayjs": "^1.11.7",
49
+ "axios": "^1.8.4",
50
+ "dayjs": "^1.11.13"
51
+ },
52
+ "peerDependencies": {
51
53
  "playwright": "^1.35.0",
52
54
  "puppeteer": "^20.5.0"
53
55
  },
56
+ "peerDependenciesMeta": {
57
+ "playwright": {
58
+ "optional": true
59
+ },
60
+ "puppeteer": {
61
+ "optional": true
62
+ }
63
+ },
54
64
  "devDependencies": {
55
65
  "@babel/core": "^7.21.4",
56
66
  "@babel/preset-env": "^7.21.4",
@@ -64,7 +74,9 @@
64
74
  "eslint-config-prettier": "^8.8.0",
65
75
  "eslint-plugin-prettier": "^4.2.1",
66
76
  "jest": "^29.5.0",
77
+ "playwright": "^1.51.1",
67
78
  "prettier": "^2.8.7",
79
+ "puppeteer": "^24.6.1",
68
80
  "release-it": "^15.10.3",
69
81
  "ts-node": "^10.9.1",
70
82
  "typescript": "^5.0.4"