refactor(src): switch to Puppeteer-driven account management
- Replace `src/Cookie.ts` with direct `Browser` and `Page` management in `Bandai` class. - Implement lazy initialization for the browser instance to avoid redundant startup costs. - Update `loginBandai` to accept an existing `browserAndPage` context rather than creating a new one. - Improve login status verification by checking for the login button ID instead of generic text. - Add `getCartList` to parse cart HTML using `cheerio` and `set-cookie-parser`. - Add `order` and `open` methods to navigate and interact with specific URLs via Puppeteer. - Update `puppeteer-utils` to save cookies automatically on page load. - Remove legacy `src/Cookie.ts` and `src/Cookie.ts` usage. - Bump `set-cookie-parser` dependency to `^3.1.0`.
This commit is contained in:
parent
fe97b67d87
commit
b8ffb6e5c3
3
bun.lock
3
bun.lock
@ -7,6 +7,7 @@
|
||||
"dependencies": {
|
||||
"cheerio": "^1.2.0",
|
||||
"puppeteer": "^24.40.0",
|
||||
"set-cookie-parser": "^3.1.0",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest",
|
||||
@ -209,6 +210,8 @@
|
||||
|
||||
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
||||
|
||||
"set-cookie-parser": ["set-cookie-parser@3.1.0", "", {}, "sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw=="],
|
||||
|
||||
"smart-buffer": ["smart-buffer@4.2.0", "", {}, "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg=="],
|
||||
|
||||
"socks": ["socks@2.8.7", "", { "dependencies": { "ip-address": "^10.0.1", "smart-buffer": "^4.2.0" } }, "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A=="],
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"cheerio": "^1.2.0",
|
||||
"puppeteer": "^24.40.0"
|
||||
"puppeteer": "^24.40.0",
|
||||
"set-cookie-parser": "^3.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,16 +1,20 @@
|
||||
import { fetch } from 'bun';
|
||||
import BandaiCookie from './Cookie';
|
||||
import { loginBandai } from './utils/account-utils';
|
||||
import BandaiCookie from './CookieManager';
|
||||
import { loginBandai, updateCookie } from './utils/account-utils';
|
||||
import * as cheerio from 'cheerio';
|
||||
import { getBandaiRawCookieRedisKey, redis } from './utils';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { parseCartList } from './utils/htmlParser';
|
||||
import { initBrowser } from './utils/puppeteer-utils';
|
||||
import type { Browser, Page } from 'puppeteer';
|
||||
|
||||
export default class Bandai {
|
||||
#email: string;
|
||||
#password: string;
|
||||
#cookieManager: BandaiCookie;
|
||||
#referrer = 'https://p-bandai.jp';
|
||||
#browserAndPage?: { browser: Browser; page: Page };
|
||||
|
||||
#isInited = false;
|
||||
|
||||
@ -20,6 +24,18 @@ export default class Bandai {
|
||||
this.#cookieManager = new BandaiCookie(email);
|
||||
}
|
||||
|
||||
async #initBrowserAndPage() {
|
||||
const browserAndPage = await initBrowser(this.#email);
|
||||
this.#browserAndPage = browserAndPage;
|
||||
}
|
||||
|
||||
async getBrowserAndPage() {
|
||||
if (!this.#browserAndPage) {
|
||||
await this.#initBrowserAndPage();
|
||||
}
|
||||
return this.#browserAndPage!;
|
||||
}
|
||||
|
||||
async #fetch(url: string, init?: RequestInit) {
|
||||
console.debug('Fetching: %s, cookie inited: %s', url, this.#isInited);
|
||||
if (!this.#isInited) {
|
||||
@ -42,10 +58,22 @@ export default class Bandai {
|
||||
async #isLogind() {
|
||||
const hasCookie = await redis.get(getBandaiRawCookieRedisKey(this.#email));
|
||||
if (!hasCookie) return false;
|
||||
const hasLoginId = await this.#fetch('https://p-bandai.jp/mypage')
|
||||
const hasLoginBtn = await this.#fetch('https://p-bandai.jp/mypage/')
|
||||
.then(res => res.text())
|
||||
.then(html => html.includes('#loign_id'));
|
||||
return !hasLoginId;
|
||||
.then(html => {
|
||||
this.#debugHTML(html, 'mypage-check-login-id')
|
||||
return html.includes('document.getElementById("btnLogin")');
|
||||
});
|
||||
if (hasLoginBtn) return false;
|
||||
const { page } = await this.getBrowserAndPage();
|
||||
const mypage = 'https://p-bandai.jp/mypage/';
|
||||
await Promise.all([
|
||||
page.goto(mypage),
|
||||
page.waitForNavigation(),
|
||||
]);
|
||||
const hasLogin = page.url() === mypage;
|
||||
if (!hasLogin) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
#debugHTML(html: string, name: string) {
|
||||
@ -73,9 +101,37 @@ export default class Bandai {
|
||||
async login() {
|
||||
const isLogin = await this.#isLogind();
|
||||
if (!isLogin) {
|
||||
await loginBandai(this.#email, this.#password);
|
||||
await loginBandai(this.#email, this.#password, await this.getBrowserAndPage());
|
||||
await this.#cookieManager.init();
|
||||
}
|
||||
const id = await this.#getId();
|
||||
return { email: this.#email, password: this.#password, id };
|
||||
}
|
||||
async getCartList() {
|
||||
const url = 'https://p-bandai.jp/cart';
|
||||
const html = await this.#fetch(url)
|
||||
.then(res => res.text());
|
||||
this.#debugHTML(html, 'cart');
|
||||
return parseCartList(html);
|
||||
}
|
||||
|
||||
async order(url: string) {
|
||||
const { page, browser } = await this.getBrowserAndPage();
|
||||
await Promise.all([
|
||||
page.goto(url),
|
||||
page.waitForNavigation(),
|
||||
page.waitForSelector('#buy'),
|
||||
]);
|
||||
await page.click('#buy');
|
||||
await updateCookie(this.#email, browser);
|
||||
}
|
||||
async open(url: string) {
|
||||
const { page, browser } = await this.getBrowserAndPage();
|
||||
await Promise.all([
|
||||
page.goto(url),
|
||||
page.waitForNavigation(),
|
||||
]);
|
||||
await updateCookie(this.#email, browser);
|
||||
Bun.sleep(1000 * 60);
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,6 @@
|
||||
import type { Cookie } from "puppeteer";
|
||||
import { getBandaiRawCookieRedisKey, redis } from "./utils";
|
||||
import { parseSetCookie } from "set-cookie-parser";
|
||||
|
||||
export default class BandaiCookie {
|
||||
#cookieMap = new Map<string, Cookie>();
|
||||
@ -17,25 +18,38 @@ export default class BandaiCookie {
|
||||
cookies.map(cookie => {
|
||||
this.#cookieMap.set(cookie.name, cookie);
|
||||
});
|
||||
console.debug('Cookie initialized.');
|
||||
}
|
||||
|
||||
updateCookie(headers: Headers) {
|
||||
const cookies = headers.getSetCookie();
|
||||
const setCookie = headers.getSetCookie();
|
||||
const cookies = parseSetCookie(setCookie);
|
||||
cookies.forEach(cookie => {
|
||||
const cookieData = cookie.split(';'); // Extract the name=value part of
|
||||
console.debug(cookieData);
|
||||
// this.#cookieMap.set(cookie)
|
||||
const value = this.#cookieMap.get(cookie.name);
|
||||
if (!value) {
|
||||
console.debug('Cookie %s not found', cookie.name);
|
||||
return;
|
||||
}
|
||||
this.#cookieMap.set(cookie.name, {
|
||||
...value,
|
||||
value: cookie.value,
|
||||
domain: cookie.domain ?? value.domain,
|
||||
expires: cookie.expires?.getTime() || value.expires,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getCookie() {
|
||||
return this.#cookieMap.entries()
|
||||
return this.#cookieMap.values()
|
||||
.toArray()
|
||||
.map(([k, v]) => `${k}=${v}`)
|
||||
.map((v) => `${v.name}=${v.value}`)
|
||||
.join('; ');
|
||||
}
|
||||
|
||||
getRawCookie(): Cookie[] {
|
||||
const cookies = this.#cookieMap.values().toArray();
|
||||
return cookies;
|
||||
}
|
||||
|
||||
async saveCookie() {
|
||||
const cookieString = JSON.stringify(this.#cookieMap.values().toArray());
|
||||
await redis.set(getBandaiRawCookieRedisKey(this.#id), cookieString);
|
||||
@ -1,15 +1,21 @@
|
||||
import type { Browser, Page } from "puppeteer";
|
||||
import { getBandaiRawCookieRedisKey, redis } from ".";
|
||||
import { initBrowser } from "./puppeteer-utils";
|
||||
|
||||
export async function loginBandai(emai: string, password: string) {
|
||||
const { browser, page } = await initBrowser(emai);
|
||||
export async function updateCookie(email: string, browser: Browser) {
|
||||
const rawCookies = await browser.cookies();
|
||||
await redis.set(getBandaiRawCookieRedisKey(email), JSON.stringify(rawCookies));
|
||||
}
|
||||
|
||||
|
||||
export async function loginBandai(email: string, password: string, browserAndPage: { browser: Browser, page: Page; }) {
|
||||
const { page } = browserAndPage;
|
||||
const url = 'https://p-bandai.jp/login/';
|
||||
await Promise.all([
|
||||
page.goto(url),
|
||||
page.waitForNavigation(),
|
||||
])
|
||||
await Promise.all([
|
||||
page.type('#login_id', emai),
|
||||
page.type('#login_id', email),
|
||||
Bun.sleep(3000), // wait for a second
|
||||
]);
|
||||
await Promise.all([
|
||||
@ -20,8 +26,5 @@ export async function loginBandai(emai: string, password: string) {
|
||||
page.click('#btnLogin'),
|
||||
page.waitForNavigation(),
|
||||
]);
|
||||
const rawCookies = await browser.cookies();
|
||||
await redis.set(getBandaiRawCookieRedisKey(emai), JSON.stringify(rawCookies));
|
||||
await browser.close()
|
||||
return;
|
||||
}
|
||||
@ -15,6 +15,7 @@ export const puppeteerInitArgs = [
|
||||
|
||||
export const CHROME = {
|
||||
Windows: 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
|
||||
// Windows: 'C:\\Users\\kyuus\\.cache\\puppeteer\\chrome\\win64-146.0.7680.153\\chrome-win64\\chrome.exe',
|
||||
// Linux: '/usr/bin/google-chrome',
|
||||
Linux: '/usr/bin/chromium',
|
||||
Mac: '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
||||
|
||||
15
src/utils/htmlParser.ts
Normal file
15
src/utils/htmlParser.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import * as cheerio from 'cheerio';
|
||||
|
||||
export function parseCartList(html: string) {
|
||||
const $ = cheerio.load(html);
|
||||
const orderList = $('.pbCartOrderList > dl').toArray();
|
||||
const items = orderList.map(dl => {
|
||||
const a = $(dl).find('dt > a');
|
||||
const title = a.text().trim();
|
||||
const url = a.attr('href') ?? '';
|
||||
const id = /item-(?<id>\d+)\/$/.exec(url)?.groups?.id ?? '';
|
||||
const img = $(dl).find('.pbCartOrderList-img img').attr('src') ?? '';
|
||||
return { title, url, id, img };
|
||||
});
|
||||
return items;
|
||||
}
|
||||
@ -4,6 +4,7 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
import { puppeteerInitArgs } from "./constants";
|
||||
import { getChromePath } from ".";
|
||||
import { updateCookie } from "./account-utils";
|
||||
|
||||
export async function setupNewPage(page: Page) {
|
||||
await page.evaluateOnNewDocument(() => {
|
||||
@ -38,6 +39,10 @@ export async function initBrowser(id: string) {
|
||||
.then(pages => pages[0] || browser.newPage())
|
||||
.then(page => page);
|
||||
setupNewPage(page);
|
||||
page.on('load', async () => {
|
||||
console.debug('page loade: %s', page.url());
|
||||
updateCookie(id, browser);
|
||||
});
|
||||
return {
|
||||
browser,
|
||||
page,
|
||||
|
||||
9
test/cart.test.ts
Normal file
9
test/cart.test.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import path from 'path';
|
||||
import { parseCartList } from "../src/utils/htmlParser";
|
||||
|
||||
test('Load cart list', async () => {
|
||||
const html = await Bun.file(path.resolve(__dirname, '../', 'debug-html', 'allin_603_outlook_com', 'cart.html')).text();
|
||||
const result = parseCartList(html);
|
||||
expect(result).toBeDefined();
|
||||
});
|
||||
@ -6,6 +6,11 @@ async function main() {
|
||||
const account = new Bandai(email, password);
|
||||
const accountInfo = await account.login();
|
||||
console.debug('Account', accountInfo);
|
||||
const url = 'https://p-bandai.jp/item/item-1000245579/?spec=pc21&cref=500894283&click_recom=1';
|
||||
await account.order(url);
|
||||
const cartList = await account.getCartList();
|
||||
console.debug('Cart', cartList);
|
||||
await account.open('https://p-bandai.jp/cart');
|
||||
}
|
||||
|
||||
main().then(() => {
|
||||
|
||||
@ -1,12 +0,0 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import Bandai from "../src/Bandai";
|
||||
import { getBandaiRawCookieRedisKey, redis } from "../src/utils";
|
||||
|
||||
test('Test login', async () => {
|
||||
const email = 'allin-603@outlook.com';
|
||||
const password = '123456789qw';
|
||||
const account = new Bandai(email, password);
|
||||
await account.login();
|
||||
const cookie = await redis.get(getBandaiRawCookieRedisKey(email));
|
||||
expect(cookie).toBeDefined();
|
||||
});
|
||||
Loading…
Reference in New Issue
Block a user