diff --git a/README.md b/README.md index 17ec854..c3cc8c8 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,13 @@ Data (including json files with claimed games, codes to redeem, screenshots) is 1. [Install Node.js](https://nodejs.org/en/download) 2. Clone/download this repository and `cd` into it in a terminal -3. Run `npm install` +3. Run `npm install && npx patchright install chromium` 4. Run `pip install apprise` (or use [pipx](https://github.com/pypa/pipx) if you have [problems](https://stackoverflow.com/questions/75608323/how-do-i-solve-error-externally-managed-environment-every-time-i-use-pip-3)) to install [apprise](https://github.com/caronc/apprise) if you want notifications 5. To get updates: `git pull; npm install` 6. Run `node epic-games`, `node prime-gaming`, `node gog`... -During `npm install` Playwright will download its Firefox to a cache in home ([doc](https://playwright.dev/docs/browsers#managing-browser-binaries)). -If you are missing some dependencies for the browser on your system, you can use `sudo npx playwright install firefox --with-deps`. +Patchright/Playwright will download its Chromium to a cache in home ([doc](https://playwright.dev/docs/browsers#managing-browser-binaries)). +If you are missing some dependencies for the browser on your system, you can use `sudo npx patchright install chromium --with-deps`. If you don't want to use Docker for quasi-headless mode, you could run inside a virtual machine, on a server, or you wake your PC at night to avoid being interrupted. diff --git a/epic-games.js b/epic-games.js index 4db1e75..68c8b7c 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,9 +1,10 @@ -import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra +// import { chromium } from 'playwright-chromium'; +import { chromium } from 'patchright'; import { authenticator } from 'otplib'; import chalk from 'chalk'; import path from 'path'; import { existsSync, writeFileSync, appendFileSync } from 'fs'; -import { resolve, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './src/util.js'; +import { resolve, jsonDb, datetime, filenamify, prompt, notify, html_game_list, handleSIGINT } from './src/util.js'; import { cfg } from './src/config.js'; const screenshot = (...a) => resolve(cfg.dir.screenshots, 'epic-games', ...a); @@ -17,35 +18,26 @@ const db = await jsonDb('epic-games.json', {}); if (cfg.time) console.time('startup'); -const browserPrefs = path.join(cfg.dir.browser, 'prefs.js'); -if (existsSync(browserPrefs)) { - console.log('Adding webgl.disabled to', browserPrefs); - appendFileSync(browserPrefs, 'user_pref("webgl.disabled", true);'); // apparently Firefox removes duplicates (and sorts), so no problem appending every time -} else { - console.log(browserPrefs, 'does not exist yet, will patch it on next run. Restart the script if you get a captcha.'); -} - // https://playwright.dev/docs/auth#multi-factor-authentication -const context = await firefox.launchPersistentContext(cfg.dir.browser, { +const context = await chromium.launchPersistentContext(cfg.dir.browser, { + // channel: 'chrome', // recommended, but `npx patchright install chrome` clashes with system Chrome - https://github.com/Kaliiiiiiiiii-Vinyzu/patchright-nodejs#best-practice----use-chrome-without-fingerprint-injection headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, - userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? - // userAgent firefox (macOS): Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 - // userAgent firefox (docker): Mozilla/5.0 (X11; Linux aarch64; rv:109.0) Gecko/20100101 Firefox/115.0 - locale: 'en-US', // ignore OS locale to be sure to have english text for locators + // locale: 'en-US', // ignore OS locale to be sure to have english text for locators recordVideo: cfg.record ? { dir: 'data/record/', size: { width: cfg.width, height: cfg.height } } : undefined, // will record a .webm video for each page navigated; without size, video would be scaled down to fit 800x800 recordHar: cfg.record ? { path: `data/record/eg-${filenamify(datetime())}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools handleSIGINT: false, // have to handle ourselves and call context.close(), otherwise recordings from above won't be saved - // user settings for firefox have to be put in $BROWSER_DIR/user.js - args: [ // https://wiki.mozilla.org/Firefox/CommandLineOptions - // '-kiosk', + // https://peter.sh/experiments/chromium-command-line-switches/ + args: [ + '--hide-crash-restore-bubble', ], + chromiumSandbox: true, // https://github.com/Kaliiiiiiiiii-Vinyzu/patchright/issues/52 }); -handleSIGINT(context); +// console.log(context.browser().browserType()); // browser is null... +if (cfg.debug) console.log(chromium.executablePath()); -// Without stealth plugin, the website shows an hcaptcha on login with username/password and in the last step of claiming a game. It may have other heuristics like unsuccessful logins as well. After <6h (TBD) it resets to no captcha again. Getting a new IP also resets. -await stealth(context); +handleSIGINT(context); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); @@ -150,7 +142,7 @@ try { // debug showed that in those cases the href was still correct, so we `goto` the urls instead of clicking. // Alternative: parse the json loaded to build the page https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions // i.e. filter data.Catalog.searchStore.elements for .promotions.promotionalOffers being set and build URL with .catalogNs.mappings[0].pageSlug or .urlSlug if not set to some wrong id like it was the case for spirit-of-the-north-f58a66 - this is also what's done here: https://github.com/claabs/epicgames-freegames-node/blob/938a9653ffd08b8284ea32cf01ac8727d25c5d4c/src/puppet/free-games.ts#L138-L213 - const urlSlugs = await Promise.all((await game_loc.elementHandles()).map(a => a.getAttribute('href'))); + const urlSlugs = await Promise.all((await game_loc.all()).map(a => a.getAttribute('href'))); const urls = urlSlugs.map(s => 'https://store.epicgames.com' + s); console.log('Free games:', urls); diff --git a/package-lock.json b/package-lock.json index 9ab1fff..00d8aaf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "fingerprint-injector": "^2.1.66", "lowdb": "^7.0.1", "otplib": "^12.0.1", + "patchright": "^1.52.3", "playwright-firefox": "^1.52.0", "puppeteer-extra-plugin-stealth": "^2.11.2" }, @@ -1386,6 +1387,20 @@ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "license": "ISC" }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -2138,6 +2153,36 @@ "node": ">= 0.8" } }, + "node_modules/patchright": { + "version": "1.52.3", + "resolved": "https://registry.npmjs.org/patchright/-/patchright-1.52.3.tgz", + "integrity": "sha512-hi4MhiUrAFIS0CQH8tcHaRwWIhgK+B9Kz74pp95Mn/MJQHPTGLRYlNiJCxjcfxF3qDNhO1lrxDORKfRPvd45MA==", + "license": "Apache-2.0", + "dependencies": { + "patchright-core": "1.52.3" + }, + "bin": { + "patchright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/patchright-core": { + "version": "1.52.3", + "resolved": "https://registry.npmjs.org/patchright-core/-/patchright-core-1.52.3.tgz", + "integrity": "sha512-vTGjtGGR0VEZPujPd/2Xfq4uFIE1Rrz5pFwApwyrbyPILERLhzRXwR9MwtIQw4infpoPhF4QAd8qisB9znrTMg==", + "license": "Apache-2.0", + "bin": { + "patchright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -3786,6 +3831,12 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "optional": true + }, "function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4301,6 +4352,20 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "dev": true }, + "patchright": { + "version": "1.52.3", + "resolved": "https://registry.npmjs.org/patchright/-/patchright-1.52.3.tgz", + "integrity": "sha512-hi4MhiUrAFIS0CQH8tcHaRwWIhgK+B9Kz74pp95Mn/MJQHPTGLRYlNiJCxjcfxF3qDNhO1lrxDORKfRPvd45MA==", + "requires": { + "fsevents": "2.3.2", + "patchright-core": "1.52.3" + } + }, + "patchright-core": { + "version": "1.52.3", + "resolved": "https://registry.npmjs.org/patchright-core/-/patchright-core-1.52.3.tgz", + "integrity": "sha512-vTGjtGGR0VEZPujPd/2Xfq4uFIE1Rrz5pFwApwyrbyPILERLhzRXwR9MwtIQw4infpoPhF4QAd8qisB9znrTMg==" + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", diff --git a/package.json b/package.json index 0487277..930b9eb 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "fingerprint-injector": "^2.1.66", "lowdb": "^7.0.1", "otplib": "^12.0.1", + "patchright": "^1.52.3", "playwright-firefox": "^1.52.0", "puppeteer-extra-plugin-stealth": "^2.11.2" },