Skip to content

Commit

Permalink
Merge pull request #8 from code-yeongyu/naver-captcha
Browse files Browse the repository at this point in the history
사용자는 네이버 서비스의 로그인 과정에 캡챠가 필요할 경우 캡챠 로그인을 할 수 있습니다.
  • Loading branch information
code-yeongyu authored Jan 22, 2022
2 parents 49d5d2a + 493e1a7 commit ad127ec
Show file tree
Hide file tree
Showing 7 changed files with 327 additions and 10 deletions.
19 changes: 18 additions & 1 deletion example/naver/reactivelyPrintPaymentHistory.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import puppeteer from "puppeteer";
import { NaverApp } from "trackpurchase";
import { NaverApp } from ".";

import readline from "readline";
import { concat, defer, filter, from, tap } from "rxjs";
import { CaptchaStatus } from "app/naver";

const printNaverPayHistory = async (id: string, password: string) => {
const MOBILE_UA =
Expand Down Expand Up @@ -38,6 +39,22 @@ const printNaverPayHistory = async (id: string, password: string) => {
});
}
}),
tap((event) => {
function instanceOfCaptchaStatus(object: any): object is CaptchaStatus {
if (object) {
return "imageData" in object && "question" in object;
}
return false;
}

if (instanceOfCaptchaStatus(event)) {
console.log(`encodedImage: ${event.imageData}`);
console.log(`question: ${event.question}`);
rl.question("captcha code: ", (code) => {
module.pageInteractor.fillCaptchaInput(code, password);
});
}
}),
filter((event) => event instanceof Array)
);
final$.subscribe((event) => {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "trackpurchase",
"version": "1.0.0",
"version": "1.1.0",
"main": "dist/index.js",
"license": "MIT",
"repository": {
Expand Down
4 changes: 4 additions & 0 deletions src/app/naver/elementParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,4 +117,8 @@ export default class ElementParser {
async parseManualOTPInputElement() {
return await this.page.$("#otp");
}

async parseCaptchaInputElement() {
return await this.page.$("#captcha");
}
}
3 changes: 2 additions & 1 deletion src/app/naver/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import Module from "./module";
import ModuleFactory from "./moduleFactory";
import URLChanger from "./urlChanger";
import PageInteractor, { LoginEvent } from "./pageInteractor";
import PageInteractor, { LoginEvent, CaptchaStatus } from "./pageInteractor";
import ElementParser from "./elementParser";
import Service from "./service";

Expand All @@ -13,4 +13,5 @@ export {
ElementParser,
Service,
LoginEvent,
CaptchaStatus,
};
252 changes: 252 additions & 0 deletions src/app/naver/pageInteractor.test.ts

Large diffs are not rendered by default.

41 changes: 39 additions & 2 deletions src/app/naver/pageInteractor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ export type LoginEvent =
| "manual-otp-required"
| "unexpected";

export interface CaptchaStatus {
readonly imageData: string;
readonly question: string;
}

export default class PageInteractor {
private _fullyLoaded = false;

Expand All @@ -27,9 +32,9 @@ export default class PageInteractor {

private async typeLoginInfo(id: string, password: string, delay: number) {
await this.page.focus("#id");
await this.page.keyboard.type(id, { delay: delay || 200 });
await this.page.keyboard.type(id, { delay: delay });
await this.page.focus("#pw");
await this.page.keyboard.type(password, { delay: delay || 200 });
await this.page.keyboard.type(password, { delay: delay });
await this.clickLoginButton();
}

Expand Down Expand Up @@ -83,6 +88,38 @@ export default class PageInteractor {
await manualOTPElement.press("Enter");
}

async getCaptchaStatus(): Promise<CaptchaStatus | null> {
const data = await this.page.evaluate(() => {
const captchaImage = document.querySelector(
"#captchaimg"
) as HTMLElement | null;
const captchaText = document.querySelector(
"#captcha_info"
) as HTMLElement | null;

if (!captchaImage || !captchaText) {
return;
}

const imageData = captchaImage.getAttribute("src") as string;
const question = captchaText.innerText;

return { imageData, question };
});

return data || null;
}

async fillCaptchaInput(answer: string, password: string) {
const captchaElement = await this.elementParser.parseCaptchaInputElement();
if (!captchaElement) {
throw new Error("captcha input element not found");
}
await captchaElement.type(answer);

await this.typeLoginInfo("", password, 200);
}

async loadMoreHistory() {
if (this._fullyLoaded) {
return;
Expand Down
16 changes: 11 additions & 5 deletions src/app/naver/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,27 @@ export default class Service {
this.module = module;
}

async normalLogin(id: string, password: string) {
async normalLogin(id: string, password: string, delay?: number) {
await this.module.urlChanger.moveToLoginURL();
await this.module.pageInteractor.login(id, password);
await this.module.pageInteractor.login(id, password, delay);
}

interactiveLogin(id: string, password: string) {
const login$ = defer(() => from(this.normalLogin(id, password)));
interactiveLogin(id: string, password: string, delay?: number) {
const login$ = defer(() => from(this.normalLogin(id, password, delay)));
const loginStatus$ = interval(500)
.pipe(mergeMap(() => this.module.pageInteractor.getLoginStatus()))
.pipe(
distinctUntilChanged(),
takeWhile((loginStatus) => loginStatus !== "success")
);
const captchaStatus$ = interval(500)
.pipe(mergeMap(() => this.module.pageInteractor.getCaptchaStatus()))
.pipe(
distinctUntilChanged((a, b) => a?.question === b?.question),
takeWhile((captchaStatus) => captchaStatus !== null)
);

const result$ = concat(login$, loginStatus$);
const result$ = concat(login$, captchaStatus$, loginStatus$);
return result$;
}

Expand Down

0 comments on commit ad127ec

Please sign in to comment.