From a8c578bd93af54a50b2c90e6aa6b075ec575b83d Mon Sep 17 00:00:00 2001 From: Trung Le Date: Thu, 31 Mar 2022 03:06:01 +0700 Subject: [PATCH 001/520] feat: dockerize, fix sign in loop --- .dockerignore | 6 ++++ Dockerfile | 75 ++++++++++++++++++++++++++++++++++++++++++++ docker/entrypoint.sh | 20 ++++++++++++ docker/vnc-start.sh | 12 +++++++ epic-games.js | 21 +++++++------ package.json | 6 ++-- 6 files changed, 127 insertions(+), 13 deletions(-) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100755 docker/entrypoint.sh create mode 100755 docker/vnc-start.sh diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..eae97d1 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +userDataDir** +node_modules + +.gitignore +**Dockerfile** +.dockerignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3f0507a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,75 @@ +# FROM mcr.microsoft.com/playwright:v1.20.0 +FROM ubuntu:focal + +ARG DEBIAN_FRONTEND=noninteractive + +# Configure Xvfb via environment variables: +ENV SCREEN_WIDTH 1440 +ENV SCREEN_HEIGHT 900 +ENV SCREEN_DEPTH 24 +ENV DISPLAY :60 + +# Configure VNC via environment variables: +ENV VNC_ENABLED true +ENV VNC_PASSWORD secret +ENV VNC_PORT 5900 +EXPOSE 5900 + +# === INSTALL Node.js === + +# Taken from https://github.com/microsoft/playwright/blob/main/utils/docker/Dockerfile.focal +RUN apt-get update && \ + # Install node16 + apt-get install -y curl wget && \ + curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ + apt-get install -y nodejs && \ + # Feature-parity with node.js base images. + apt-get install -y --no-install-recommends git openssh-client && \ + npm install -g yarn && \ + # clean apt cache + rm -rf /var/lib/apt/lists/* && \ + # Create the pwuser + adduser pwuser + + +# === Install the base requirements to run and debug webdriver implementations === +RUN apt-get update \ + && apt-get install --no-install-recommends --no-install-suggests -y \ + xvfb \ + xauth \ + ca-certificates \ + x11vnc \ + fluxbox \ + stterm \ + curl \ + tini \ + && apt-get clean \ + && rm -rf \ + /tmp/* \ + /usr/share/doc/* \ + /var/cache/* \ + /var/lib/apt/lists/* \ + /var/tmp/* + + +WORKDIR /fgc +COPY package.json . +# Install chromium & dependencies only +RUN export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \ + && npm install \ + && npx playwright install-deps \ + && npx playwright install chromium \ + && apt-get clean \ + && rm -rf /var/lib/apt/lists/* + +COPY . . + +# Shell scripts +RUN mv ./docker/entrypoint.sh /usr/local/bin/entrypoint \ + && chmod +x /usr/local/bin/entrypoint \ + && mv ./docker/vnc-start.sh /usr/local/bin/vnc-start \ + && chmod +x /usr/local/bin/vnc-start + + +ENTRYPOINT ["entrypoint"] +CMD ["node", "epic-games.js"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100755 index 0000000..6880f75 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +if [ "$VNC_ENABLED" = true ]; then + set -- vnc-start "$@" +fi + +if [ "$EXPOSE_X11" = true ]; then + set -- --listen-tcp "$@" +fi + +# 6000+SERVERNUM is the TCP port Xvfb is listening on: +SERVERNUM=$(echo "$DISPLAY" | sed 's/:\([0-9][0-9]*\).*/\1/') + +# Options passed directly to the Xvfb server: +# -ac disables host-based access control mechanisms +# −screen NUM WxHxD creates the screen and sets its width, height, and depth +SERVERARGS="-ac -screen 0 ${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}" + +exec tini -g -- \ + xvfb-run --server-num "$SERVERNUM" --server-args "$SERVERARGS" "$@" diff --git a/docker/vnc-start.sh b/docker/vnc-start.sh new file mode 100755 index 0000000..10ce6bb --- /dev/null +++ b/docker/vnc-start.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +# Disable fbsetbg and start fluxbox in a background process: +mkdir -p ~/.fluxbox && echo 'background: unset' >>~/.fluxbox/overlay +fluxbox -display "$DISPLAY" & + +# Start VNC in a background process: +x11vnc -display "$DISPLAY" -forever -shared -rfbport "${VNC_PORT:-5900}" \ + -passwd "${VNC_PASSWORD:-secret}" & + +# Execute the given command: +exec "$@" diff --git a/epic-games.js b/epic-games.js index b098603..b670241 100644 --- a/epic-games.js +++ b/epic-games.js @@ -3,16 +3,17 @@ import path from 'path'; import { __dirname, stealth } from './util.js'; const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector -const URL_LOGIN = 'https://www.epicgames.com/login'; -const URL_CLAIM = 'https://www.epicgames.com/store/en-US/free-games'; +const URL_CLAIM = 'https://store.epicgames.com/store/en-US/free-games'; +const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; const TIMEOUT = 20 * 1000; // 20s, default is 30s // https://playwright.dev/docs/auth#multi-factor-authentication const context = await chromium.launchPersistentContext(path.resolve(__dirname, 'userDataDir'), { - channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge + // chrome will not work in linux arm64, only chromium + // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge headless: false, viewport: { width: 1280, height: 1280 }, - userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO update if browser is updated! + userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO update if browser is updated! locale: "en-US", // ignore OS locale to be sure to have english text for locators args: [ // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.', but flags below don't work. '--disable-session-crashed-bubble', @@ -35,15 +36,15 @@ const clickIfExists = async selector => { await page.click(selector); }; -await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); // default 'load' takes forever +await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever // with persistent context the cookie message will only show up the first time, so we can't unconditionally wait for it - try to catch it or let the user click it. await clickIfExists('button:has-text("Accept All Cookies")'); // to not waste screen space in --debug while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { // TODO also check alternative for signed-in state console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page."); context.setDefaultTimeout(0); // give user time to log in without timeout - await page.goto(URL_LOGIN, {waitUntil: 'domcontentloaded'}); + await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); // after login it just reloads the login page... - await page.waitForNavigation({url: URL_CLAIM}); + await page.waitForNavigation({ url: URL_CLAIM }); context.setDefaultTimeout(TIMEOUT); // process.exit(1); } @@ -54,7 +55,7 @@ await page.waitForSelector(game_sel); // const games = await page.$$(game_sel); // 'Element is not attached to the DOM' after navigation; had `for (const game of games) { await game.click(); ... } const n = await page.locator(game_sel).count(); console.log('Number of free games:', n); -for (let i=1; i<=n; i++) { +for (let i = 1; i <= n; i++) { await page.click(`:nth-match(${game_sel}, ${i})`); const title = await page.locator('h1 div').first().innerText(); console.log('Current free game:', title); @@ -105,8 +106,8 @@ for (let i=1; i<=n; i++) { } // await page.pause(); } - if (i Date: Thu, 31 Mar 2022 19:41:42 +0200 Subject: [PATCH 002/520] prime-gaming: fix wait for sign in, exit if not, arg show otherwise headless --- prime-gaming.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index c9c630b..8a5801a 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -3,6 +3,8 @@ import path from 'path'; import { __dirname, stealth } from './util.js'; const debug = process.env.PWDEBUG == '1'; // runs headful and opens https://playwright.dev/docs/inspector +const show = process.argv.includes('show', 2); +const headless = !debug && !show; // const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? const URL_CLAIM = 'https://gaming.amazon.com/home'; @@ -10,8 +12,8 @@ const TIMEOUT = 20 * 1000; // 20s, default is 30s // https://playwright.dev/docs/auth#multi-factor-authentication const context = await chromium.launchPersistentContext(path.resolve(__dirname, 'userDataDir'), { - channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge - headless: false, + // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge, chrome will not work on arm64 linux, only chromium which is the default + headless, viewport: { width: 1280, height: 1280 }, userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36', // see replace of Headless in newStealthContext above. TODO update if browser is updated! locale: "en-US", // ignore OS locale to be sure to have english text for locators @@ -32,9 +34,15 @@ const clickIfExists = async selector => { }; await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); // default 'load' takes forever +await Promise.any(['button:has-text("Sign in")', '[data-a-target="user-dropdown-first-name-text"]'].map(s => page.waitForSelector(s))); await clickIfExists('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")'); // to not waste screen space in --debug while (await page.locator('button:has-text("Sign in")').count() > 0) { - console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page."); + console.error('Not signed in anymore.'); + if (headless) { + console.log('Please run `node prime-gaming show` to login in the opened browser.'); + await context.close(); // not needed? + process.exit(1); + } await page.click('button:has-text("Sign in")'); if (!debug) context.setDefaultTimeout(0); // give user time to log in without timeout await page.waitForNavigation({url: 'https://gaming.amazon.com/home?signedIn=true'}); From a707acf1b887e0d13a13e813bf5094e0a62bebf4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 31 Mar 2022 19:43:07 +0200 Subject: [PATCH 003/520] prime-gaming: click Games since now only placeholders until in view --- prime-gaming.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 8a5801a..13677ef 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -49,6 +49,7 @@ while (await page.locator('button:has-text("Sign in")').count() > 0) { if (!debug) context.setDefaultTimeout(TIMEOUT); } console.log('Signed in.'); +await page.click('button:has-text("Games")'); await page.waitForSelector('div[data-a-target="offer-list-FGWP_FULL"]'); console.log('Number of already claimed games (total):', await page.locator('div[data-a-target="offer-list-FGWP_FULL"] p:has-text("Claimed")').count()); const game_sel = 'div[data-a-target="offer-list-FGWP_FULL"] .offer__action:has-text("Claim game")'; @@ -74,7 +75,7 @@ for (const card of games) { if (!card) break; const title = await (await card.$('h3')).innerText(); console.log('Current free game:', title); - await (await card.$('button')).click(); + await (await card.$('text=Claim')).click(); // await page.waitForNavigation(); await page.click('button:has-text("Claim now")'); // TODO only Origin shows a key, check for 'Claimed' or code From fd56cac06b754331b5ac58eece69a2646bcb8984 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 31 Mar 2022 19:55:19 +0200 Subject: [PATCH 004/520] prime-gaming: only print code to redeem game for Origin --- prime-gaming.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 13677ef..4e0dce0 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -78,9 +78,12 @@ for (const card of games) { await (await card.$('text=Claim')).click(); // await page.waitForNavigation(); await page.click('button:has-text("Claim now")'); + console.log(await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText()); // TODO only Origin shows a key, check for 'Claimed' or code - const code = await page.inputValue('input[type="text"]'); - console.log('Code to redeem game:', code); + if (await page.locator('div:has-text("Origin")').count() > 0) { + const code = await page.inputValue('input[type="text"]'); + console.log('Code to redeem game:', code); + } // await page.pause(); await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); n = await page.locator(game_sel).count(); From 930b7b525673faa598feb62b8e75a036050a202e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 31 Mar 2022 19:56:02 +0200 Subject: [PATCH 005/520] prime-gaming: removing Headless from userAgent not required, works headless --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 4e0dce0..2f39690 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -15,7 +15,6 @@ const context = await chromium.launchPersistentContext(path.resolve(__dirname, ' // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge, chrome will not work on arm64 linux, only chromium which is the default headless, viewport: { width: 1280, height: 1280 }, - userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36', // see replace of Headless in newStealthContext above. TODO update if browser is updated! locale: "en-US", // ignore OS locale to be sure to have english text for locators }); @@ -34,6 +33,7 @@ const clickIfExists = async selector => { }; await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); // default 'load' takes forever +// need to wait for some elements to exist before checking if signed in or accepting cookies: await Promise.any(['button:has-text("Sign in")', '[data-a-target="user-dropdown-first-name-text"]'].map(s => page.waitForSelector(s))); await clickIfExists('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")'); // to not waste screen space in --debug while (await page.locator('button:has-text("Sign in")').count() > 0) { From 0381b73d5eabc0cec36afa142b6a99f64a443d08 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 31 Mar 2022 20:10:29 +0200 Subject: [PATCH 006/520] readme: prime-gaming works headless, docker for epic-games, see #11 --- README.md | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index eff7f62..41e0b18 100644 --- a/README.md +++ b/README.md @@ -11,31 +11,36 @@ Claims free games on 2. Clone/download this repository and `cd` into it in a terminal 3. Run `npm install && npx playwright install chromium` -This downloads Chromium (337 MB) to a cache in home ([doc](https://playwright.dev/docs/browsers#managing-browser-binaries)). +This downloads Chromium (343 MB) to a cache in home ([doc](https://playwright.dev/docs/browsers#managing-browser-binaries)). ## Usage - -Both scripts start an automated Chromium instance. It will first check if you are logged in, and if not wait for you to do so. After login, you can also restart the script if it does not redirect back. +Both scripts start an automated Chromium instance, either with the browser GUI shown or hidden (*headless mode*). -If something goes wrong, use `PWDEBUG=1 node epic-games` to [inspect](https://playwright.dev/docs/inspector). +Login has to be done in the browser. It's hard to automate since you usually need to enter some OTP (but you can select 'remember this device'). +After login, the script will just continue, but you can also restart it. -Ideally, claiming would run in *headless mode* (without browser GUI - comment out `headless: false` to test), and on a Raspberry Pi: -- Epic Games Store detects running in headless mode (despite stealth plugin) and gets stuck with a captcha challenge ([issue](https://github.com/vogler/free-games-claimer/issues/2)). Did not test it yet for Prime Gaming. -- Playwright seems to not run on (headless) RPi? See [issue](https://github.com/vogler/free-games-claimer/issues/3). +If something goes wrong, use `PWDEBUG=1 node ...` to [inspect](https://playwright.dev/docs/inspector). ### Epic Games Store Run `node epic-games` -Login: Instead of redirecting back, the website seems to just reload the login URL. Go to https://www.epicgames.com/store/en-US/free-games manually, or restart the script. +Does not run headless, but can be run quasi-headless inside a Docker container (see below). + +They detect headless mode (despite stealth plugin) and it gets stuck with a captcha challenge ([issue](https://github.com/vogler/free-games-claimer/issues/2)). ### Amazon Prime Gaming -Run `node prime-gaming` +Run `node prime-gaming` + +Runs headless. Run `node prime-gaming show` to show the GUI (to login). Claiming the Amazon Games works, external Epic Games also work if the account is linked. -Keys for Origin and GOG should be printed to the console and need to be redeemed manually at the moment ([issue](https://github.com/vogler/free-games-claimer/issues/5)). +Keys for Origin (and GOG?) should be printed to the console and need to be redeemed manually at the moment ([issue](https://github.com/vogler/free-games-claimer/issues/5)). Other stores not tested. +### Docker +See https://github.com/vogler/free-games-claimer/pull/11 (TODO). + ### Run periodically Epic Games releases one (sometimes more) free game *every week*, but around christmas every day. Prime Gaming has new games *every month*. From dbf4804dc7c14809cb57f197acffd16592624698 Mon Sep 17 00:00:00 2001 From: Trung Le Date: Fri, 1 Apr 2022 01:47:48 +0700 Subject: [PATCH 007/520] fix: reduce size, fix signin redirect * correct freegames url * skip downloading browsers in docker * remove fluxbox * remove stdout for vnc & xvfb --- Dockerfile | 13 +- docker/entrypoint.sh | 19 +- docker/vnc-start.sh | 4 - epic-games.js | 6 +- package-lock.json | 2104 ++++++++++++++++-------------------------- 5 files changed, 830 insertions(+), 1316 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3f0507a..4baeb48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,6 +15,10 @@ ENV VNC_PASSWORD secret ENV VNC_PORT 5900 EXPOSE 5900 +# Playwright +ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true +ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD true + # === INSTALL Node.js === # Taken from https://github.com/microsoft/playwright/blob/main/utils/docker/Dockerfile.focal @@ -36,11 +40,8 @@ RUN apt-get update && \ RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y \ xvfb \ - xauth \ ca-certificates \ x11vnc \ - fluxbox \ - stterm \ curl \ tini \ && apt-get clean \ @@ -55,10 +56,8 @@ RUN apt-get update \ WORKDIR /fgc COPY package.json . # Install chromium & dependencies only -RUN export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true \ - && npm install \ - && npx playwright install-deps \ - && npx playwright install chromium \ +RUN npm install \ + && npx playwright install --with-deps chromium \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 6880f75..aebe085 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,20 +1,15 @@ #!/bin/sh -if [ "$VNC_ENABLED" = true ]; then - set -- vnc-start "$@" -fi - -if [ "$EXPOSE_X11" = true ]; then - set -- --listen-tcp "$@" -fi - # 6000+SERVERNUM is the TCP port Xvfb is listening on: -SERVERNUM=$(echo "$DISPLAY" | sed 's/:\([0-9][0-9]*\).*/\1/') +# SERVERNUM=$(echo "$DISPLAY" | sed 's/:\([0-9][0-9]*\).*/\1/') # Options passed directly to the Xvfb server: # -ac disables host-based access control mechanisms # −screen NUM WxHxD creates the screen and sets its width, height, and depth -SERVERARGS="-ac -screen 0 ${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}" +Xvfb "$DISPLAY" -ac -screen 0 "${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}" >/dev/null 2>&1 & -exec tini -g -- \ - xvfb-run --server-num "$SERVERNUM" --server-args "$SERVERARGS" "$@" +if [ "$VNC_ENABLED" = true ]; then + vnc-start >/dev/null 2>&1 & +fi + +exec tini -g -- "$@" diff --git a/docker/vnc-start.sh b/docker/vnc-start.sh index 10ce6bb..ef28937 100755 --- a/docker/vnc-start.sh +++ b/docker/vnc-start.sh @@ -1,9 +1,5 @@ #!/bin/sh -# Disable fbsetbg and start fluxbox in a background process: -mkdir -p ~/.fluxbox && echo 'background: unset' >>~/.fluxbox/overlay -fluxbox -display "$DISPLAY" & - # Start VNC in a background process: x11vnc -display "$DISPLAY" -forever -shared -rfbport "${VNC_PORT:-5900}" \ -passwd "${VNC_PASSWORD:-secret}" & diff --git a/epic-games.js b/epic-games.js index b670241..671ff58 100644 --- a/epic-games.js +++ b/epic-games.js @@ -3,16 +3,18 @@ import path from 'path'; import { __dirname, stealth } from './util.js'; const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector -const URL_CLAIM = 'https://store.epicgames.com/store/en-US/free-games'; +const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; const TIMEOUT = 20 * 1000; // 20s, default is 30s +const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) || 1280; +const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; // https://playwright.dev/docs/auth#multi-factor-authentication const context = await chromium.launchPersistentContext(path.resolve(__dirname, 'userDataDir'), { // chrome will not work in linux arm64, only chromium // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge headless: false, - viewport: { width: 1280, height: 1280 }, + viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO update if browser is updated! locale: "en-US", // ignore OS locale to be sure to have english text for locators args: [ // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.', but flags below don't work. diff --git a/package-lock.json b/package-lock.json index c175607..6098fca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,51 +1,51 @@ { - "name": "epicgames-claimer", + "name": "free-games-claimer", "lockfileVersion": 2, "requires": true, "packages": { "": { "devDependencies": { - "@playwright/test": "^1.17.1", - "playwright": "^1.17.1", + "@playwright/test": "^1.20.1", + "playwright": "^1.20.1", "puppeteer-extra-plugin-stealth": "^2.9.0" } }, "node_modules/@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "dependencies": { - "@babel/highlight": "^7.16.0" + "@babel/highlight": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/compat-data": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz", - "integrity": "sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.5.tgz", - "integrity": "sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ==", + "version": "7.16.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", + "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.5", - "@babel/helper-compilation-targets": "^7.16.3", - "@babel/helper-module-transforms": "^7.16.5", - "@babel/helpers": "^7.16.5", - "@babel/parser": "^7.16.5", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.5", - "@babel/types": "^7.16.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.8", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helpers": "^7.16.7", + "@babel/parser": "^7.16.12", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.10", + "@babel/types": "^7.16.8", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -62,12 +62,12 @@ } }, "node_modules/@babel/generator": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.5.tgz", - "integrity": "sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", + "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0", + "@babel/types": "^7.17.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" }, @@ -76,25 +76,25 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", - "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", - "integrity": "sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.16.0", - "@babel/helper-validator-option": "^7.14.5", + "@babel/compat-data": "^7.17.7", + "@babel/helper-validator-option": "^7.16.7", "browserslist": "^4.17.5", "semver": "^6.3.0" }, @@ -106,18 +106,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz", - "integrity": "sha512-NEohnYA7mkB8L5JhU7BLwcBdU3j83IziR9aseMueWGeAjblbul3zzb8UvJ3a1zuBiqCMObzCJHFqKIQE6hTVmg==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", + "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-member-expression-to-functions": "^7.16.5", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/helper-replace-supers": "^7.16.5", - "@babel/helper-split-export-declaration": "^7.16.0" + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -127,142 +127,142 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz", - "integrity": "sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", "dev": true, "dependencies": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz", - "integrity": "sha512-7fecSXq7ZrLE+TWshbGT+HyCLkxloWNhTbU2QM1NTI/tDqyf0oZiMcEfYtDuUDCo528EOlt39G1rftea4bRZIw==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", + "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-imports": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", - "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz", - "integrity": "sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-module-imports": "^7.16.0", - "@babel/helper-simple-access": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.5", - "@babel/types": "^7.16.0" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", - "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz", - "integrity": "sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.5.tgz", - "integrity": "sha512-ao3seGVa/FZCMCCNDuBcqnBFSbdr8N2EW35mzojx3TwfIbdPmNK+JV6+2d5bR0Z71W5ocLnQp9en/cTF7pBJiQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", "dev": true, "dependencies": { - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-member-expression-to-functions": "^7.16.5", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/traverse": "^7.16.5", - "@babel/types": "^7.16.0" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-simple-access": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", - "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" @@ -281,56 +281,56 @@ } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "dependencies": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helpers": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.5.tgz", - "integrity": "sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.8.tgz", + "integrity": "sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw==", "dev": true, "dependencies": { - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.5", - "@babel/types": "^7.16.0" + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" }, @@ -339,9 +339,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.16.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.6.tgz", - "integrity": "sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz", + "integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==", "dev": true, "bin": { "parser": "bin/babel-parser.js" @@ -351,13 +351,13 @@ } }, "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.5.tgz", - "integrity": "sha512-pJD3HjgRv83s5dv1sTnDbZOaTjghKEz8KUn1Kbh2eAIRhGuyQ1XSeI4xVXU3UlIEVA3DAyIdxqT1eRn7Wcn55A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", + "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.5", - "@babel/helper-plugin-utils": "^7.16.5" + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -367,12 +367,12 @@ } }, "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.5.tgz", - "integrity": "sha512-P05/SJZTTvHz79LNYTF8ff5xXge0kk5sIIWAypcWgX4BTRUgyHc8wRxJ/Hk+mU0KXldgOOslKaeqnhthcDJCJQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -383,12 +383,12 @@ } }, "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.5.tgz", - "integrity": "sha512-i+sltzEShH1vsVydvNaTRsgvq2vZsfyrd7K7vPLUU/KgS0D5yZMe6uipM0+izminnkKrEfdUnz7CxMRb6oHZWw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", + "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -399,12 +399,12 @@ } }, "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.5.tgz", - "integrity": "sha512-xqibl7ISO2vjuQM+MzR3rkd0zfNWltk7n9QhaD8ghMmMceVguYrNDt7MikRyj4J4v3QehpnrU8RYLnC7z/gZLA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", + "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -415,12 +415,12 @@ } }, "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.5.tgz", - "integrity": "sha512-YwMsTp/oOviSBhrjwi0vzCUycseCYwoXnLiXIL3YNjHSMBHicGTz7GjVU/IGgz4DtOEXBdCNG72pvCX22ehfqg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", + "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -431,12 +431,12 @@ } }, "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.5.tgz", - "integrity": "sha512-DvB9l/TcsCRvsIV9v4jxR/jVP45cslTVC0PMVHvaJhhNuhn2Y1SOhCSFlPK777qLB5wb8rVDaNoqMTyOqtY5Iw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -447,12 +447,12 @@ } }, "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.5.tgz", - "integrity": "sha512-kzdHgnaXRonttiTfKYnSVafbWngPPr2qKw9BWYBESl91W54e+9R5pP70LtWxV56g0f05f/SQrwHYkfvbwcdQ/A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", + "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, @@ -464,13 +464,13 @@ } }, "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.5.tgz", - "integrity": "sha512-+yFMO4BGT3sgzXo+lrq7orX5mAZt57DwUK6seqII6AcJnJOIhBJ8pzKH47/ql/d426uQ7YhN8DpUFirQzqYSUA==", + "version": "7.16.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", + "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.5", - "@babel/helper-plugin-utils": "^7.16.5" + "@babel/helper-create-class-features-plugin": "^7.16.10", + "@babel/helper-plugin-utils": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -480,14 +480,14 @@ } }, "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.5.tgz", - "integrity": "sha512-+YGh5Wbw0NH3y/E5YMu6ci5qTDmAEVNoZ3I54aB6nVEOZ5BQ7QJlwKq5pYVucQilMByGn/bvX0af+uNaPRCabA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", + "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", "dev": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-create-class-features-plugin": "^7.16.5", - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -499,9 +499,8 @@ }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -535,9 +534,8 @@ }, "node_modules/@babel/plugin-syntax-json-strings": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -583,9 +581,8 @@ }, "node_modules/@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -595,9 +592,8 @@ }, "node_modules/@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, @@ -633,12 +629,12 @@ } }, "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.5.tgz", - "integrity": "sha512-/d4//lZ1Vqb4mZ5xTep3dDK888j7BGM/iKqBmndBaoYAFPlPKrGU608VVBz5JeyAb6YQDjRu1UKqj86UhwWVgw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", + "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.5" + "@babel/helper-plugin-utils": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -648,14 +644,14 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.5.tgz", - "integrity": "sha512-ABhUkxvoQyqhCWyb8xXtfwqNMJD7tx+irIRnUh6lmyFud7Jln1WzONXKlax1fg/ey178EXbs4bSGNd6PngO+SQ==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", + "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", "dev": true, "dependencies": { - "@babel/helper-module-transforms": "^7.16.5", - "@babel/helper-plugin-utils": "^7.16.5", - "@babel/helper-simple-access": "^7.16.0", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", "babel-plugin-dynamic-import-node": "^2.3.3" }, "engines": { @@ -666,14 +662,14 @@ } }, "node_modules/@babel/plugin-transform-typescript": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz", - "integrity": "sha512-NO4XoryBng06jjw/qWEU2LhcLJr1tWkhpMam/H4eas/CDKMX/b2/Ylb6EI256Y7+FVPCawwSM1rrJNOpDiz+Lg==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz", + "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==", "dev": true, "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-typescript": "^7.16.0" + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-typescript": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -683,14 +679,14 @@ } }, "node_modules/@babel/preset-typescript": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.5.tgz", - "integrity": "sha512-lmAWRoJ9iOSvs3DqOndQpj8XqXkzaiQs50VG/zESiI9D3eoZhGriU675xNCr0UwvsuXrhMAGvyk1w+EVWF3u8Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz", + "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==", "dev": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.16.5", - "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-transform-typescript": "^7.16.1" + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-transform-typescript": "^7.16.7" }, "engines": { "node": ">=6.9.0" @@ -700,33 +696,33 @@ } }, "node_modules/@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.5.tgz", - "integrity": "sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.5", - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.5", - "@babel/types": "^7.16.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -735,12 +731,12 @@ } }, "node_modules/@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", "dev": true, "dependencies": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" }, "engines": { @@ -749,9 +745,8 @@ }, "node_modules/@jest/types": { "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", @@ -765,9 +760,8 @@ }, "node_modules/@jest/types/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -780,9 +774,8 @@ }, "node_modules/@jest/types/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -796,9 +789,8 @@ }, "node_modules/@jest/types/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -808,24 +800,21 @@ }, "node_modules/@jest/types/node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@jest/types/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/@jest/types/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -834,46 +823,45 @@ } }, "node_modules/@playwright/test": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.17.1.tgz", - "integrity": "sha512-mMZS5OMTN/vUlqd1JZkFoAk2FsIZ4/E/00tw5it2c/VF4+3z/aWO+PPd8ShEGzYME7B16QGWNPjyFpDQI1t4RQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.20.1.tgz", + "integrity": "sha512-muk3KZXfA7sXTwUEXfL3m4tusj/MBGYjxIFmooi+F2Pf6hKjjVl4+8niy77Xujk4jpL7hZbbqq9v5bRl2m+C8Q==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.14.5", - "@babel/core": "^7.14.8", - "@babel/plugin-proposal-class-properties": "^7.14.5", - "@babel/plugin-proposal-dynamic-import": "^7.14.5", - "@babel/plugin-proposal-export-namespace-from": "^7.14.5", - "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", - "@babel/plugin-proposal-numeric-separator": "^7.14.5", - "@babel/plugin-proposal-optional-chaining": "^7.14.5", - "@babel/plugin-proposal-private-methods": "^7.14.5", - "@babel/plugin-proposal-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-transform-modules-commonjs": "^7.14.5", - "@babel/preset-typescript": "^7.14.5", - "colors": "^1.4.0", - "commander": "^8.2.0", - "debug": "^4.1.1", - "expect": "=27.2.5", - "jest-matcher-utils": "=27.2.5", - "jpeg-js": "^0.4.2", - "mime": "^2.4.6", - "minimatch": "^3.0.3", - "ms": "^2.1.2", - "open": "^8.3.0", - "pirates": "^4.0.1", - "pixelmatch": "^5.2.1", - "playwright-core": "=1.17.1", - "pngjs": "^5.0.0", - "rimraf": "^3.0.2", - "source-map-support": "^0.4.18", - "stack-utils": "^2.0.3", - "yazl": "^2.5.1" + "@babel/code-frame": "7.16.7", + "@babel/core": "7.16.12", + "@babel/helper-plugin-utils": "7.16.7", + "@babel/plugin-proposal-class-properties": "7.16.7", + "@babel/plugin-proposal-dynamic-import": "7.16.7", + "@babel/plugin-proposal-export-namespace-from": "7.16.7", + "@babel/plugin-proposal-logical-assignment-operators": "7.16.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7", + "@babel/plugin-proposal-numeric-separator": "7.16.7", + "@babel/plugin-proposal-optional-chaining": "7.16.7", + "@babel/plugin-proposal-private-methods": "7.16.11", + "@babel/plugin-proposal-private-property-in-object": "7.16.7", + "@babel/plugin-syntax-async-generators": "7.8.4", + "@babel/plugin-syntax-json-strings": "7.8.3", + "@babel/plugin-syntax-object-rest-spread": "7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "7.8.3", + "@babel/plugin-transform-modules-commonjs": "7.16.8", + "@babel/preset-typescript": "7.16.7", + "colors": "1.4.0", + "commander": "8.3.0", + "debug": "4.3.3", + "expect": "27.2.5", + "jest-matcher-utils": "27.2.5", + "json5": "2.2.1", + "mime": "3.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "open": "8.4.0", + "pirates": "4.0.4", + "playwright-core": "1.20.1", + "rimraf": "3.0.2", + "source-map-support": "0.4.18", + "stack-utils": "2.0.5", + "yazl": "2.5.1" }, "bin": { "playwright": "cli.js" @@ -884,54 +872,47 @@ }, "node_modules/@types/debug": { "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", "dev": true, + "license": "MIT", "dependencies": { "@types/ms": "*" } }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/istanbul-lib-report": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "node_modules/@types/istanbul-reports": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", "dev": true, + "license": "MIT", "dependencies": { "@types/istanbul-lib-report": "*" } }, "node_modules/@types/ms": { "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/node": { "version": "17.0.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz", - "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/puppeteer": { "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz", - "integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@types/node": "*" @@ -939,30 +920,26 @@ }, "node_modules/@types/stack-utils": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/yargs": { "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", "dev": true, + "license": "MIT", "dependencies": { "@types/yargs-parser": "*" } }, "node_modules/@types/yargs-parser": { "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/@types/yauzl": { "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@types/node": "*" @@ -970,9 +947,8 @@ }, "node_modules/agent-base": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, + "license": "MIT", "dependencies": { "debug": "4" }, @@ -982,9 +958,8 @@ }, "node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1003,9 +978,8 @@ }, "node_modules/arr-union": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1021,14 +995,11 @@ }, "node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "funding": [ { @@ -1044,13 +1015,13 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "peer": true }, "node_modules/bl": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "buffer": "^5.5.0", @@ -1060,9 +1031,8 @@ }, "node_modules/brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -1070,9 +1040,8 @@ }, "node_modules/braces": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, + "license": "MIT", "dependencies": { "fill-range": "^7.0.1" }, @@ -1081,15 +1050,25 @@ } }, "node_modules/browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.20.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", + "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], "dependencies": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", + "caniuse-lite": "^1.0.30001317", + "electron-to-chromium": "^1.4.84", "escalade": "^3.1.1", - "node-releases": "^2.0.1", + "node-releases": "^2.0.2", "picocolors": "^1.0.0" }, "bin": { @@ -1097,16 +1076,10 @@ }, "engines": { "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" } }, "node_modules/buffer": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "funding": [ { @@ -1122,6 +1095,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "peer": true, "dependencies": { "base64-js": "^1.3.1", @@ -1130,9 +1104,8 @@ }, "node_modules/buffer-crc32": { "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", "dev": true, + "license": "MIT", "engines": { "node": "*" } @@ -1151,14 +1124,20 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001292", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001292.tgz", - "integrity": "sha512-jnT4Tq0Q4ma+6nncYQVe7d73kmDmE9C3OGTx3MvW7lBM/eY1S1DZTMBON7dqV481RhNiS5OxD7k9JQvmDOTirw==", + "version": "1.0.30001322", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001322.tgz", + "integrity": "sha512-neRmrmIrCGuMnxGSoh+x7zYtQFFgnSY2jaomjU56sCkTA6JINqQrxutF459JpWcWRajvoyn95sOXq4Pqrnyjew==", "dev": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - } + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] }, "node_modules/chalk": { "version": "2.4.2", @@ -1176,16 +1155,14 @@ }, "node_modules/chownr": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true, + "license": "ISC", "peer": true }, "node_modules/clone-deep": { "version": "0.2.4", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", - "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", "dev": true, + "license": "MIT", "dependencies": { "for-own": "^0.1.3", "is-plain-object": "^2.0.1", @@ -1199,9 +1176,8 @@ }, "node_modules/clone-deep/node_modules/is-plain-object": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, + "license": "MIT", "dependencies": { "isobject": "^3.0.1" }, @@ -1226,42 +1202,37 @@ }, "node_modules/colors": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.1.90" } }, "node_modules/commander": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true, + "license": "MIT", "engines": { "node": ">= 12" } }, "node_modules/concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/convert-source-map": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.1" } }, "node_modules/debug": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -1276,24 +1247,21 @@ }, "node_modules/debug/node_modules/ms": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/deepmerge": { "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/define-lazy-prop": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1312,31 +1280,28 @@ }, "node_modules/devtools-protocol": { "version": "0.0.937139", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.937139.tgz", - "integrity": "sha512-daj+rzR3QSxsPRy5vjjthn58axO8c11j58uY0lG5vvlJk/EiOdCWOptGdkXDjtuRHr78emKq0udHCXM4trhoDQ==", "dev": true, + "license": "BSD-3-Clause", "peer": true }, "node_modules/diff-sequences": { "version": "27.4.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", - "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", "dev": true, + "license": "MIT", "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/electron-to-chromium": { - "version": "1.4.28", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.28.tgz", - "integrity": "sha512-Gzbf0wUtKfyPaqf0Plz+Ctinf9eQIzxEqBHwSvbGfeOm9GMNdLxyu1dNiCUfM+x6r4BE0xUJNh3Nmg9gfAtTmg==", + "version": "1.4.101", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.101.tgz", + "integrity": "sha512-XJH+XmJjACx1S7ASl/b//KePcda5ocPnFH2jErztXcIS8LpP0SE6rX8ZxiY5/RaDPnaF1rj0fPaHfppzb0e2Aw==", "dev": true }, "node_modules/end-of-stream": { "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, + "license": "MIT", "dependencies": { "once": "^1.4.0" } @@ -1361,9 +1326,8 @@ }, "node_modules/expect": { "version": "27.2.5", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.2.5.tgz", - "integrity": "sha512-ZrO0w7bo8BgGoP/bLz+HDCI+0Hfei9jUSZs5yI/Wyn9VkG9w8oJ7rHRgYj+MA7yqqFa0IwHA3flJzZtYugShJA==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^27.2.5", "ansi-styles": "^5.0.0", @@ -1378,9 +1342,8 @@ }, "node_modules/expect/node_modules/ansi-styles": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -1390,9 +1353,8 @@ }, "node_modules/extract-zip": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -1410,18 +1372,16 @@ }, "node_modules/fd-slicer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, + "license": "MIT", "dependencies": { "pend": "~1.2.0" } }, "node_modules/fill-range": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, + "license": "MIT", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1431,9 +1391,8 @@ }, "node_modules/find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "locate-path": "^5.0.0", @@ -1445,18 +1404,16 @@ }, "node_modules/for-in": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/for-own": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, + "license": "MIT", "dependencies": { "for-in": "^1.0.1" }, @@ -1466,16 +1423,14 @@ }, "node_modules/fs-constants": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/fs-extra": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "dev": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -1487,9 +1442,8 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/function-bind": { "version": "1.1.1", @@ -1499,9 +1453,8 @@ }, "node_modules/gensync": { "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6.9.0" } @@ -1522,9 +1475,8 @@ }, "node_modules/get-stream": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, + "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -1537,9 +1489,8 @@ }, "node_modules/glob": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1566,9 +1517,8 @@ }, "node_modules/graceful-fs": { "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/has": { "version": "1.0.3", @@ -1592,9 +1542,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true, "engines": { "node": ">= 0.4" @@ -1605,9 +1555,8 @@ }, "node_modules/https-proxy-agent": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "dev": true, + "license": "MIT", "dependencies": { "agent-base": "6", "debug": "4" @@ -1618,8 +1567,6 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, "funding": [ { @@ -1635,13 +1582,13 @@ "url": "https://feross.org/support" } ], + "license": "BSD-3-Clause", "peer": true }, "node_modules/inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1649,9 +1596,8 @@ }, "node_modules/inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/ip": { "version": "1.1.5", @@ -1661,15 +1607,13 @@ }, "node_modules/is-buffer": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/is-docker": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true, + "license": "MIT", "bin": { "is-docker": "cli.js" }, @@ -1682,27 +1626,24 @@ }, "node_modules/is-extendable": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.12.0" } }, "node_modules/is-wsl": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, + "license": "MIT", "dependencies": { "is-docker": "^2.0.0" }, @@ -1712,18 +1653,16 @@ }, "node_modules/isobject": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/jest-diff": { "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.2.tgz", - "integrity": "sha512-ujc9ToyUZDh9KcqvQDkk/gkbf6zSaeEg9AiBxtttXW59H/AcqEYp1ciXAtJp+jXWva5nAf/ePtSsgWwE5mqp4Q==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^27.4.0", @@ -1736,9 +1675,8 @@ }, "node_modules/jest-diff/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1751,9 +1689,8 @@ }, "node_modules/jest-diff/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1767,9 +1704,8 @@ }, "node_modules/jest-diff/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1779,24 +1715,21 @@ }, "node_modules/jest-diff/node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jest-diff/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/jest-diff/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -1806,18 +1739,16 @@ }, "node_modules/jest-get-type": { "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", - "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", "dev": true, + "license": "MIT", "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } }, "node_modules/jest-matcher-utils": { "version": "27.2.5", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.5.tgz", - "integrity": "sha512-qNR/kh6bz0Dyv3m68Ck2g1fLW5KlSOUNcFQh87VXHZwWc/gY6XwnKofx76Qytz3x5LDWT09/2+yXndTkaG4aWg==", "dev": true, + "license": "MIT", "dependencies": { "chalk": "^4.0.0", "jest-diff": "^27.2.5", @@ -1830,9 +1761,8 @@ }, "node_modules/jest-matcher-utils/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1845,9 +1775,8 @@ }, "node_modules/jest-matcher-utils/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1861,9 +1790,8 @@ }, "node_modules/jest-matcher-utils/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1873,24 +1801,21 @@ }, "node_modules/jest-matcher-utils/node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jest-matcher-utils/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/jest-matcher-utils/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -1900,9 +1825,8 @@ }, "node_modules/jest-message-util": { "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.2.tgz", - "integrity": "sha512-OMRqRNd9E0DkBLZpFtZkAGYOXl6ZpoMtQJWTAREJKDOFa0M6ptB7L67tp+cszMBkvSgKOhNtQp2Vbcz3ZZKo/w==", "dev": true, + "license": "MIT", "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^27.4.2", @@ -1920,9 +1844,8 @@ }, "node_modules/jest-message-util/node_modules/ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^2.0.1" }, @@ -1935,9 +1858,8 @@ }, "node_modules/jest-message-util/node_modules/chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" @@ -1951,9 +1873,8 @@ }, "node_modules/jest-message-util/node_modules/color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "~1.1.4" }, @@ -1963,24 +1884,21 @@ }, "node_modules/jest-message-util/node_modules/color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/jest-message-util/node_modules/has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/jest-message-util/node_modules/supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, + "license": "MIT", "dependencies": { "has-flag": "^4.0.0" }, @@ -1990,9 +1908,8 @@ }, "node_modules/jest-regex-util": { "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", - "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", "dev": true, + "license": "MIT", "engines": { "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" } @@ -2022,13 +1939,10 @@ } }, "node_modules/json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, "bin": { "json5": "lib/cli.js" }, @@ -2038,9 +1952,8 @@ }, "node_modules/jsonfile": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -2050,9 +1963,8 @@ }, "node_modules/kind-of": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, + "license": "MIT", "dependencies": { "is-buffer": "^1.1.5" }, @@ -2062,18 +1974,16 @@ }, "node_modules/lazy-cache": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "p-locate": "^4.1.0" @@ -2084,9 +1994,8 @@ }, "node_modules/merge-deep": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", - "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", "dev": true, + "license": "MIT", "dependencies": { "arr-union": "^3.1.0", "clone-deep": "^0.2.4", @@ -2098,9 +2007,8 @@ }, "node_modules/micromatch": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "dev": true, + "license": "MIT", "dependencies": { "braces": "^3.0.1", "picomatch": "^2.2.3" @@ -2110,22 +2018,21 @@ } }, "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "dev": true, "bin": { "mime": "cli.js" }, "engines": { - "node": ">=4.0.0" + "node": ">=10.0.0" } }, "node_modules/minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2133,17 +2040,10 @@ "node": "*" } }, - "node_modules/minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, "node_modules/mixin-object": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", "dev": true, + "license": "MIT", "dependencies": { "for-in": "^0.1.3", "is-extendable": "^0.1.1" @@ -2154,31 +2054,27 @@ }, "node_modules/mixin-object/node_modules/for-in": { "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/mkdirp-classic": { "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/node-fetch": { "version": "2.6.5", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", - "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "whatwg-url": "^5.0.0" @@ -2188,9 +2084,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", "dev": true }, "node_modules/object-keys": { @@ -2222,18 +2118,16 @@ }, "node_modules/once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "license": "ISC", "dependencies": { "wrappy": "1" } }, "node_modules/open": { "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", "dev": true, + "license": "MIT", "dependencies": { "define-lazy-prop": "^2.0.0", "is-docker": "^2.1.1", @@ -2248,9 +2142,8 @@ }, "node_modules/p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "p-try": "^2.0.0" @@ -2264,9 +2157,8 @@ }, "node_modules/p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "p-limit": "^2.2.0" @@ -2277,9 +2169,8 @@ }, "node_modules/p-try": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=6" @@ -2287,9 +2178,8 @@ }, "node_modules/path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=8" @@ -2297,18 +2187,16 @@ }, "node_modules/path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/pend": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/picocolors": { "version": "1.0.0", @@ -2318,9 +2206,8 @@ }, "node_modules/picomatch": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.6" }, @@ -2330,9 +2217,8 @@ }, "node_modules/pirates": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", - "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 6" } @@ -2360,9 +2246,8 @@ }, "node_modules/pkg-dir": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "find-up": "^4.0.0" @@ -2372,13 +2257,13 @@ } }, "node_modules/playwright": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.17.1.tgz", - "integrity": "sha512-DisCkW9MblDJNS3rG61p8LiLA2WA7IY/4A4W7DX4BphWe/HuWjKmGQptuk4NVIh5UuSwXpW/jaH2+ZgjHs3GMA==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.20.1.tgz", + "integrity": "sha512-d/25SFUk6Rkt3h+RU13T7h6o0UTCLKXKYJILWVlC+NmrE7Tvn3LlXxoREfFXVNFikRZWTV60WBCZKgNbj7RfrA==", "dev": true, "hasInstallScript": true, "dependencies": { - "playwright-core": "=1.17.1" + "playwright-core": "1.20.1" }, "bin": { "playwright": "cli.js" @@ -2388,27 +2273,29 @@ } }, "node_modules/playwright-core": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.17.1.tgz", - "integrity": "sha512-C3c8RpPiC3qr15fRDN6dx6WnUkPLFmST37gms2aoHPDRvp7EaGDPMMZPpqIm/QWB5J40xDrQCD4YYHz2nBTojQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.20.1.tgz", + "integrity": "sha512-A8ZsZ09gaSbxP0UijoLyzp3LJc0kWMxDooLPi+mm4/5iYnTbd6PF5nKjoFw1a7KwjZIEgdhJduah4BcUIh+IPA==", "dev": true, "dependencies": { - "commander": "^8.2.0", - "debug": "^4.1.1", - "extract-zip": "^2.0.1", - "https-proxy-agent": "^5.0.0", - "jpeg-js": "^0.4.2", - "mime": "^2.4.6", - "pngjs": "^5.0.0", - "progress": "^2.0.3", - "proper-lockfile": "^4.1.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "socks-proxy-agent": "^6.1.0", - "stack-utils": "^2.0.3", - "ws": "^7.4.6", - "yauzl": "^2.10.0", - "yazl": "^2.5.1" + "colors": "1.4.0", + "commander": "8.3.0", + "debug": "4.3.3", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "jpeg-js": "0.4.3", + "mime": "3.0.0", + "pixelmatch": "5.2.1", + "pngjs": "6.0.0", + "progress": "2.0.3", + "proper-lockfile": "4.1.2", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "socks-proxy-agent": "6.1.1", + "stack-utils": "2.0.5", + "ws": "8.4.2", + "yauzl": "2.10.0", + "yazl": "2.5.1" }, "bin": { "playwright": "cli.js" @@ -2418,19 +2305,18 @@ } }, "node_modules/pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", "dev": true, "engines": { - "node": ">=10.13.0" + "node": ">=12.13.0" } }, "node_modules/pretty-format": { "version": "27.4.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.2.tgz", - "integrity": "sha512-p0wNtJ9oLuvgOQDEIZ9zQjZffK7KtyR6Si0jnXULIDwrlNF8Cuir3AZP0hHv0jmKuNN/edOnbMjnzd4uTcmWiw==", "dev": true, + "license": "MIT", "dependencies": { "@jest/types": "^27.4.2", "ansi-regex": "^5.0.1", @@ -2443,9 +2329,8 @@ }, "node_modules/pretty-format/node_modules/ansi-styles": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" }, @@ -2455,9 +2340,8 @@ }, "node_modules/progress": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -2475,15 +2359,13 @@ }, "node_modules/proxy-from-env": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/pump": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, + "license": "MIT", "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -2491,10 +2373,9 @@ }, "node_modules/puppeteer": { "version": "13.0.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-13.0.1.tgz", - "integrity": "sha512-wqGIx59LzYqWhYcJQphMT+ux0sgatEUbjKG0lbjJxNVqVIT3ZC5m4Bvmq2gHE3qhb63EwS+rNkql08bm4BvO0A==", "dev": true, "hasInstallScript": true, + "license": "Apache-2.0", "peer": true, "dependencies": { "debug": "4.3.2", @@ -2516,9 +2397,8 @@ }, "node_modules/puppeteer-extra": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.2.3.tgz", - "integrity": "sha512-CnSN9yIedbAbS8WmRybaDHJLf6goRk+VYM/kbH6i/+EMadCaAeh2O+1/mFUMN2LbkbDNAp2Vd/UwrTVCHjTxyg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "@types/debug": "^4.1.0", @@ -2535,9 +2415,8 @@ }, "node_modules/puppeteer-extra-plugin": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.0.tgz", - "integrity": "sha512-wbiw12USE3b+maMk/IMaroYsz7rusVI9G+ml6pCFCnFFh91Z9BAEiVzhCpOHuquVXEiCCsDTWhDUgvdNxQHOyw==", "dev": true, + "license": "MIT", "dependencies": { "@types/debug": "^4.1.0", "debug": "^4.1.1", @@ -2552,9 +2431,8 @@ }, "node_modules/puppeteer-extra-plugin-stealth": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.9.0.tgz", - "integrity": "sha512-erZ9lkIcOkfYmLPP2jv2AiqvNBFhQJinWJhcm40pqSjwJTsZXHsTARUyRCsBYEEBvNIs3Wz3E0zVlTRc4IJ6Hg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.1", "puppeteer-extra-plugin": "^3.2.0", @@ -2566,9 +2444,8 @@ }, "node_modules/puppeteer-extra-plugin-user-data-dir": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.3.1.tgz", - "integrity": "sha512-yhaYMaNFdfQ1LbA94ZElW1zU8rh+MFmO+GZA0gtQ8BXc+UZ6aRrWS9flIZvlXDzk+ZsXhCbTEohEwZ8lEDLRVA==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.1", "fs-extra": "^10.0.0", @@ -2580,9 +2457,8 @@ }, "node_modules/puppeteer-extra-plugin-user-preferences": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.3.1.tgz", - "integrity": "sha512-t/FyGQj2aqtHOROqL02z+k2kNQe0cjT0Hd9pG5FJ7x0JXx1722PhOuK7FeJLQMJ+BLl2YvCUgaWSC8Zohjts5A==", "dev": true, + "license": "MIT", "dependencies": { "debug": "^4.1.1", "deepmerge": "^4.2.2", @@ -2595,9 +2471,8 @@ }, "node_modules/puppeteer/node_modules/debug": { "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "ms": "2.1.2" @@ -2613,16 +2488,14 @@ }, "node_modules/puppeteer/node_modules/ms": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, + "license": "MIT", "peer": true }, "node_modules/puppeteer/node_modules/ws": { "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", "dev": true, + "license": "MIT", "peer": true, "engines": { "node": ">=10.0.0" @@ -2642,15 +2515,13 @@ }, "node_modules/react-is": { "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/readable-stream": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "inherits": "^2.0.3", @@ -2672,9 +2543,8 @@ }, "node_modules/rimraf": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -2687,9 +2557,8 @@ }, "node_modules/safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/semver": { "version": "6.3.0", @@ -2702,9 +2571,8 @@ }, "node_modules/shallow-clone": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", - "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", "dev": true, + "license": "MIT", "dependencies": { "is-extendable": "^0.1.1", "kind-of": "^2.0.1", @@ -2717,9 +2585,8 @@ }, "node_modules/shallow-clone/node_modules/kind-of": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", - "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", "dev": true, + "license": "MIT", "dependencies": { "is-buffer": "^1.0.2" }, @@ -2729,24 +2596,22 @@ }, "node_modules/shallow-clone/node_modules/lazy-cache": { "version": "0.2.7", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/signal-exit": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, "node_modules/slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2762,13 +2627,13 @@ } }, "node_modules/socks": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", - "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", + "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", "dev": true, "dependencies": { "ip": "^1.1.5", - "smart-buffer": "^4.1.0" + "smart-buffer": "^4.2.0" }, "engines": { "node": ">= 10.13.0", @@ -2791,27 +2656,24 @@ }, "node_modules/source-map": { "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } }, "node_modules/source-map-support": { "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, + "license": "MIT", "dependencies": { "source-map": "^0.5.6" } }, "node_modules/stack-utils": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", "dev": true, + "license": "MIT", "dependencies": { "escape-string-regexp": "^2.0.0" }, @@ -2821,18 +2683,16 @@ }, "node_modules/stack-utils/node_modules/escape-string-regexp": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, "node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "safe-buffer": "~5.2.0" @@ -2840,8 +2700,6 @@ }, "node_modules/string_decoder/node_modules/safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "funding": [ { @@ -2857,6 +2715,7 @@ "url": "https://feross.org/support" } ], + "license": "MIT", "peer": true }, "node_modules/supports-color": { @@ -2873,9 +2732,8 @@ }, "node_modules/tar-fs": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "chownr": "^1.1.1", @@ -2886,9 +2744,8 @@ }, "node_modules/tar-stream": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "bl": "^4.0.3", @@ -2903,9 +2760,8 @@ }, "node_modules/through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true, + "license": "MIT", "peer": true }, "node_modules/to-fast-properties": { @@ -2919,9 +2775,8 @@ }, "node_modules/to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, + "license": "MIT", "dependencies": { "is-number": "^7.0.0" }, @@ -2931,16 +2786,14 @@ }, "node_modules/tr46": { "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "dev": true, + "license": "MIT", "peer": true }, "node_modules/unbzip2-stream": { "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "buffer": "^5.2.1", @@ -2949,32 +2802,28 @@ }, "node_modules/universalify": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } }, "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, + "license": "MIT", "peer": true }, "node_modules/webidl-conversions": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", "dev": true, + "license": "BSD-2-Clause", "peer": true }, "node_modules/whatwg-url": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", "dev": true, + "license": "MIT", "peer": true, "dependencies": { "tr46": "~0.0.3", @@ -2983,17 +2832,16 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "dev": true, + "license": "ISC" }, "node_modules/ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz", + "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==", "dev": true, "engines": { - "node": ">=8.3.0" + "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", @@ -3010,9 +2858,8 @@ }, "node_modules/yauzl": { "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" @@ -3020,9 +2867,8 @@ }, "node_modules/yazl": { "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", - "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", "dev": true, + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3" } @@ -3030,35 +2876,35 @@ }, "dependencies": { "@babel/code-frame": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz", - "integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", + "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", "dev": true, "requires": { - "@babel/highlight": "^7.16.0" + "@babel/highlight": "^7.16.7" } }, "@babel/compat-data": { - "version": "7.16.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.16.4.tgz", - "integrity": "sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", + "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", "dev": true }, "@babel/core": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.5.tgz", - "integrity": "sha512-wUcenlLzuWMZ9Zt8S0KmFwGlH6QKRh3vsm/dhDA3CHkiTA45YuG1XkHRcNRl73EFPXDp/d5kVOU0/y7x2w6OaQ==", + "version": "7.16.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", + "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.5", - "@babel/helper-compilation-targets": "^7.16.3", - "@babel/helper-module-transforms": "^7.16.5", - "@babel/helpers": "^7.16.5", - "@babel/parser": "^7.16.5", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.5", - "@babel/types": "^7.16.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.16.8", + "@babel/helper-compilation-targets": "^7.16.7", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helpers": "^7.16.7", + "@babel/parser": "^7.16.12", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.16.10", + "@babel/types": "^7.16.8", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -3068,159 +2914,159 @@ } }, "@babel/generator": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.5.tgz", - "integrity": "sha512-kIvCdjZqcdKqoDbVVdt5R99icaRtrtYhYK/xux5qiWCBmfdvEYMFZ68QCrpE5cbFM1JsuArUNs1ZkuKtTtUcZA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", + "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", "dev": true, "requires": { - "@babel/types": "^7.16.0", + "@babel/types": "^7.17.0", "jsesc": "^2.5.1", "source-map": "^0.5.0" } }, "@babel/helper-annotate-as-pure": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz", - "integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", + "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-compilation-targets": { - "version": "7.16.3", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.16.3.tgz", - "integrity": "sha512-vKsoSQAyBmxS35JUOOt+07cLc6Nk/2ljLIHwmq2/NM6hdioUaqEXq/S+nXvbvXbZkNDlWOymPanJGOc4CBjSJA==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", + "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", "dev": true, "requires": { - "@babel/compat-data": "^7.16.0", - "@babel/helper-validator-option": "^7.14.5", + "@babel/compat-data": "^7.17.7", + "@babel/helper-validator-option": "^7.16.7", "browserslist": "^4.17.5", "semver": "^6.3.0" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.5.tgz", - "integrity": "sha512-NEohnYA7mkB8L5JhU7BLwcBdU3j83IziR9aseMueWGeAjblbul3zzb8UvJ3a1zuBiqCMObzCJHFqKIQE6hTVmg==", + "version": "7.17.6", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", + "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-member-expression-to-functions": "^7.16.5", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/helper-replace-supers": "^7.16.5", - "@babel/helper-split-export-declaration": "^7.16.0" + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/helper-replace-supers": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7" } }, "@babel/helper-environment-visitor": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.5.tgz", - "integrity": "sha512-ODQyc5AnxmZWm/R2W7fzhamOk1ey8gSguo5SGvF0zcB3uUzRpTRmM/jmLSm9bDMyPlvbyJ+PwPEK0BWIoZ9wjg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", + "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-function-name": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz", - "integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", + "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", "dev": true, "requires": { - "@babel/helper-get-function-arity": "^7.16.0", - "@babel/template": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/helper-get-function-arity": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/helper-get-function-arity": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz", - "integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", + "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-hoist-variables": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz", - "integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", + "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-member-expression-to-functions": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.16.5.tgz", - "integrity": "sha512-7fecSXq7ZrLE+TWshbGT+HyCLkxloWNhTbU2QM1NTI/tDqyf0oZiMcEfYtDuUDCo528EOlt39G1rftea4bRZIw==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", + "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.17.0" } }, "@babel/helper-module-imports": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz", - "integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", + "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-module-transforms": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.16.5.tgz", - "integrity": "sha512-CkvMxgV4ZyyioElFwcuWnDCcNIeyqTkCm9BxXZi73RR1ozqlpboqsbGUNvRTflgZtFbbJ1v5Emvm+lkjMYY/LQ==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", + "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-module-imports": "^7.16.0", - "@babel/helper-simple-access": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/helper-validator-identifier": "^7.15.7", - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.5", - "@babel/types": "^7.16.0" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-module-imports": "^7.16.7", + "@babel/helper-simple-access": "^7.17.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/helper-validator-identifier": "^7.16.7", + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" } }, "@babel/helper-optimise-call-expression": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.0.tgz", - "integrity": "sha512-SuI467Gi2V8fkofm2JPnZzB/SUuXoJA5zXe/xzyPP2M04686RzFKFHPK6HDVN6JvWBIEW8tt9hPR7fXdn2Lgpw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", + "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-plugin-utils": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.5.tgz", - "integrity": "sha512-59KHWHXxVA9K4HNF4sbHCf+eJeFe0Te/ZFGqBT4OjXhrwvA04sGfaEGsVTdsjoszq0YTP49RC9UKe5g8uN2RwQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", + "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", "dev": true }, "@babel/helper-replace-supers": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.5.tgz", - "integrity": "sha512-ao3seGVa/FZCMCCNDuBcqnBFSbdr8N2EW35mzojx3TwfIbdPmNK+JV6+2d5bR0Z71W5ocLnQp9en/cTF7pBJiQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", + "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", "dev": true, "requires": { - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-member-expression-to-functions": "^7.16.5", - "@babel/helper-optimise-call-expression": "^7.16.0", - "@babel/traverse": "^7.16.5", - "@babel/types": "^7.16.0" + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-member-expression-to-functions": "^7.16.7", + "@babel/helper-optimise-call-expression": "^7.16.7", + "@babel/traverse": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/helper-simple-access": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.16.0.tgz", - "integrity": "sha512-o1rjBT/gppAqKsYfUdfHq5Rk03lMQrkPHG1OWzHWpLgVXRH4HnMM9Et9CVdIqwkCQlobnGHEJMsgWP/jE1zUiw==", + "version": "7.17.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", + "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.17.0" } }, "@babel/helper-skip-transparent-expression-wrappers": { @@ -3233,151 +3079,149 @@ } }, "@babel/helper-split-export-declaration": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz", - "integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", + "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", "dev": true, "requires": { - "@babel/types": "^7.16.0" + "@babel/types": "^7.16.7" } }, "@babel/helper-validator-identifier": { - "version": "7.15.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz", - "integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", + "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", "dev": true }, "@babel/helper-validator-option": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.14.5.tgz", - "integrity": "sha512-OX8D5eeX4XwcroVW45NMvoYaIuFI+GQpA2a8Gi+X/U/cDUIRsV37qQfF905F0htTRCREQIB4KqPeaveRJUl3Ow==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", + "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", "dev": true }, "@babel/helpers": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.16.5.tgz", - "integrity": "sha512-TLgi6Lh71vvMZGEkFuIxzaPsyeYCHQ5jJOOX1f0xXn0uciFuE8cEk0wyBquMcCxBXZ5BJhE2aUB7pnWTD150Tw==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.8.tgz", + "integrity": "sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw==", "dev": true, "requires": { - "@babel/template": "^7.16.0", - "@babel/traverse": "^7.16.5", - "@babel/types": "^7.16.0" + "@babel/template": "^7.16.7", + "@babel/traverse": "^7.17.3", + "@babel/types": "^7.17.0" } }, "@babel/highlight": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz", - "integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==", + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", "js-tokens": "^4.0.0" } }, "@babel/parser": { - "version": "7.16.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.6.tgz", - "integrity": "sha512-Gr86ujcNuPDnNOY8mi383Hvi8IYrJVJYuf3XcuBM/Dgd+bINn/7tHqsj+tKkoreMbmGsFLsltI/JJd8fOFWGDQ==", + "version": "7.17.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz", + "integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==", "dev": true }, "@babel/plugin-proposal-class-properties": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.5.tgz", - "integrity": "sha512-pJD3HjgRv83s5dv1sTnDbZOaTjghKEz8KUn1Kbh2eAIRhGuyQ1XSeI4xVXU3UlIEVA3DAyIdxqT1eRn7Wcn55A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", + "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.5", - "@babel/helper-plugin-utils": "^7.16.5" + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-proposal-dynamic-import": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.5.tgz", - "integrity": "sha512-P05/SJZTTvHz79LNYTF8ff5xXge0kk5sIIWAypcWgX4BTRUgyHc8wRxJ/Hk+mU0KXldgOOslKaeqnhthcDJCJQ==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", + "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" } }, "@babel/plugin-proposal-export-namespace-from": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.5.tgz", - "integrity": "sha512-i+sltzEShH1vsVydvNaTRsgvq2vZsfyrd7K7vPLUU/KgS0D5yZMe6uipM0+izminnkKrEfdUnz7CxMRb6oHZWw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", + "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.5.tgz", - "integrity": "sha512-xqibl7ISO2vjuQM+MzR3rkd0zfNWltk7n9QhaD8ghMmMceVguYrNDt7MikRyj4J4v3QehpnrU8RYLnC7z/gZLA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", + "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.5.tgz", - "integrity": "sha512-YwMsTp/oOviSBhrjwi0vzCUycseCYwoXnLiXIL3YNjHSMBHicGTz7GjVU/IGgz4DtOEXBdCNG72pvCX22ehfqg==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", + "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" } }, "@babel/plugin-proposal-numeric-separator": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.5.tgz", - "integrity": "sha512-DvB9l/TcsCRvsIV9v4jxR/jVP45cslTVC0PMVHvaJhhNuhn2Y1SOhCSFlPK777qLB5wb8rVDaNoqMTyOqtY5Iw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", + "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-proposal-optional-chaining": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.5.tgz", - "integrity": "sha512-kzdHgnaXRonttiTfKYnSVafbWngPPr2qKw9BWYBESl91W54e+9R5pP70LtWxV56g0f05f/SQrwHYkfvbwcdQ/A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", + "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", "@babel/plugin-syntax-optional-chaining": "^7.8.3" } }, "@babel/plugin-proposal-private-methods": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.5.tgz", - "integrity": "sha512-+yFMO4BGT3sgzXo+lrq7orX5mAZt57DwUK6seqII6AcJnJOIhBJ8pzKH47/ql/d426uQ7YhN8DpUFirQzqYSUA==", + "version": "7.16.11", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", + "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.5", - "@babel/helper-plugin-utils": "^7.16.5" + "@babel/helper-create-class-features-plugin": "^7.16.10", + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-proposal-private-property-in-object": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.5.tgz", - "integrity": "sha512-+YGh5Wbw0NH3y/E5YMu6ci5qTDmAEVNoZ3I54aB6nVEOZ5BQ7QJlwKq5pYVucQilMByGn/bvX0af+uNaPRCabA==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", + "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", "dev": true, "requires": { - "@babel/helper-annotate-as-pure": "^7.16.0", - "@babel/helper-create-class-features-plugin": "^7.16.5", - "@babel/helper-plugin-utils": "^7.16.5", + "@babel/helper-annotate-as-pure": "^7.16.7", + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" } }, "@babel/plugin-syntax-async-generators": { "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" @@ -3403,8 +3247,6 @@ }, "@babel/plugin-syntax-json-strings": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" @@ -3439,8 +3281,6 @@ }, "@babel/plugin-syntax-object-rest-spread": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" @@ -3448,8 +3288,6 @@ }, "@babel/plugin-syntax-optional-catch-binding": { "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", "dev": true, "requires": { "@babel/helper-plugin-utils": "^7.8.0" @@ -3474,91 +3312,89 @@ } }, "@babel/plugin-syntax-typescript": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.5.tgz", - "integrity": "sha512-/d4//lZ1Vqb4mZ5xTep3dDK888j7BGM/iKqBmndBaoYAFPlPKrGU608VVBz5JeyAb6YQDjRu1UKqj86UhwWVgw==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", + "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.5" + "@babel/helper-plugin-utils": "^7.16.7" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.5.tgz", - "integrity": "sha512-ABhUkxvoQyqhCWyb8xXtfwqNMJD7tx+irIRnUh6lmyFud7Jln1WzONXKlax1fg/ey178EXbs4bSGNd6PngO+SQ==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", + "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", "dev": true, "requires": { - "@babel/helper-module-transforms": "^7.16.5", - "@babel/helper-plugin-utils": "^7.16.5", - "@babel/helper-simple-access": "^7.16.0", + "@babel/helper-module-transforms": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-simple-access": "^7.16.7", "babel-plugin-dynamic-import-node": "^2.3.3" } }, "@babel/plugin-transform-typescript": { - "version": "7.16.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.1.tgz", - "integrity": "sha512-NO4XoryBng06jjw/qWEU2LhcLJr1tWkhpMam/H4eas/CDKMX/b2/Ylb6EI256Y7+FVPCawwSM1rrJNOpDiz+Lg==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz", + "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==", "dev": true, "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.0", - "@babel/helper-plugin-utils": "^7.14.5", - "@babel/plugin-syntax-typescript": "^7.16.0" + "@babel/helper-create-class-features-plugin": "^7.16.7", + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/plugin-syntax-typescript": "^7.16.7" } }, "@babel/preset-typescript": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.5.tgz", - "integrity": "sha512-lmAWRoJ9iOSvs3DqOndQpj8XqXkzaiQs50VG/zESiI9D3eoZhGriU675xNCr0UwvsuXrhMAGvyk1w+EVWF3u8Q==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz", + "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==", "dev": true, "requires": { - "@babel/helper-plugin-utils": "^7.16.5", - "@babel/helper-validator-option": "^7.14.5", - "@babel/plugin-transform-typescript": "^7.16.1" + "@babel/helper-plugin-utils": "^7.16.7", + "@babel/helper-validator-option": "^7.16.7", + "@babel/plugin-transform-typescript": "^7.16.7" } }, "@babel/template": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz", - "integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==", + "version": "7.16.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", + "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/parser": "^7.16.0", - "@babel/types": "^7.16.0" + "@babel/code-frame": "^7.16.7", + "@babel/parser": "^7.16.7", + "@babel/types": "^7.16.7" } }, "@babel/traverse": { - "version": "7.16.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.5.tgz", - "integrity": "sha512-FOCODAzqUMROikDYLYxl4nmwiLlu85rNqBML/A5hKRVXG2LV8d0iMqgPzdYTcIpjZEBB7D6UDU9vxRZiriASdQ==", + "version": "7.17.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", + "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", "dev": true, "requires": { - "@babel/code-frame": "^7.16.0", - "@babel/generator": "^7.16.5", - "@babel/helper-environment-visitor": "^7.16.5", - "@babel/helper-function-name": "^7.16.0", - "@babel/helper-hoist-variables": "^7.16.0", - "@babel/helper-split-export-declaration": "^7.16.0", - "@babel/parser": "^7.16.5", - "@babel/types": "^7.16.0", + "@babel/code-frame": "^7.16.7", + "@babel/generator": "^7.17.3", + "@babel/helper-environment-visitor": "^7.16.7", + "@babel/helper-function-name": "^7.16.7", + "@babel/helper-hoist-variables": "^7.16.7", + "@babel/helper-split-export-declaration": "^7.16.7", + "@babel/parser": "^7.17.3", + "@babel/types": "^7.17.0", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz", - "integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==", + "version": "7.17.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", + "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", "dev": true, "requires": { - "@babel/helper-validator-identifier": "^7.15.7", + "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" } }, "@jest/types": { "version": "27.4.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-27.4.2.tgz", - "integrity": "sha512-j35yw0PMTPpZsUoOBiuHzr1zTYoad1cVIE0ajEjcrJONxxrko/IRGKkXx3os0Nsi4Hu3+5VmDbVfq5WhG/pWAg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "^2.0.0", @@ -3570,8 +3406,6 @@ "dependencies": { "ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" @@ -3579,8 +3413,6 @@ }, "chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -3589,8 +3421,6 @@ }, "color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" @@ -3598,20 +3428,14 @@ }, "color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -3620,52 +3444,49 @@ } }, "@playwright/test": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.17.1.tgz", - "integrity": "sha512-mMZS5OMTN/vUlqd1JZkFoAk2FsIZ4/E/00tw5it2c/VF4+3z/aWO+PPd8ShEGzYME7B16QGWNPjyFpDQI1t4RQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.20.1.tgz", + "integrity": "sha512-muk3KZXfA7sXTwUEXfL3m4tusj/MBGYjxIFmooi+F2Pf6hKjjVl4+8niy77Xujk4jpL7hZbbqq9v5bRl2m+C8Q==", "dev": true, "requires": { - "@babel/code-frame": "^7.14.5", - "@babel/core": "^7.14.8", - "@babel/plugin-proposal-class-properties": "^7.14.5", - "@babel/plugin-proposal-dynamic-import": "^7.14.5", - "@babel/plugin-proposal-export-namespace-from": "^7.14.5", - "@babel/plugin-proposal-logical-assignment-operators": "^7.14.5", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.5", - "@babel/plugin-proposal-numeric-separator": "^7.14.5", - "@babel/plugin-proposal-optional-chaining": "^7.14.5", - "@babel/plugin-proposal-private-methods": "^7.14.5", - "@babel/plugin-proposal-private-property-in-object": "^7.14.5", - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-transform-modules-commonjs": "^7.14.5", - "@babel/preset-typescript": "^7.14.5", - "colors": "^1.4.0", - "commander": "^8.2.0", - "debug": "^4.1.1", - "expect": "=27.2.5", - "jest-matcher-utils": "=27.2.5", - "jpeg-js": "^0.4.2", - "mime": "^2.4.6", - "minimatch": "^3.0.3", - "ms": "^2.1.2", - "open": "^8.3.0", - "pirates": "^4.0.1", - "pixelmatch": "^5.2.1", - "playwright-core": "=1.17.1", - "pngjs": "^5.0.0", - "rimraf": "^3.0.2", - "source-map-support": "^0.4.18", - "stack-utils": "^2.0.3", - "yazl": "^2.5.1" + "@babel/code-frame": "7.16.7", + "@babel/core": "7.16.12", + "@babel/helper-plugin-utils": "7.16.7", + "@babel/plugin-proposal-class-properties": "7.16.7", + "@babel/plugin-proposal-dynamic-import": "7.16.7", + "@babel/plugin-proposal-export-namespace-from": "7.16.7", + "@babel/plugin-proposal-logical-assignment-operators": "7.16.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7", + "@babel/plugin-proposal-numeric-separator": "7.16.7", + "@babel/plugin-proposal-optional-chaining": "7.16.7", + "@babel/plugin-proposal-private-methods": "7.16.11", + "@babel/plugin-proposal-private-property-in-object": "7.16.7", + "@babel/plugin-syntax-async-generators": "7.8.4", + "@babel/plugin-syntax-json-strings": "7.8.3", + "@babel/plugin-syntax-object-rest-spread": "7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "7.8.3", + "@babel/plugin-transform-modules-commonjs": "7.16.8", + "@babel/preset-typescript": "7.16.7", + "colors": "1.4.0", + "commander": "8.3.0", + "debug": "4.3.3", + "expect": "27.2.5", + "jest-matcher-utils": "27.2.5", + "json5": "2.2.1", + "mime": "3.0.0", + "minimatch": "3.0.4", + "ms": "2.1.3", + "open": "8.4.0", + "pirates": "4.0.4", + "playwright-core": "1.20.1", + "rimraf": "3.0.2", + "source-map-support": "0.4.18", + "stack-utils": "2.0.5", + "yazl": "2.5.1" } }, "@types/debug": { "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", "dev": true, "requires": { "@types/ms": "*" @@ -3673,14 +3494,10 @@ }, "@types/istanbul-lib-coverage": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", - "integrity": "sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==", "dev": true }, "@types/istanbul-lib-report": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", "dev": true, "requires": { "@types/istanbul-lib-coverage": "*" @@ -3688,8 +3505,6 @@ }, "@types/istanbul-reports": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz", - "integrity": "sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==", "dev": true, "requires": { "@types/istanbul-lib-report": "*" @@ -3697,20 +3512,14 @@ }, "@types/ms": { "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", "dev": true }, "@types/node": { "version": "17.0.5", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.5.tgz", - "integrity": "sha512-w3mrvNXLeDYV1GKTZorGJQivK6XLCoGwpnyJFbJVK/aTBQUxOCaa/GlFAAN3OTDFcb7h5tiFG+YXCO2By+riZw==", "dev": true }, "@types/puppeteer": { "version": "5.4.3", - "resolved": "https://registry.npmjs.org/@types/puppeteer/-/puppeteer-5.4.3.tgz", - "integrity": "sha512-3nE8YgR9DIsgttLW+eJf6mnXxq8Ge+27m5SU3knWmrlfl6+KOG0Bf9f7Ua7K+C4BnaTMAh3/UpySqdAYvrsvjg==", "dev": true, "peer": true, "requires": { @@ -3719,14 +3528,10 @@ }, "@types/stack-utils": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", - "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==", "dev": true }, "@types/yargs": { "version": "16.0.4", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz", - "integrity": "sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -3734,14 +3539,10 @@ }, "@types/yargs-parser": { "version": "20.2.1", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.1.tgz", - "integrity": "sha512-7tFImggNeNBVMsn0vLrpn1H1uPrUBdnARPTpZoitY37ZrdJREzf7I16tMrlK3hen349gr1NYh8CmZQa7CTG6Aw==", "dev": true }, "@types/yauzl": { "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", "dev": true, "optional": true, "requires": { @@ -3750,8 +3551,6 @@ }, "agent-base": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, "requires": { "debug": "4" @@ -3759,8 +3558,6 @@ }, "ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true }, "ansi-styles": { @@ -3774,8 +3571,6 @@ }, "arr-union": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, "babel-plugin-dynamic-import-node": { @@ -3789,21 +3584,15 @@ }, "balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, "base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, "peer": true }, "bl": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", "dev": true, "peer": true, "requires": { @@ -3814,8 +3603,6 @@ }, "brace-expansion": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -3824,30 +3611,26 @@ }, "braces": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { "fill-range": "^7.0.1" } }, "browserslist": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.19.1.tgz", - "integrity": "sha512-u2tbbG5PdKRTUoctO3NBD8FQ5HdPh1ZXPHzp1rwaa5jTc+RV9/+RlWiAIKmjRPQF+xbGM9Kklj5bZQFa2s/38A==", + "version": "4.20.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", + "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", "dev": true, "requires": { - "caniuse-lite": "^1.0.30001286", - "electron-to-chromium": "^1.4.17", + "caniuse-lite": "^1.0.30001317", + "electron-to-chromium": "^1.4.84", "escalade": "^3.1.1", - "node-releases": "^2.0.1", + "node-releases": "^2.0.2", "picocolors": "^1.0.0" } }, "buffer": { "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, "peer": true, "requires": { @@ -3857,8 +3640,6 @@ }, "buffer-crc32": { "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", "dev": true }, "call-bind": { @@ -3872,9 +3653,9 @@ } }, "caniuse-lite": { - "version": "1.0.30001292", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001292.tgz", - "integrity": "sha512-jnT4Tq0Q4ma+6nncYQVe7d73kmDmE9C3OGTx3MvW7lBM/eY1S1DZTMBON7dqV481RhNiS5OxD7k9JQvmDOTirw==", + "version": "1.0.30001322", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001322.tgz", + "integrity": "sha512-neRmrmIrCGuMnxGSoh+x7zYtQFFgnSY2jaomjU56sCkTA6JINqQrxutF459JpWcWRajvoyn95sOXq4Pqrnyjew==", "dev": true }, "chalk": { @@ -3890,15 +3671,11 @@ }, "chownr": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "dev": true, "peer": true }, "clone-deep": { "version": "0.2.4", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", - "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=", "dev": true, "requires": { "for-own": "^0.1.3", @@ -3910,8 +3687,6 @@ "dependencies": { "is-plain-object": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { "isobject": "^3.0.1" @@ -3936,26 +3711,18 @@ }, "colors": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true }, "commander": { "version": "8.3.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", - "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", "dev": true }, "concat-map": { "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "convert-source-map": { "version": "1.8.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", - "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "dev": true, "requires": { "safe-buffer": "~5.1.1" @@ -3963,8 +3730,6 @@ }, "debug": { "version": "4.3.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", - "integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==", "dev": true, "requires": { "ms": "2.1.2" @@ -3972,22 +3737,16 @@ "dependencies": { "ms": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true } } }, "deepmerge": { "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, "define-lazy-prop": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", "dev": true }, "define-properties": { @@ -4001,27 +3760,21 @@ }, "devtools-protocol": { "version": "0.0.937139", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.937139.tgz", - "integrity": "sha512-daj+rzR3QSxsPRy5vjjthn58axO8c11j58uY0lG5vvlJk/EiOdCWOptGdkXDjtuRHr78emKq0udHCXM4trhoDQ==", "dev": true, "peer": true }, "diff-sequences": { "version": "27.4.0", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.4.0.tgz", - "integrity": "sha512-YqiQzkrsmHMH5uuh8OdQFU9/ZpADnwzml8z0O5HvRNda+5UZsaX/xN+AAxfR2hWq1Y7HZnAzO9J5lJXOuDz2Ww==", "dev": true }, "electron-to-chromium": { - "version": "1.4.28", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.28.tgz", - "integrity": "sha512-Gzbf0wUtKfyPaqf0Plz+Ctinf9eQIzxEqBHwSvbGfeOm9GMNdLxyu1dNiCUfM+x6r4BE0xUJNh3Nmg9gfAtTmg==", + "version": "1.4.101", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.101.tgz", + "integrity": "sha512-XJH+XmJjACx1S7ASl/b//KePcda5ocPnFH2jErztXcIS8LpP0SE6rX8ZxiY5/RaDPnaF1rj0fPaHfppzb0e2Aw==", "dev": true }, "end-of-stream": { "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "requires": { "once": "^1.4.0" @@ -4041,8 +3794,6 @@ }, "expect": { "version": "27.2.5", - "resolved": "https://registry.npmjs.org/expect/-/expect-27.2.5.tgz", - "integrity": "sha512-ZrO0w7bo8BgGoP/bLz+HDCI+0Hfei9jUSZs5yI/Wyn9VkG9w8oJ7rHRgYj+MA7yqqFa0IwHA3flJzZtYugShJA==", "dev": true, "requires": { "@jest/types": "^27.2.5", @@ -4055,16 +3806,12 @@ "dependencies": { "ansi-styles": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true } } }, "extract-zip": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, "requires": { "@types/yauzl": "^2.9.1", @@ -4075,8 +3822,6 @@ }, "fd-slicer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, "requires": { "pend": "~1.2.0" @@ -4084,8 +3829,6 @@ }, "fill-range": { "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { "to-regex-range": "^5.0.1" @@ -4093,8 +3836,6 @@ }, "find-up": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", "dev": true, "peer": true, "requires": { @@ -4104,14 +3845,10 @@ }, "for-in": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", "dev": true }, "for-own": { "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", "dev": true, "requires": { "for-in": "^1.0.1" @@ -4119,15 +3856,11 @@ }, "fs-constants": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", "dev": true, "peer": true }, "fs-extra": { "version": "10.0.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.0.tgz", - "integrity": "sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==", "dev": true, "requires": { "graceful-fs": "^4.2.0", @@ -4137,8 +3870,6 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, "function-bind": { @@ -4149,8 +3880,6 @@ }, "gensync": { "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", "dev": true }, "get-intrinsic": { @@ -4166,8 +3895,6 @@ }, "get-stream": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, "requires": { "pump": "^3.0.0" @@ -4175,8 +3902,6 @@ }, "glob": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", @@ -4195,8 +3920,6 @@ }, "graceful-fs": { "version": "4.2.8", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", - "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", "dev": true }, "has": { @@ -4215,15 +3938,13 @@ "dev": true }, "has-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", - "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", "dev": true }, "https-proxy-agent": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", "dev": true, "requires": { "agent-base": "6", @@ -4232,15 +3953,11 @@ }, "ieee754": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true, "peer": true }, "inflight": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { "once": "^1.3.0", @@ -4249,8 +3966,6 @@ }, "inherits": { "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, "ip": { @@ -4261,32 +3976,22 @@ }, "is-buffer": { "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-docker": { "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "dev": true }, "is-extendable": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", "dev": true }, "is-number": { "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "dev": true }, "is-wsl": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", "dev": true, "requires": { "is-docker": "^2.0.0" @@ -4294,14 +3999,10 @@ }, "isobject": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", "dev": true }, "jest-diff": { "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-27.4.2.tgz", - "integrity": "sha512-ujc9ToyUZDh9KcqvQDkk/gkbf6zSaeEg9AiBxtttXW59H/AcqEYp1ciXAtJp+jXWva5nAf/ePtSsgWwE5mqp4Q==", "dev": true, "requires": { "chalk": "^4.0.0", @@ -4312,8 +4013,6 @@ "dependencies": { "ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" @@ -4321,8 +4020,6 @@ }, "chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -4331,8 +4028,6 @@ }, "color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" @@ -4340,20 +4035,14 @@ }, "color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -4363,14 +4052,10 @@ }, "jest-get-type": { "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-27.4.0.tgz", - "integrity": "sha512-tk9o+ld5TWq41DkK14L4wox4s2D9MtTpKaAVzXfr5CUKm5ZK2ExcaFE0qls2W71zE/6R2TxxrK9w2r6svAFDBQ==", "dev": true }, "jest-matcher-utils": { "version": "27.2.5", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-27.2.5.tgz", - "integrity": "sha512-qNR/kh6bz0Dyv3m68Ck2g1fLW5KlSOUNcFQh87VXHZwWc/gY6XwnKofx76Qytz3x5LDWT09/2+yXndTkaG4aWg==", "dev": true, "requires": { "chalk": "^4.0.0", @@ -4381,8 +4066,6 @@ "dependencies": { "ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" @@ -4390,8 +4073,6 @@ }, "chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -4400,8 +4081,6 @@ }, "color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" @@ -4409,20 +4088,14 @@ }, "color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -4432,8 +4105,6 @@ }, "jest-message-util": { "version": "27.4.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-27.4.2.tgz", - "integrity": "sha512-OMRqRNd9E0DkBLZpFtZkAGYOXl6ZpoMtQJWTAREJKDOFa0M6ptB7L67tp+cszMBkvSgKOhNtQp2Vbcz3ZZKo/w==", "dev": true, "requires": { "@babel/code-frame": "^7.12.13", @@ -4449,8 +4120,6 @@ "dependencies": { "ansi-styles": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "requires": { "color-convert": "^2.0.1" @@ -4458,8 +4127,6 @@ }, "chalk": { "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, "requires": { "ansi-styles": "^4.1.0", @@ -4468,8 +4135,6 @@ }, "color-convert": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "requires": { "color-name": "~1.1.4" @@ -4477,20 +4142,14 @@ }, "color-name": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, "has-flag": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, "supports-color": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, "requires": { "has-flag": "^4.0.0" @@ -4500,8 +4159,6 @@ }, "jest-regex-util": { "version": "27.4.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-27.4.0.tgz", - "integrity": "sha512-WeCpMpNnqJYMQoOjm1nTtsgbR4XHAk1u00qDoNBQoykM280+/TmgA5Qh5giC1ecy6a5d4hbSsHzpBtu5yvlbEg==", "dev": true }, "jpeg-js": { @@ -4523,18 +4180,13 @@ "dev": true }, "json5": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", - "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", + "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "dev": true }, "jsonfile": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", @@ -4543,8 +4195,6 @@ }, "kind-of": { "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { "is-buffer": "^1.1.5" @@ -4552,14 +4202,10 @@ }, "lazy-cache": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", "dev": true }, "locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", "dev": true, "peer": true, "requires": { @@ -4568,8 +4214,6 @@ }, "merge-deep": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", - "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -4579,8 +4223,6 @@ }, "micromatch": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", - "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", "dev": true, "requires": { "braces": "^3.0.1", @@ -4588,30 +4230,20 @@ } }, "mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", "dev": true }, "minimatch": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" } }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", - "dev": true - }, "mixin-object": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", - "integrity": "sha1-T7lJRB2rGCVA8f4DW6YOGUel5X4=", "dev": true, "requires": { "for-in": "^0.1.3", @@ -4620,29 +4252,21 @@ "dependencies": { "for-in": { "version": "0.1.8", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha1-2Hc5COMSVhCZUrH9ubP6hn0ndeE=", "dev": true } } }, "mkdirp-classic": { "version": "0.5.3", - "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", - "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true, "peer": true }, "ms": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, "node-fetch": { "version": "2.6.5", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.5.tgz", - "integrity": "sha512-mmlIVHJEu5rnIxgEgez6b9GgWXbkZj5YZ7fx+2r94a2E+Uirsp6HsPTPlomfdHtpt/B0cdKviwkoaM6pyvUOpQ==", "dev": true, "peer": true, "requires": { @@ -4650,9 +4274,9 @@ } }, "node-releases": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.1.tgz", - "integrity": "sha512-CqyzN6z7Q6aMeF/ktcMVTzhAHCEpf8SOarwpzpf8pNBY2k5/oM34UHldUwp8VKI7uxct2HxSRdJjBaZeESzcxA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", + "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", "dev": true }, "object-keys": { @@ -4675,8 +4299,6 @@ }, "once": { "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -4684,8 +4306,6 @@ }, "open": { "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", "dev": true, "requires": { "define-lazy-prop": "^2.0.0", @@ -4695,8 +4315,6 @@ }, "p-limit": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "dev": true, "peer": true, "requires": { @@ -4705,8 +4323,6 @@ }, "p-locate": { "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", "dev": true, "peer": true, "requires": { @@ -4715,28 +4331,20 @@ }, "p-try": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", "dev": true, "peer": true }, "path-exists": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "peer": true }, "path-is-absolute": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, "pend": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", "dev": true }, "picocolors": { @@ -4747,14 +4355,10 @@ }, "picomatch": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", - "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", "dev": true }, "pirates": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.4.tgz", - "integrity": "sha512-ZIrVPH+A52Dw84R0L3/VS9Op04PuQ2SEoJL6bkshmiTic/HldyW9Tf7oH5mhJZBK7NmDx27vSMrYEXPXclpDKw==", "dev": true }, "pixelmatch": { @@ -4776,8 +4380,6 @@ }, "pkg-dir": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", "dev": true, "peer": true, "requires": { @@ -4785,48 +4387,48 @@ } }, "playwright": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.17.1.tgz", - "integrity": "sha512-DisCkW9MblDJNS3rG61p8LiLA2WA7IY/4A4W7DX4BphWe/HuWjKmGQptuk4NVIh5UuSwXpW/jaH2+ZgjHs3GMA==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.20.1.tgz", + "integrity": "sha512-d/25SFUk6Rkt3h+RU13T7h6o0UTCLKXKYJILWVlC+NmrE7Tvn3LlXxoREfFXVNFikRZWTV60WBCZKgNbj7RfrA==", "dev": true, "requires": { - "playwright-core": "=1.17.1" + "playwright-core": "1.20.1" } }, "playwright-core": { - "version": "1.17.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.17.1.tgz", - "integrity": "sha512-C3c8RpPiC3qr15fRDN6dx6WnUkPLFmST37gms2aoHPDRvp7EaGDPMMZPpqIm/QWB5J40xDrQCD4YYHz2nBTojQ==", + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.20.1.tgz", + "integrity": "sha512-A8ZsZ09gaSbxP0UijoLyzp3LJc0kWMxDooLPi+mm4/5iYnTbd6PF5nKjoFw1a7KwjZIEgdhJduah4BcUIh+IPA==", "dev": true, "requires": { - "commander": "^8.2.0", - "debug": "^4.1.1", - "extract-zip": "^2.0.1", - "https-proxy-agent": "^5.0.0", - "jpeg-js": "^0.4.2", - "mime": "^2.4.6", - "pngjs": "^5.0.0", - "progress": "^2.0.3", - "proper-lockfile": "^4.1.1", - "proxy-from-env": "^1.1.0", - "rimraf": "^3.0.2", - "socks-proxy-agent": "^6.1.0", - "stack-utils": "^2.0.3", - "ws": "^7.4.6", - "yauzl": "^2.10.0", - "yazl": "^2.5.1" + "colors": "1.4.0", + "commander": "8.3.0", + "debug": "4.3.3", + "extract-zip": "2.0.1", + "https-proxy-agent": "5.0.0", + "jpeg-js": "0.4.3", + "mime": "3.0.0", + "pixelmatch": "5.2.1", + "pngjs": "6.0.0", + "progress": "2.0.3", + "proper-lockfile": "4.1.2", + "proxy-from-env": "1.1.0", + "rimraf": "3.0.2", + "socks-proxy-agent": "6.1.1", + "stack-utils": "2.0.5", + "ws": "8.4.2", + "yauzl": "2.10.0", + "yazl": "2.5.1" } }, "pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", "dev": true }, "pretty-format": { "version": "27.4.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.4.2.tgz", - "integrity": "sha512-p0wNtJ9oLuvgOQDEIZ9zQjZffK7KtyR6Si0jnXULIDwrlNF8Cuir3AZP0hHv0jmKuNN/edOnbMjnzd4uTcmWiw==", "dev": true, "requires": { "@jest/types": "^27.4.2", @@ -4837,16 +4439,12 @@ "dependencies": { "ansi-styles": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", "dev": true } } }, "progress": { "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", "dev": true }, "proper-lockfile": { @@ -4862,14 +4460,10 @@ }, "proxy-from-env": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true }, "pump": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, "requires": { "end-of-stream": "^1.1.0", @@ -4878,8 +4472,6 @@ }, "puppeteer": { "version": "13.0.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-13.0.1.tgz", - "integrity": "sha512-wqGIx59LzYqWhYcJQphMT+ux0sgatEUbjKG0lbjJxNVqVIT3ZC5m4Bvmq2gHE3qhb63EwS+rNkql08bm4BvO0A==", "dev": true, "peer": true, "requires": { @@ -4899,8 +4491,6 @@ "dependencies": { "debug": { "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, "peer": true, "requires": { @@ -4909,15 +4499,11 @@ }, "ms": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true, "peer": true }, "ws": { "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", "dev": true, "peer": true, "requires": {} @@ -4926,8 +4512,6 @@ }, "puppeteer-extra": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.2.3.tgz", - "integrity": "sha512-CnSN9yIedbAbS8WmRybaDHJLf6goRk+VYM/kbH6i/+EMadCaAeh2O+1/mFUMN2LbkbDNAp2Vd/UwrTVCHjTxyg==", "dev": true, "peer": true, "requires": { @@ -4939,8 +4523,6 @@ }, "puppeteer-extra-plugin": { "version": "3.2.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.0.tgz", - "integrity": "sha512-wbiw12USE3b+maMk/IMaroYsz7rusVI9G+ml6pCFCnFFh91Z9BAEiVzhCpOHuquVXEiCCsDTWhDUgvdNxQHOyw==", "dev": true, "requires": { "@types/debug": "^4.1.0", @@ -4950,8 +4532,6 @@ }, "puppeteer-extra-plugin-stealth": { "version": "2.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.9.0.tgz", - "integrity": "sha512-erZ9lkIcOkfYmLPP2jv2AiqvNBFhQJinWJhcm40pqSjwJTsZXHsTARUyRCsBYEEBvNIs3Wz3E0zVlTRc4IJ6Hg==", "dev": true, "requires": { "debug": "^4.1.1", @@ -4961,8 +4541,6 @@ }, "puppeteer-extra-plugin-user-data-dir": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.3.1.tgz", - "integrity": "sha512-yhaYMaNFdfQ1LbA94ZElW1zU8rh+MFmO+GZA0gtQ8BXc+UZ6aRrWS9flIZvlXDzk+ZsXhCbTEohEwZ8lEDLRVA==", "dev": true, "requires": { "debug": "^4.1.1", @@ -4972,8 +4550,6 @@ }, "puppeteer-extra-plugin-user-preferences": { "version": "2.3.1", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.3.1.tgz", - "integrity": "sha512-t/FyGQj2aqtHOROqL02z+k2kNQe0cjT0Hd9pG5FJ7x0JXx1722PhOuK7FeJLQMJ+BLl2YvCUgaWSC8Zohjts5A==", "dev": true, "requires": { "debug": "^4.1.1", @@ -4984,14 +4560,10 @@ }, "react-is": { "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, "readable-stream": { "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "dev": true, "peer": true, "requires": { @@ -5008,8 +4580,6 @@ }, "rimraf": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" @@ -5017,8 +4587,6 @@ }, "safe-buffer": { "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, "semver": { @@ -5029,8 +4597,6 @@ }, "shallow-clone": { "version": "0.1.2", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", - "integrity": "sha1-WQnodLp3EG1zrEFM/sH/yofZcGA=", "dev": true, "requires": { "is-extendable": "^0.1.1", @@ -5041,8 +4607,6 @@ "dependencies": { "kind-of": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", - "integrity": "sha1-AY7HpM5+OobLkUG+UZ0kyPqpgbU=", "dev": true, "requires": { "is-buffer": "^1.0.2" @@ -5050,22 +4614,18 @@ }, "lazy-cache": { "version": "0.2.7", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha1-f+3fLctu23fRHvHRF6tf/fCrG2U=", "dev": true } } }, "signal-exit": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", - "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", "dev": true }, "slash": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true }, "smart-buffer": { @@ -5075,13 +4635,13 @@ "dev": true }, "socks": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.1.tgz", - "integrity": "sha512-kLQ9N5ucj8uIcxrDwjm0Jsqk06xdpBjGNQtpXy4Q8/QY2k+fY7nZH8CARy+hkbG+SGAovmzzuauCpBlb8FrnBA==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", + "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", "dev": true, "requires": { "ip": "^1.1.5", - "smart-buffer": "^4.1.0" + "smart-buffer": "^4.2.0" } }, "socks-proxy-agent": { @@ -5097,14 +4657,10 @@ }, "source-map": { "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, "source-map-support": { "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", "dev": true, "requires": { "source-map": "^0.5.6" @@ -5112,8 +4668,6 @@ }, "stack-utils": { "version": "2.0.5", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.5.tgz", - "integrity": "sha512-xrQcmYhOsn/1kX+Vraq+7j4oE2j/6BFscZ0etmYg81xuM8Gq0022Pxb8+IqgOFUIaxHs0KaSb7T1+OegiNrNFA==", "dev": true, "requires": { "escape-string-regexp": "^2.0.0" @@ -5121,16 +4675,12 @@ "dependencies": { "escape-string-regexp": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", "dev": true } } }, "string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "peer": true, "requires": { @@ -5139,8 +4689,6 @@ "dependencies": { "safe-buffer": { "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", "dev": true, "peer": true } @@ -5157,8 +4705,6 @@ }, "tar-fs": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", "dev": true, "peer": true, "requires": { @@ -5170,8 +4716,6 @@ }, "tar-stream": { "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "dev": true, "peer": true, "requires": { @@ -5184,8 +4728,6 @@ }, "through": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true, "peer": true }, @@ -5197,8 +4739,6 @@ }, "to-regex-range": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { "is-number": "^7.0.0" @@ -5206,15 +4746,11 @@ }, "tr46": { "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=", "dev": true, "peer": true }, "unbzip2-stream": { "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, "peer": true, "requires": { @@ -5224,28 +4760,20 @@ }, "universalify": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, "util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "peer": true }, "webidl-conversions": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=", "dev": true, "peer": true }, "whatwg-url": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", "dev": true, "peer": true, "requires": { @@ -5255,21 +4783,17 @@ }, "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "ws": { - "version": "7.5.6", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", - "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz", + "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==", "dev": true, "requires": {} }, "yauzl": { "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, "requires": { "buffer-crc32": "~0.2.3", @@ -5278,8 +4802,6 @@ }, "yazl": { "version": "2.5.1", - "resolved": "https://registry.npmjs.org/yazl/-/yazl-2.5.1.tgz", - "integrity": "sha512-phENi2PLiHnHb6QBVot+dJnaAZ0xosj7p3fWl+znIjBDlnMI2PsZCJZ306BPTFOaHf5qdDEI8x5qFrSOBN5vrw==", "dev": true, "requires": { "buffer-crc32": "~0.2.3" From 9416f3769890acdbe8e39387c50c456444a8d744 Mon Sep 17 00:00:00 2001 From: Trung Le Date: Fri, 1 Apr 2022 02:00:49 +0700 Subject: [PATCH 008/520] fix: add screenshots to dockerignore --- .dockerignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.dockerignore b/.dockerignore index eae97d1..35412b7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,9 @@ userDataDir** node_modules +screenshots .gitignore **Dockerfile** .dockerignore +.env +auth.json From 7971be297c0547a2e1cf5de66c3dea963380916f Mon Sep 17 00:00:00 2001 From: Trung Le Date: Sat, 9 Apr 2022 14:50:42 +0700 Subject: [PATCH 009/520] feat: use novnc, fix Thank you for buying timeout --- Dockerfile | 3 +++ docker/vnc-start.sh | 5 ++++- epic-games.js | 7 ++----- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4baeb48..b8c1b90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,9 @@ ENV DISPLAY :60 ENV VNC_ENABLED true ENV VNC_PASSWORD secret ENV VNC_PORT 5900 +ENV NOVNC_PORT 6080 EXPOSE 5900 +EXPOSE 6080 # Playwright ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true @@ -44,6 +46,7 @@ RUN apt-get update \ x11vnc \ curl \ tini \ + novnc websockify \ && apt-get clean \ && rm -rf \ /tmp/* \ diff --git a/docker/vnc-start.sh b/docker/vnc-start.sh index ef28937..92423ea 100755 --- a/docker/vnc-start.sh +++ b/docker/vnc-start.sh @@ -2,7 +2,10 @@ # Start VNC in a background process: x11vnc -display "$DISPLAY" -forever -shared -rfbport "${VNC_PORT:-5900}" \ - -passwd "${VNC_PASSWORD:-secret}" & + -passwd "${VNC_PASSWORD:-secret}" -bg +NOVNC_HOME=/usr/share/novnc +ln -s $NOVNC_HOME/vnc_auto.html $NOVNC_HOME/index.html +websockify -D --web "$NOVNC_HOME" "$NOVNC_PORT" "localhost:$VNC_PORT" & # Execute the given command: exec "$@" diff --git a/epic-games.js b/epic-games.js index 671ff58..1c9e975 100644 --- a/epic-games.js +++ b/epic-games.js @@ -6,7 +6,7 @@ const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https:/ const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; const TIMEOUT = 20 * 1000; // 20s, default is 30s -const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) || 1280; +const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) - 80 || 1280; const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; // https://playwright.dev/docs/auth#multi-factor-authentication @@ -86,10 +86,7 @@ for (let i = 1; i <= n; i++) { // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 const btnAgree = iframe.locator('button:has-text("I Agree")'); try { - await Promise.any([btnAgree.waitFor(), page.waitForSelector('text=Thank you for buying')]); // EU: wait for agree button, non-EU: potentially done - // await clickIfExists('button:has-text("I Agree")', iframe); // default arg: FrameLocator is incompatible with Page and even Locator... - if (await btnAgree.count() > 0) - await btnAgree.click(); + await Promise.any([btnAgree.waitFor().then(() => btnAgree.click()), page.waitForSelector('text=Thank you for buying')]); // EU: wait for agree button, non-EU: potentially done // TODO check for hcaptcha - the following is even true when no captcha is shown... // if (await iframe.frameLocator('#talon_frame_checkout_free_prod').locator('text=Please complete a security check to continue').count() > 0) { // console.error('Encountered hcaptcha. Giving up :('); From 21c13ad4d6dcb5cb21384bdd82ba220a5cc3e7fd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 21 Apr 2022 20:36:35 +0200 Subject: [PATCH 010/520] fix #14 `count` and `click` should use the same (button) locator --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 1c9e975..dd6569b 100644 --- a/epic-games.js +++ b/epic-games.js @@ -62,7 +62,7 @@ for (let i = 1; i <= n; i++) { const title = await page.locator('h1 div').first().innerText(); console.log('Current free game:', title); // click Continue if 'This game contains mature content recommended only for ages 18+' - if (await page.locator(':has-text("Continue")').count() > 0) { + if (await page.locator('button:has-text("Continue")').count() > 0) { console.log('This game contains mature content recommended only for ages 18+'); await page.click('button:has-text("Continue")'); } From fdf34ef218f3b0393a1b8260a5e93521af3dfc9a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 30 Apr 2022 21:11:27 +0200 Subject: [PATCH 011/520] prime-gaming: fix selectors --- prime-gaming.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 2f39690..59132d9 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -52,7 +52,7 @@ console.log('Signed in.'); await page.click('button:has-text("Games")'); await page.waitForSelector('div[data-a-target="offer-list-FGWP_FULL"]'); console.log('Number of already claimed games (total):', await page.locator('div[data-a-target="offer-list-FGWP_FULL"] p:has-text("Claimed")').count()); -const game_sel = 'div[data-a-target="offer-list-FGWP_FULL"] .offer__action:has-text("Claim game")'; +const game_sel = 'div[data-a-target="offer-list-FGWP_FULL"] [data-a-target="item-card"]:has-text("Claim game")'; const n = await page.locator(game_sel).count(); console.log('Number of free unclaimed games (Prime Gaming):', n); const games = await page.$$(game_sel); @@ -60,20 +60,20 @@ const games = await page.$$(game_sel); for (const card of games) { // const card = page.locator(`:nth-match(${game_sel}, ${i})`); // this will reevaluate after games are claimed and index will be wrong // const title = await card.locator('h3').first().innerText(); - const title = await (await card.$('h3')).innerText(); + const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); await (await card.$('button')).click(); // await page.pause(); } // claim games in linked stores. Origin: key, Epic Games Store: linked { - const game_sel = 'div[data-a-target="offer-list-FGWP_FULL"] .offer__action:has(p:text-is("Claim"))'; + const game_sel = 'div[data-a-target="offer-list-FGWP_FULL"] [data-a-target="item-card"]:has(p:text-is("Claim"))'; do { let n = await page.locator(game_sel).count(); console.log('Number of free unclaimed games (external stores):', n); const card = await page.$(game_sel); if (!card) break; - const title = await (await card.$('h3')).innerText(); + const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); await (await card.$('text=Claim')).click(); // await page.waitForNavigation(); From 4e90fb67c663e4572eaf2aaae00cea2ef8feb6cd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 30 Apr 2022 21:52:55 +0200 Subject: [PATCH 012/520] prime-gaming: screenshot of claimed external game, print URL to redeem --- prime-gaming.js | 34 ++++++++++++++++++++++++---------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 59132d9..911f1f0 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -50,9 +50,10 @@ while (await page.locator('button:has-text("Sign in")').count() > 0) { } console.log('Signed in.'); await page.click('button:has-text("Games")'); -await page.waitForSelector('div[data-a-target="offer-list-FGWP_FULL"]'); -console.log('Number of already claimed games (total):', await page.locator('div[data-a-target="offer-list-FGWP_FULL"] p:has-text("Claimed")').count()); -const game_sel = 'div[data-a-target="offer-list-FGWP_FULL"] [data-a-target="item-card"]:has-text("Claim game")'; +const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; +await page.waitForSelector(games_sel); +console.log('Number of already claimed games (total):', await page.locator(`${games_sel} p:has-text("Collected")`).count()); +const game_sel = `${games_sel} [data-a-target="item-card"]:has-text("Claim game")`; const n = await page.locator(game_sel).count(); console.log('Number of free unclaimed games (Prime Gaming):', n); const games = await page.$$(game_sel); @@ -62,12 +63,12 @@ for (const card of games) { // const title = await card.locator('h3').first().innerText(); const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); - await (await card.$('button')).click(); + await (await card.$('button:has-text("Claim game")')).click(); // await page.pause(); } // claim games in linked stores. Origin: key, Epic Games Store: linked { - const game_sel = 'div[data-a-target="offer-list-FGWP_FULL"] [data-a-target="item-card"]:has(p:text-is("Claim"))'; + const game_sel = `${games_sel} [data-a-target="item-card"]:has(p:text-is("Claim"))`; do { let n = await page.locator(game_sel).count(); console.log('Number of free unclaimed games (external stores):', n); @@ -77,16 +78,29 @@ for (const card of games) { console.log('Current free game:', title); await (await card.$('text=Claim')).click(); // await page.waitForNavigation(); - await page.click('button:has-text("Claim now")'); - console.log(await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText()); - // TODO only Origin shows a key, check for 'Claimed' or code - if (await page.locator('div:has-text("Origin")').count() > 0) { + await page.click('button:has-text("Claim now")'); // waits for navigation + const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); + // FULL GAME FOR PC ON: GOG.COM, ORIGIN, LEGACY GAMES, EPIC GAMES + const store = store_text.toLowerCase().replace('full game for pc on ', ''); + console.log('External store:', store); + // save screenshot of potential code just in case + const p = `screenshots/${title.replace(/[^a-z0-9]/gi, '_')}.png`; + await page.screenshot({ path: p, fullPage: true }); + console.info('Saved a screenshot of page to', p); + // print code if external store is not connected + const redeem = { + 'origin': 'https://www.origin.com/redeem', + 'gog.com': 'https://www.gog.com/redeem', + 'legacy games': 'https://www.legacygames.com/primedeal', + }; + if (store in redeem) { const code = await page.inputValue('input[type="text"]'); console.log('Code to redeem game:', code); + console.log('URL to redeem game:', redeem[store]); } // await page.pause(); await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); - n = await page.locator(game_sel).count(); + await page.click('button:has-text("Games")'); } while (n); } await context.close(); From f1dd867d398ce8e86bccfc15b3fad065abe4097f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 30 Apr 2022 21:53:03 +0200 Subject: [PATCH 013/520] missing ; --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index dd6569b..7a0ca06 100644 --- a/epic-games.js +++ b/epic-games.js @@ -99,7 +99,7 @@ for (let i = 1; i <= n; i++) { } catch (e) { console.log(e); const p = `screenshots/${new Date().toISOString()}.png`; - await page.screenshot({ path: p, fullPage: true }) + await page.screenshot({ path: p, fullPage: true }); console.info('Saved a screenshot of hcaptcha challenge to', p); console.error('Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? } From 21311834d9cfcbdf7c863e5b726df2285a03ca71 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 30 Apr 2022 22:06:57 +0200 Subject: [PATCH 014/520] npm scripts for docker, update readme --- README.md | 19 ++++++++----------- package.json | 7 +++---- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 41e0b18..fba7ec8 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,6 @@ Claims free games on This downloads Chromium (343 MB) to a cache in home ([doc](https://playwright.dev/docs/browsers#managing-browser-binaries)). ## Usage - Both scripts start an automated Chromium instance, either with the browser GUI shown or hidden (*headless mode*). Login has to be done in the browser. It's hard to automate since you usually need to enter some OTP (but you can select 'remember this device'). @@ -23,11 +22,12 @@ After login, the script will just continue, but you can also restart it. If something goes wrong, use `PWDEBUG=1 node ...` to [inspect](https://playwright.dev/docs/inspector). ### Epic Games Store -Run `node epic-games` - -Does not run headless, but can be run quasi-headless inside a Docker container (see below). - -They detect headless mode (despite stealth plugin) and it gets stuck with a captcha challenge ([issue](https://github.com/vogler/free-games-claimer/issues/2)). +Options: +- Run `node epic-games` (not headless, i.e. browser is visible, [headless leads to captcha](https://github.com/vogler/free-games-claimer/issues/2)) +- Run headless inside Docker: + - [Install Docker](https://docs.docker.com/get-docker/) + - `npm run docker:build` + - `npm run docker:epic-games` ### Amazon Prime Gaming Run `node prime-gaming` @@ -35,11 +35,8 @@ Run `node prime-gaming` Runs headless. Run `node prime-gaming show` to show the GUI (to login). Claiming the Amazon Games works, external Epic Games also work if the account is linked. -Keys for Origin (and GOG?) should be printed to the console and need to be redeemed manually at the moment ([issue](https://github.com/vogler/free-games-claimer/issues/5)). -Other stores not tested. - -### Docker -See https://github.com/vogler/free-games-claimer/pull/11 (TODO). +Keys for {Origin, GOG.com, Legacy Games} should be printed to the console and need to be redeemed manually at the URL printed to the terminal ([issue](https://github.com/vogler/free-games-claimer/issues/5)). +A screenshot of the page with the code is saved to `screenshots` as well. ### Run periodically Epic Games releases one (sometimes more) free game *every week*, but around christmas every day. diff --git a/package.json b/package.json index 1487165..7892c6a 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,8 @@ "scripts": { "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", - "test": "npx playwright test --timeout 10000", - "debug": "npx playwright test --debug", - "start": "node main.stealth" + "docker:build": "docker build --tag free-games-claimer .", + "docker:epic-games": "docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$(pwd)/userDataDir:/fgc/userDataDir\" --name free-games-claimer free-games-claimer" }, "devDependencies": { "@playwright/test": "^1.20.1", @@ -12,4 +11,4 @@ "puppeteer-extra-plugin-stealth": "^2.9.0" }, "type": "module" -} \ No newline at end of file +} From 64c9222f56762a9c7bc1226212902fa1b385a8a5 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 4 May 2022 01:30:23 +0200 Subject: [PATCH 015/520] docker volume: use npm's $INIT_CWD instead of *nix/bash $(pwd) = Win/cmd ${PWD}, #15 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7892c6a..7d12b0d 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", "docker:build": "docker build --tag free-games-claimer .", - "docker:epic-games": "docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$(pwd)/userDataDir:/fgc/userDataDir\" --name free-games-claimer free-games-claimer" + "docker:epic-games": "docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/userDataDir:/fgc/userDataDir\" --name free-games-claimer free-games-claimer" }, "devDependencies": { "@playwright/test": "^1.20.1", From a1eec65869e1bc81a5d43c4417ea5e131f5fc2a0 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 4 May 2022 02:04:04 +0200 Subject: [PATCH 016/520] Dockerfile: dos2unix ./docker/*.sh, #15 https://stackoverflow.com/questions/51508150/standard-init-linux-go190-exec-user-process-caused-no-such-file-or-directory --- Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Dockerfile b/Dockerfile index b8c1b90..a28b7cc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -47,6 +47,7 @@ RUN apt-get update \ curl \ tini \ novnc websockify \ + dos2unix \ && apt-get clean \ && rm -rf \ /tmp/* \ @@ -67,6 +68,7 @@ RUN npm install \ COPY . . # Shell scripts +RUN dos2unix ./docker/*.sh RUN mv ./docker/entrypoint.sh /usr/local/bin/entrypoint \ && chmod +x /usr/local/bin/entrypoint \ && mv ./docker/vnc-start.sh /usr/local/bin/vnc-start \ From a4ba21025c04b0be19976ae82c4c22221d57417f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 4 May 2022 09:51:29 +0200 Subject: [PATCH 017/520] docker: cross-env for vars on Windows, #15 https://stackoverflow.com/questions/58924328/generic-node-js-init-cwd-for-windows-and-nix --- package-lock.json | 140 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- 2 files changed, 142 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 6098fca..ed2fc9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "": { "devDependencies": { "@playwright/test": "^1.20.1", + "cross-env": "^7.0.3", "playwright": "^1.20.1", "puppeteer-extra-plugin-stealth": "^2.9.0" } @@ -1229,6 +1230,38 @@ "safe-buffer": "~5.1.1" } }, + "node_modules/cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "dependencies": { + "cross-spawn": "^7.0.1" + }, + "bin": { + "cross-env": "src/bin/cross-env.js", + "cross-env-shell": "src/bin/cross-env-shell.js" + }, + "engines": { + "node": ">=10.14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "4.3.3", "dev": true, @@ -1651,6 +1684,12 @@ "node": ">=8" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "node_modules/isobject": { "version": "3.0.1", "dev": true, @@ -2193,6 +2232,15 @@ "node": ">=0.10.0" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/pend": { "version": "1.2.0", "dev": true, @@ -2602,6 +2650,27 @@ "node": ">=0.10.0" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -2830,6 +2899,21 @@ "webidl-conversions": "^3.0.0" } }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/wrappy": { "version": "1.0.2", "dev": true, @@ -3728,6 +3812,26 @@ "safe-buffer": "~5.1.1" } }, + "cross-env": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", + "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.1" + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, "debug": { "version": "4.3.3", "dev": true, @@ -3997,6 +4101,12 @@ "is-docker": "^2.0.0" } }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "isobject": { "version": "3.0.1", "dev": true @@ -4343,6 +4453,12 @@ "version": "1.0.1", "dev": true }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, "pend": { "version": "1.2.0", "dev": true @@ -4618,6 +4734,21 @@ } } }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -4781,6 +4912,15 @@ "webidl-conversions": "^3.0.0" } }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, "wrappy": { "version": "1.0.2", "dev": true diff --git a/package.json b/package.json index 7d12b0d..d28c2ce 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,11 @@ "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", "docker:build": "docker build --tag free-games-claimer .", - "docker:epic-games": "docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/userDataDir:/fgc/userDataDir\" --name free-games-claimer free-games-claimer" + "docker:epic-games": "cross-env docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/userDataDir:/fgc/userDataDir\" --name free-games-claimer free-games-claimer" }, "devDependencies": { "@playwright/test": "^1.20.1", + "cross-env": "^7.0.3", "playwright": "^1.20.1", "puppeteer-extra-plugin-stealth": "^2.9.0" }, From 812e2b6530821a8108ebbdb58e2e343f54b2a1c1 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 4 May 2022 10:27:04 +0200 Subject: [PATCH 018/520] docker, #15: use cross-env-shell instead of just cross-env, rm quotes?, TODO check if paths with space are quoted --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d28c2ce..d51b341 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", "docker:build": "docker build --tag free-games-claimer .", - "docker:epic-games": "cross-env docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/userDataDir:/fgc/userDataDir\" --name free-games-claimer free-games-claimer" + "docker:epic-games": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v $INIT_CWD/userDataDir:/fgc/userDataDir --name free-games-claimer free-games-claimer" }, "devDependencies": { "@playwright/test": "^1.20.1", From 8b9af60e80d9ce33e8f34081ae817d0b12b2bfb2 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 5 May 2022 15:56:43 +0200 Subject: [PATCH 019/520] docker, #15: quote $INIT_CWD in case it has spaces --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d51b341..f513123 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", "docker:build": "docker build --tag free-games-claimer .", - "docker:epic-games": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v $INIT_CWD/userDataDir:/fgc/userDataDir --name free-games-claimer free-games-claimer" + "docker:epic-games": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/userDataDir\":/fgc/userDataDir --name free-games-claimer free-games-claimer" }, "devDependencies": { "@playwright/test": "^1.20.1", From a38e0def81871d0da2d3d6a1c7bbf979755a94d3 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 5 May 2022 16:34:20 +0200 Subject: [PATCH 020/520] docker, #15: rm chromium profile lock before run This locked the profile everytime a run was killed and made runs afterwards time out. Maybe due to changed hostname, maybe due to how the docker container kills playwright - didn't check. https://bugs.chromium.org/p/chromium/issues/detail?id=367048 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f513123..7c17d1f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", "docker:build": "docker build --tag free-games-claimer .", - "docker:epic-games": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/userDataDir\":/fgc/userDataDir --name free-games-claimer free-games-claimer" + "docker:epic-games": "rm -f userDataDir/SingletonLock && cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/userDataDir\":/fgc/userDataDir --name free-games-claimer free-games-claimer" }, "devDependencies": { "@playwright/test": "^1.20.1", From baebfd5b8c29e809a37970f49340475abad7463a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 5 May 2022 22:25:10 +0200 Subject: [PATCH 021/520] docker, #15: use rimraf since rm -f is del /f on Windows --- package-lock.json | 8 ++++++-- package.json | 5 +++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index ed2fc9d..0ec8edb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,8 @@ "@playwright/test": "^1.20.1", "cross-env": "^7.0.3", "playwright": "^1.20.1", - "puppeteer-extra-plugin-stealth": "^2.9.0" + "puppeteer-extra-plugin-stealth": "^2.9.0", + "rimraf": "^3.0.2" } }, "node_modules/@babel/code-frame": { @@ -2591,8 +2592,9 @@ }, "node_modules/rimraf": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, - "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -4696,6 +4698,8 @@ }, "rimraf": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "dev": true, "requires": { "glob": "^7.1.3" diff --git a/package.json b/package.json index 7c17d1f..b5fc415 100644 --- a/package.json +++ b/package.json @@ -3,13 +3,14 @@ "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", "docker:build": "docker build --tag free-games-claimer .", - "docker:epic-games": "rm -f userDataDir/SingletonLock && cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/userDataDir\":/fgc/userDataDir --name free-games-claimer free-games-claimer" + "docker:epic-games": "rimraf userDataDir/SingletonLock && cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/userDataDir\":/fgc/userDataDir --name free-games-claimer free-games-claimer" }, "devDependencies": { "@playwright/test": "^1.20.1", "cross-env": "^7.0.3", "playwright": "^1.20.1", - "puppeteer-extra-plugin-stealth": "^2.9.0" + "puppeteer-extra-plugin-stealth": "^2.9.0", + "rimraf": "^3.0.2" }, "type": "module" } From fd4085d57cfe8c7f79fefce77da1f3d9263e3ebd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 5 May 2022 23:15:12 +0200 Subject: [PATCH 022/520] jsconfig.module: esnext since coc-tsserver complained about await... --- jsconfig.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/jsconfig.json b/jsconfig.json index 31ebd76..38451e9 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -2,8 +2,8 @@ "compilerOptions": { "checkJs": true, "target": "es2021", - "module": "es2022", - "moduleResolution": "node", + "module": "esnext", + "moduleResolution": "node" }, "exclude": ["node_modules", "**/node_modules"] } From bba71efbc9e359537fc7f1056b155793e79d5421 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 5 May 2022 23:21:14 +0200 Subject: [PATCH 023/520] fix type in Promise.any --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 7a0ca06..f422d91 100644 --- a/epic-games.js +++ b/epic-games.js @@ -86,7 +86,7 @@ for (let i = 1; i <= n; i++) { // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 const btnAgree = iframe.locator('button:has-text("I Agree")'); try { - await Promise.any([btnAgree.waitFor().then(() => btnAgree.click()), page.waitForSelector('text=Thank you for buying')]); // EU: wait for agree button, non-EU: potentially done + await Promise.any([btnAgree.waitFor().then(() => btnAgree.click()), page.waitForSelector('text=Thank you for buying').then(_ => {})]); // EU: wait for agree button, non-EU: potentially done // TODO check for hcaptcha - the following is even true when no captcha is shown... // if (await iframe.frameLocator('#talon_frame_checkout_free_prod').locator('text=Please complete a security check to continue').count() > 0) { // console.error('Encountered hcaptcha. Giving up :('); From 59450ed05ca402025cfd25e9e2cb5fb728667eb1 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 5 May 2022 23:49:55 +0200 Subject: [PATCH 024/520] mv userDataDir data/browser; mv screenshots data/ --- .dockerignore | 5 +---- .gitignore | 5 +---- epic-games.js | 7 ++++--- package.json | 2 +- prime-gaming.js | 6 +++--- util.js | 11 +++++++++-- 6 files changed, 19 insertions(+), 17 deletions(-) diff --git a/.dockerignore b/.dockerignore index 35412b7..ffd3c43 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,9 +1,6 @@ -userDataDir** node_modules -screenshots +data .gitignore **Dockerfile** .dockerignore -.env -auth.json diff --git a/.gitignore b/.gitignore index b2ff24b..902b281 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,2 @@ node_modules/ -auth.json -.env -userDataDir/ -screenshots/ +data/ diff --git a/epic-games.js b/epic-games.js index f422d91..214ba93 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,6 +1,7 @@ import { chromium } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { __dirname, stealth } from './util.js'; +import { dirs, stealth } from './util.js'; + const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; @@ -10,7 +11,7 @@ const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) - 80 || 1280; const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; // https://playwright.dev/docs/auth#multi-factor-authentication -const context = await chromium.launchPersistentContext(path.resolve(__dirname, 'userDataDir'), { +const context = await chromium.launchPersistentContext(dirs.browser, { // chrome will not work in linux arm64, only chromium // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge headless: false, @@ -98,7 +99,7 @@ for (let i = 1; i <= n; i++) { console.log('Claimed successfully!'); } catch (e) { console.log(e); - const p = `screenshots/${new Date().toISOString()}.png`; + const p = path.resolve(dirs.screenshots, `${new Date().toISOString()}.png`); await page.screenshot({ path: p, fullPage: true }); console.info('Saved a screenshot of hcaptcha challenge to', p); console.error('Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? diff --git a/package.json b/package.json index b5fc415..bca64ee 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", "docker:build": "docker build --tag free-games-claimer .", - "docker:epic-games": "rimraf userDataDir/SingletonLock && cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/userDataDir\":/fgc/userDataDir --name free-games-claimer free-games-claimer" + "docker:epic-games": "rimraf data/browser/SingletonLock && cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/data\":/fgc/data --name free-games-claimer free-games-claimer" }, "devDependencies": { "@playwright/test": "^1.20.1", diff --git a/prime-gaming.js b/prime-gaming.js index 911f1f0..ebc6347 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,6 +1,6 @@ import { chromium } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { __dirname, stealth } from './util.js'; +import { dirs, stealth } from './util.js'; const debug = process.env.PWDEBUG == '1'; // runs headful and opens https://playwright.dev/docs/inspector const show = process.argv.includes('show', 2); @@ -11,7 +11,7 @@ const URL_CLAIM = 'https://gaming.amazon.com/home'; const TIMEOUT = 20 * 1000; // 20s, default is 30s // https://playwright.dev/docs/auth#multi-factor-authentication -const context = await chromium.launchPersistentContext(path.resolve(__dirname, 'userDataDir'), { +const context = await chromium.launchPersistentContext(dirs.browser, { // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge, chrome will not work on arm64 linux, only chromium which is the default headless, viewport: { width: 1280, height: 1280 }, @@ -84,7 +84,7 @@ for (const card of games) { const store = store_text.toLowerCase().replace('full game for pc on ', ''); console.log('External store:', store); // save screenshot of potential code just in case - const p = `screenshots/${title.replace(/[^a-z0-9]/gi, '_')}.png`; + const p = path.resolve(dirs.screenshots, `${title.replace(/[^a-z0-9]/gi, '_')}.png`); await page.screenshot({ path: p, fullPage: true }); console.info('Saved a screenshot of page to', p); // print code if external store is not connected diff --git a/util.js b/util.js index d971ba1..9c6d48e 100644 --- a/util.js +++ b/util.js @@ -1,8 +1,15 @@ // https://stackoverflow.com/questions/46745014/alternative-for-dirname-in-node-js-when-using-es6-modules import path from 'node:path'; import { fileURLToPath } from 'node:url'; -export const __filename = fileURLToPath(import.meta.url); -export const __dirname = path.dirname(__filename); +// not the same since these will give the absolute paths for this file instead of for the file using them +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +// explicit object instead of Object.fromEntries since the built-in type would loose the keys, better type: https://dev.to/svehla/typescript-object-fromentries-389c +const dataDir = s => path.resolve(__dirname, 'data', s); +export const dirs = { + browser: dataDir('browser'), + screenshots: dataDir('screenshots'), +}; // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 const newStealthContext = async (browser, contextOptions = {}, debug = false) => { From a0de165e6f86ec8a48ce2c052dca3d148bfcf079 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 6 May 2022 00:07:48 +0200 Subject: [PATCH 025/520] :has-text("Continue") -> button:has-text("Continue") --- epic-games.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index 214ba93..1e165c7 100644 --- a/epic-games.js +++ b/epic-games.js @@ -74,8 +74,8 @@ for (let i = 1; i <= n; i++) { console.log('Not in library yet! Click GET.') await page.click('[data-testid="purchase-cta-button"]'); // click Continue if 'Device not supported. This product is not compatible with your current device.' - await Promise.any([':has-text("Continue")', '#webPurchaseContainer iframe'].map(s => page.waitForSelector(s))); // wait for Continue xor iframe - if (await page.locator(':has-text("Continue")').count() > 0) { + await Promise.any(['button:has-text("Continue")', '#webPurchaseContainer iframe'].map(s => page.waitForSelector(s))); // wait for Continue xor iframe + if (await page.locator('button:has-text("Continue")').count() > 0) { // console.log('Device not supported. This product is not compatible with your current device.'); await page.click('button:has-text("Continue")'); } From 2d98a252fda7a99a509d485c8577014b1458bf48 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 6 May 2022 00:13:03 +0200 Subject: [PATCH 026/520] docker, #15: space in path: need \" instead of just " around $INIT_CWD for cross-env-shell --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bca64ee..caa41a7 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", "docker:build": "docker build --tag free-games-claimer .", - "docker:epic-games": "rimraf data/browser/SingletonLock && cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \"$INIT_CWD/data\":/fgc/data --name free-games-claimer free-games-claimer" + "docker:epic-games": "rimraf data/browser/SingletonLock && cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name free-games-claimer free-games-claimer" }, "devDependencies": { "@playwright/test": "^1.20.1", From ebde6b62083515194c6ec67920ef037e92e8649f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 6 May 2022 00:53:04 +0200 Subject: [PATCH 027/520] Update README.md --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fba7ec8..7d51702 100644 --- a/README.md +++ b/README.md @@ -22,12 +22,13 @@ After login, the script will just continue, but you can also restart it. If something goes wrong, use `PWDEBUG=1 node ...` to [inspect](https://playwright.dev/docs/inspector). ### Epic Games Store -Options: -- Run `node epic-games` (not headless, i.e. browser is visible, [headless leads to captcha](https://github.com/vogler/free-games-claimer/issues/2)) -- Run headless inside Docker: +Alternatives: +- Run `node epic-games` (browser window will open, [headless leads to captcha](https://github.com/vogler/free-games-claimer/issues/2)) +- Run with Docker (browser is hidden inside -> headless for host): - [Install Docker](https://docs.docker.com/get-docker/) - `npm run docker:build` - `npm run docker:epic-games` + - When you need to login, go to http://localhost:6080 with password `secret` (you can also connect with another VNC client) ### Amazon Prime Gaming Run `node prime-gaming` @@ -36,7 +37,7 @@ Runs headless. Run `node prime-gaming show` to show the GUI (to login). Claiming the Amazon Games works, external Epic Games also work if the account is linked. Keys for {Origin, GOG.com, Legacy Games} should be printed to the console and need to be redeemed manually at the URL printed to the terminal ([issue](https://github.com/vogler/free-games-claimer/issues/5)). -A screenshot of the page with the code is saved to `screenshots` as well. +A screenshot of the page with the code is saved to `data/screenshots` as well. ### Run periodically Epic Games releases one (sometimes more) free game *every week*, but around christmas every day. From 50bb784a33adffdb54244b97a7e39cf7d3b2d85f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 6 May 2022 00:58:38 +0200 Subject: [PATCH 028/520] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d51702..446cf47 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,8 @@ A screenshot of the page with the code is saved to `data/screenshots` as well. Epic Games releases one (sometimes more) free game *every week*, but around christmas every day. Prime Gaming has new games *every month*. -It is save to run both scripts every day. Since they are not running headless, it makes sense to run them at a time or on a machine that you are not actively using at that point. You could run them in a virtual machine, on a server, or you wake your PC at night to do it. +It is save to run both scripts every day. +If you can't use Docker for quasi-headless mode, you could run in a virtual machine, on a server, or you wake your PC at night to avoid being interrupted. - Linux/macOS: `crontab -e` - macOS: [launchd](https://stackoverflow.com/questions/132955/how-do-i-set-a-task-to-run-every-so-often) From c90bd7574b8146d4ff2046f2677e47bf85f29b28 Mon Sep 17 00:00:00 2001 From: Trung Le Date: Fri, 6 May 2022 09:38:00 +0700 Subject: [PATCH 029/520] feat: remove chrome profile lock in docker --- docker/entrypoint.sh | 2 ++ package.json | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index aebe085..a6533b0 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,5 +1,7 @@ #!/bin/sh +rm -f /fgc/data/browser/SingletonLock + # 6000+SERVERNUM is the TCP port Xvfb is listening on: # SERVERNUM=$(echo "$DISPLAY" | sed 's/:\([0-9][0-9]*\).*/\1/') diff --git a/package.json b/package.json index caa41a7..f39cfc2 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", "docker:build": "docker build --tag free-games-claimer .", - "docker:epic-games": "rimraf data/browser/SingletonLock && cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name free-games-claimer free-games-claimer" + "docker:epic-games": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name free-games-claimer free-games-claimer" }, "devDependencies": { "@playwright/test": "^1.20.1", @@ -13,4 +13,4 @@ "rimraf": "^3.0.2" }, "type": "module" -} +} \ No newline at end of file From 680452e4110c142e7177ddd1681fe49d7e042221 Mon Sep 17 00:00:00 2001 From: Trung Le Date: Fri, 6 May 2022 09:41:04 +0700 Subject: [PATCH 030/520] chore: remove unused package --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index f39cfc2..3c301fd 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,7 @@ "@playwright/test": "^1.20.1", "cross-env": "^7.0.3", "playwright": "^1.20.1", - "puppeteer-extra-plugin-stealth": "^2.9.0", - "rimraf": "^3.0.2" + "puppeteer-extra-plugin-stealth": "^2.9.0" }, "type": "module" } \ No newline at end of file From 9fbdd5258492ea2c963c23a91b228be60ab4024a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 6 May 2022 11:36:43 +0200 Subject: [PATCH 031/520] prev. commit was missing package-lock.json - rm rimraf Re 680452e4110c142e7177ddd1681fe49d7e042221 --- package-lock.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0ec8edb..96af45f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,7 @@ "@playwright/test": "^1.20.1", "cross-env": "^7.0.3", "playwright": "^1.20.1", - "puppeteer-extra-plugin-stealth": "^2.9.0", - "rimraf": "^3.0.2" + "puppeteer-extra-plugin-stealth": "^2.9.0" } }, "node_modules/@babel/code-frame": { From f26ff5dcff1c6c81d00652bf3bf17c9b28c83076 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 6 May 2022 11:50:33 +0200 Subject: [PATCH 032/520] comment: remove chromium profile lock --- docker/entrypoint.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index a6533b0..35fd467 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -1,5 +1,9 @@ #!/bin/sh +# Remove chromium profile lock. +# When running in docker and then killing it, on the next run chromium displayed a dialog to unlock the profile which made the script time out. +# Maybe due to changed hostname of container or due to how the docker container kills playwright - didn't check. +# https://bugs.chromium.org/p/chromium/issues/detail?id=367048 rm -f /fgc/data/browser/SingletonLock # 6000+SERVERNUM is the TCP port Xvfb is listening on: From cb9aed28719a10d6719e035a47d394f44ee72a94 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 11 May 2022 14:01:44 +0200 Subject: [PATCH 033/520] mention noVNC URL in login message --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 1e165c7..65a5825 100644 --- a/epic-games.js +++ b/epic-games.js @@ -43,7 +43,7 @@ await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' // with persistent context the cookie message will only show up the first time, so we can't unconditionally wait for it - try to catch it or let the user click it. await clickIfExists('button:has-text("Accept All Cookies")'); // to not waste screen space in --debug while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { // TODO also check alternative for signed-in state - console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page."); + console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page. If using docker, open http://localhost:6080"); context.setDefaultTimeout(0); // give user time to log in without timeout await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); // after login it just reloads the login page... From 8288f3dce9607413f88678474c6702f5f9582d09 Mon Sep 17 00:00:00 2001 From: XEGARE Date: Thu, 19 May 2022 21:24:44 +0500 Subject: [PATCH 034/520] Fix wait "FreeOfferCard" --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 65a5825..af79af3 100644 --- a/epic-games.js +++ b/epic-games.js @@ -53,7 +53,7 @@ while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { } console.log('Signed in.'); // click on each banner with 'Free Now'. TODO just extract the URLs and go to them in the loop -const game_sel = 'div[data-component="FreeOfferCard"]:has-text("Free Now")'; +const game_sel = 'div[data-component="OfferCard"]:has-text("Free Now")'; await page.waitForSelector(game_sel); // const games = await page.$$(game_sel); // 'Element is not attached to the DOM' after navigation; had `for (const game of games) { await game.click(); ... } const n = await page.locator(game_sel).count(); From 335c4b0292bbe2508f63b9c641005e9ca0c21fb8 Mon Sep 17 00:00:00 2001 From: Trung Le Date: Fri, 27 May 2022 10:57:18 +0700 Subject: [PATCH 035/520] fix: unable to claim collection --- epic-games.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/epic-games.js b/epic-games.js index af79af3..fc972b9 100644 --- a/epic-games.js +++ b/epic-games.js @@ -60,18 +60,18 @@ const n = await page.locator(game_sel).count(); console.log('Number of free games:', n); for (let i = 1; i <= n; i++) { await page.click(`:nth-match(${game_sel}, ${i})`); - const title = await page.locator('h1 div').first().innerText(); + const title = await page.locator('h1').first().innerText(); console.log('Current free game:', title); // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { console.log('This game contains mature content recommended only for ages 18+'); await page.click('button:has-text("Continue")'); } - const btnText = await page.locator('[data-testid="purchase-cta-button"]').innerText(); + const btnText = await page.locator('[data-testid="purchase-cta-button"]').first().innerText(); if (btnText.toLowerCase() == 'in library') { console.log('Already in library! Nothing to claim.'); } else { - console.log('Not in library yet! Click GET.') + console.log('Not in library yet! Click GET.'); await page.click('[data-testid="purchase-cta-button"]'); // click Continue if 'Device not supported. This product is not compatible with your current device.' await Promise.any(['button:has-text("Continue")', '#webPurchaseContainer iframe'].map(s => page.waitForSelector(s))); // wait for Continue xor iframe @@ -87,7 +87,7 @@ for (let i = 1; i <= n; i++) { // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 const btnAgree = iframe.locator('button:has-text("I Agree")'); try { - await Promise.any([btnAgree.waitFor().then(() => btnAgree.click()), page.waitForSelector('text=Thank you for buying').then(_ => {})]); // EU: wait for agree button, non-EU: potentially done + await Promise.any([btnAgree.waitFor().then(() => btnAgree.click()), page.waitForSelector('text=Thank you for buying').then(_ => { })]); // EU: wait for agree button, non-EU: potentially done // TODO check for hcaptcha - the following is even true when no captcha is shown... // if (await iframe.frameLocator('#talon_frame_checkout_free_prod').locator('text=Please complete a security check to continue').count() > 0) { // console.error('Encountered hcaptcha. Giving up :('); From 43c2df7e4a23b4ca151396b3ddd91326bbe50298 Mon Sep 17 00:00:00 2001 From: Trung Le Date: Fri, 27 May 2022 11:17:32 +0700 Subject: [PATCH 036/520] feat: improve Get button locator --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index fc972b9..51c5c45 100644 --- a/epic-games.js +++ b/epic-games.js @@ -67,7 +67,7 @@ for (let i = 1; i <= n; i++) { console.log('This game contains mature content recommended only for ages 18+'); await page.click('button:has-text("Continue")'); } - const btnText = await page.locator('[data-testid="purchase-cta-button"]').first().innerText(); + const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); if (btnText.toLowerCase() == 'in library') { console.log('Already in library! Nothing to claim.'); } else { From 63d88895373db64644ef65ff4582a536b9b9580d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 16 Jun 2022 16:17:31 +0200 Subject: [PATCH 037/520] clickIfExists did not work for cookie banner -> just click and catch timeout instead of await --- epic-games.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/epic-games.js b/epic-games.js index 51c5c45..2a5f9e5 100644 --- a/epic-games.js +++ b/epic-games.js @@ -33,15 +33,9 @@ if (!debug) context.setDefaultTimeout(TIMEOUT); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist console.log('userAgent:', await page.evaluate(() => navigator.userAgent)); - -const clickIfExists = async selector => { - if (await page.locator(selector).count() > 0) - await page.click(selector); -}; - await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever -// with persistent context the cookie message will only show up the first time, so we can't unconditionally wait for it - try to catch it or let the user click it. -await clickIfExists('button:has-text("Accept All Cookies")'); // to not waste screen space in --debug +// Accept cookies to get rid of banner to save space on screen. Will only appear for a fresh context, so we don't await, but let it time out if it does not exist and catch the exception. clickIfExists by checking selector's count > 0 did not work. +page.click('button:has-text("Accept All Cookies")').catch(_ => {}); // _ => console.info('Cookies already accepted') while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { // TODO also check alternative for signed-in state console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page. If using docker, open http://localhost:6080"); context.setDefaultTimeout(0); // give user time to log in without timeout From 6c190c1f602fd74e0d4c3c8a5824f03ea26dffaf Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 16 Jun 2022 16:18:45 +0200 Subject: [PATCH 038/520] epic-games changed OfferCard selector -> just click `a` with the right text --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 2a5f9e5..a6a406f 100644 --- a/epic-games.js +++ b/epic-games.js @@ -47,7 +47,7 @@ while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { } console.log('Signed in.'); // click on each banner with 'Free Now'. TODO just extract the URLs and go to them in the loop -const game_sel = 'div[data-component="OfferCard"]:has-text("Free Now")'; +const game_sel = 'a:has-text("Free Now")'; await page.waitForSelector(game_sel); // const games = await page.$$(game_sel); // 'Element is not attached to the DOM' after navigation; had `for (const game of games) { await game.click(); ... } const n = await page.locator(game_sel).count(); From 70b2b0d105a2d4a894da46bcde60d3a6242316a9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 30 Jun 2022 16:36:14 +0200 Subject: [PATCH 039/520] prime-gaming: button text changed from Games to Prime Day -> use [data-type="Game"] instead --- prime-gaming.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index ebc6347..993e310 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -49,7 +49,7 @@ while (await page.locator('button:has-text("Sign in")').count() > 0) { if (!debug) context.setDefaultTimeout(TIMEOUT); } console.log('Signed in.'); -await page.click('button:has-text("Games")'); +await page.click('button[data-type="Game"]'); const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; await page.waitForSelector(games_sel); console.log('Number of already claimed games (total):', await page.locator(`${games_sel} p:has-text("Collected")`).count()); @@ -100,7 +100,7 @@ for (const card of games) { } // await page.pause(); await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); - await page.click('button:has-text("Games")'); + await page.click('button[data-type="Game"]'); } while (n); } await context.close(); From 584c80e93937f1210e9fe922215a7f77bba054b3 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 30 Jun 2022 16:43:22 +0200 Subject: [PATCH 040/520] screenshots/{prime-gaming/{internal,external}, epic-games} --- epic-games.js | 2 +- prime-gaming.js | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index a6a406f..ac93d59 100644 --- a/epic-games.js +++ b/epic-games.js @@ -93,7 +93,7 @@ for (let i = 1; i <= n; i++) { console.log('Claimed successfully!'); } catch (e) { console.log(e); - const p = path.resolve(dirs.screenshots, `${new Date().toISOString()}.png`); + const p = path.resolve(dirs.screenshots, 'epic-games', `${new Date().toISOString()}.png`); await page.screenshot({ path: p, fullPage: true }); console.info('Saved a screenshot of hcaptcha challenge to', p); console.error('Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? diff --git a/prime-gaming.js b/prime-gaming.js index 993e310..92fc676 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -64,6 +64,10 @@ for (const card of games) { const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); await (await card.$('button:has-text("Claim game")')).click(); + // const img = await (await card.$('img.tw-image')).getAttribute('src'); + // console.log('Image:', img); + const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); + await card.screenshot({ path: p }); // await page.pause(); } // claim games in linked stores. Origin: key, Epic Games Store: linked @@ -84,7 +88,7 @@ for (const card of games) { const store = store_text.toLowerCase().replace('full game for pc on ', ''); console.log('External store:', store); // save screenshot of potential code just in case - const p = path.resolve(dirs.screenshots, `${title.replace(/[^a-z0-9]/gi, '_')}.png`); + const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); await page.screenshot({ path: p, fullPage: true }); console.info('Saved a screenshot of page to', p); // print code if external store is not connected @@ -102,5 +106,7 @@ for (const card of games) { await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); await page.click('button[data-type="Game"]'); } while (n); + const p = path.resolve(dirs.screenshots, 'prime-gaming', `${new Date().toISOString()}.png`); + await page.screenshot({ path: p, fullPage: true }); } await context.close(); From 152fe12fdd5bdfda6de8fd606c8485a66540da9a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 30 Jun 2022 22:22:34 +0200 Subject: [PATCH 041/520] prime-gaming: regex for store_text, screenshot later since code not captured --- prime-gaming.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 92fc676..b62a55e 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -85,12 +85,9 @@ for (const card of games) { await page.click('button:has-text("Claim now")'); // waits for navigation const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); // FULL GAME FOR PC ON: GOG.COM, ORIGIN, LEGACY GAMES, EPIC GAMES - const store = store_text.toLowerCase().replace('full game for pc on ', ''); + // 3 Full PC Games on Legacy Games + const store = store_text.toLowerCase().replace(/.* on /, ''); console.log('External store:', store); - // save screenshot of potential code just in case - const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); - await page.screenshot({ path: p, fullPage: true }); - console.info('Saved a screenshot of page to', p); // print code if external store is not connected const redeem = { 'origin': 'https://www.origin.com/redeem', @@ -102,6 +99,10 @@ for (const card of games) { console.log('Code to redeem game:', code); console.log('URL to redeem game:', redeem[store]); } + // save screenshot of potential code just in case + const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); + await page.screenshot({ path: p, fullPage: true }); + console.info('Saved a screenshot of page to', p); // await page.pause(); await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); await page.click('button[data-type="Game"]'); From 80e2a693d76d37a7e0768376e3c0741cfbba1238 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 30 Jun 2022 22:40:04 +0200 Subject: [PATCH 042/520] prime-gaming: get custom redeem URL for legacy games --- prime-gaming.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prime-gaming.js b/prime-gaming.js index b62a55e..91caa17 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -97,6 +97,9 @@ for (const card of games) { if (store in redeem) { const code = await page.inputValue('input[type="text"]'); console.log('Code to redeem game:', code); + if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ + redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); + } console.log('URL to redeem game:', redeem[store]); } // save screenshot of potential code just in case From 18de5fdfa83cd464c01a272b0479f019ac329809 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 13 Jul 2022 15:49:03 +0200 Subject: [PATCH 043/520] use lowdb for data/prime-gaming.json --- package-lock.json | 43 +++++++++++++++++++++++++ package.json | 3 +- prime-gaming.js | 81 +++++++++++++++++++++++++++++++++-------------- util.js | 10 ++++++ 4 files changed, 112 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 96af45f..4fda962 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "devDependencies": { "@playwright/test": "^1.20.1", "cross-env": "^7.0.3", + "lowdb": "^3.0.0", "playwright": "^1.20.1", "puppeteer-extra-plugin-stealth": "^2.9.0" } @@ -2031,6 +2032,21 @@ "node": ">=8" } }, + "node_modules/lowdb": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-3.0.0.tgz", + "integrity": "sha512-9KZRulmIcU8fZuWiaM0d5e2/nPnrFyXkeXVpqT+MJS+vgbgOf1EbtvgQmba8HwUFgDl1oeZR6XqEJnkJmQdKmg==", + "dev": true, + "dependencies": { + "steno": "^2.1.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/merge-deep": { "version": "3.0.3", "dev": true, @@ -2759,6 +2775,18 @@ "node": ">=8" } }, + "node_modules/steno": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/steno/-/steno-2.1.0.tgz", + "integrity": "sha512-mauOsiaqTNGFkWqIfwcm3y/fq+qKKaIWf1vf3ocOuTdco9XoHCO2AGF1gFYXuZFSWuP38Q8LBHBGJv2KnJSXyA==", + "dev": true, + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/typicode" + } + }, "node_modules/string_decoder": { "version": "1.3.0", "dev": true, @@ -4323,6 +4351,15 @@ "p-locate": "^4.1.0" } }, + "lowdb": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-3.0.0.tgz", + "integrity": "sha512-9KZRulmIcU8fZuWiaM0d5e2/nPnrFyXkeXVpqT+MJS+vgbgOf1EbtvgQmba8HwUFgDl1oeZR6XqEJnkJmQdKmg==", + "dev": true, + "requires": { + "steno": "^2.1.0" + } + }, "merge-deep": { "version": "3.0.3", "dev": true, @@ -4813,6 +4850,12 @@ } } }, + "steno": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/steno/-/steno-2.1.0.tgz", + "integrity": "sha512-mauOsiaqTNGFkWqIfwcm3y/fq+qKKaIWf1vf3ocOuTdco9XoHCO2AGF1gFYXuZFSWuP38Q8LBHBGJv2KnJSXyA==", + "dev": true + }, "string_decoder": { "version": "1.3.0", "dev": true, diff --git a/package.json b/package.json index 3c301fd..8d24498 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,9 @@ "devDependencies": { "@playwright/test": "^1.20.1", "cross-env": "^7.0.3", + "lowdb": "^3.0.0", "playwright": "^1.20.1", "puppeteer-extra-plugin-stealth": "^2.9.0" }, "type": "module" -} \ No newline at end of file +} diff --git a/prime-gaming.js b/prime-gaming.js index 91caa17..ff114f2 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,6 +1,6 @@ import { chromium } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { dirs, stealth } from './util.js'; +import { dirs, jsonDb, datetime, stealth } from './util.js'; const debug = process.env.PWDEBUG == '1'; // runs headful and opens https://playwright.dev/docs/inspector const show = process.argv.includes('show', 2); @@ -10,6 +10,17 @@ const headless = !debug && !show; const URL_CLAIM = 'https://gaming.amazon.com/home'; const TIMEOUT = 20 * 1000; // 20s, default is 30s +const db = await jsonDb('prime-gaming.json'); +db.data ||= { claimed: [], runs: [] }; +const run = { + startTime: datetime(), + endTime: null, + n_internal: null, // unclaimed games at beginning + c_internal: 0, // claimed games at end + n_external: null, + c_external: 0, +}; + // https://playwright.dev/docs/auth#multi-factor-authentication const context = await chromium.launchPersistentContext(dirs.browser, { // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge, chrome will not work on arm64 linux, only chromium which is the default @@ -25,13 +36,14 @@ if (!debug) context.setDefaultTimeout(TIMEOUT); // const page = /* context.pages().length ? context.pages[0] : */ await context.newPage(); const page = context.pages()[0]; -console.log('userAgent:', await page.evaluate(() => navigator.userAgent)); +console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); const clickIfExists = async selector => { if (await page.locator(selector).count() > 0) await page.click(selector); }; +try { await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); // default 'load' takes forever // need to wait for some elements to exist before checking if signed in or accepting cookies: await Promise.any(['button:has-text("Sign in")', '[data-a-target="user-dropdown-first-name-text"]'].map(s => page.waitForSelector(s))); @@ -54,8 +66,8 @@ const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; await page.waitForSelector(games_sel); console.log('Number of already claimed games (total):', await page.locator(`${games_sel} p:has-text("Collected")`).count()); const game_sel = `${games_sel} [data-a-target="item-card"]:has-text("Claim game")`; -const n = await page.locator(game_sel).count(); -console.log('Number of free unclaimed games (Prime Gaming):', n); +run.n_internal = await page.locator(game_sel).count(); +console.log('Number of free unclaimed games (Prime Gaming):', run.n_internal); const games = await page.$$(game_sel); // for (let i=1; i<=n; i++) { for (const card of games) { @@ -64,6 +76,8 @@ for (const card of games) { const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); await (await card.$('button:has-text("Claim game")')).click(); + db.data.claimed.push({title, time: datetime(), store: 'internal'}); + run.c_internal++; // const img = await (await card.$('img.tw-image')).getAttribute('src'); // console.log('Image:', img); const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); @@ -72,9 +86,11 @@ for (const card of games) { } // claim games in linked stores. Origin: key, Epic Games Store: linked { + let n; const game_sel = `${games_sel} [data-a-target="item-card"]:has(p:text-is("Claim"))`; do { - let n = await page.locator(game_sel).count(); + n = await page.locator(game_sel).count(); + run.n_external ||= n; console.log('Number of free unclaimed games (external stores):', n); const card = await page.$(game_sel); if (!card) break; @@ -82,35 +98,52 @@ for (const card of games) { console.log('Current free game:', title); await (await card.$('text=Claim')).click(); // await page.waitForNavigation(); - await page.click('button:has-text("Claim now")'); // waits for navigation + await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")')]); // waits for navigation const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); // FULL GAME FOR PC ON: GOG.COM, ORIGIN, LEGACY GAMES, EPIC GAMES // 3 Full PC Games on Legacy Games const store = store_text.toLowerCase().replace(/.* on /, ''); console.log('External store:', store); - // print code if external store is not connected - const redeem = { - 'origin': 'https://www.origin.com/redeem', - 'gog.com': 'https://www.gog.com/redeem', - 'legacy games': 'https://www.legacygames.com/primedeal', - }; - if (store in redeem) { - const code = await page.inputValue('input[type="text"]'); - console.log('Code to redeem game:', code); - if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ - redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); + if(await page.locator('div:has-text("Link game account")').count()) { + console.error('Account linking is required to claim this offer!'); + } else { + // print code if there is one + const redeem = { + // 'origin': 'https://www.origin.com/redeem', // TODO still needed or now only via account linking? + 'gog.com': 'https://www.gog.com/redeem', + 'legacy games': 'https://www.legacygames.com/primedeal', + }; + let code; + if (store in redeem) { // did not work for linked origin: && !await page.locator('div:has-text("Successfully Claimed")').count() + code = await page.inputValue('input[type="text"]'); + console.log('Code to redeem game:', code); + if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ + redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); + } + console.log('URL to redeem game:', redeem[store]); } - console.log('URL to redeem game:', redeem[store]); + db.data.claimed.push({title, time: datetime(), store, code}); + // save screenshot of potential code just in case + const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); + await page.screenshot({ path: p, fullPage: true }); + console.info('Saved a screenshot of page to', p); + run.c_external++; } - // save screenshot of potential code just in case - const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); - await page.screenshot({ path: p, fullPage: true }); - console.info('Saved a screenshot of page to', p); // await page.pause(); await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); await page.click('button[data-type="Game"]'); } while (n); - const p = path.resolve(dirs.screenshots, 'prime-gaming', `${new Date().toISOString()}.png`); + const p = path.resolve(dirs.screenshots, 'prime-gaming', `${datetime()}.png`); await page.screenshot({ path: p, fullPage: true }); } -await context.close(); +} catch(error) { + console.error(error); + run.error = error.toString(); +} finally { + // write out json db + run.endTime = datetime(); + db.data.runs.push(run); + await db.write(); // TODO try-finally to always write out any updates + + await context.close(); +} diff --git a/util.js b/util.js index 9c6d48e..2db70d2 100644 --- a/util.js +++ b/util.js @@ -7,10 +7,20 @@ const __dirname = path.dirname(__filename); // explicit object instead of Object.fromEntries since the built-in type would loose the keys, better type: https://dev.to/svehla/typescript-object-fromentries-389c const dataDir = s => path.resolve(__dirname, 'data', s); export const dirs = { + data: dataDir('.'), browser: dataDir('browser'), screenshots: dataDir('screenshots'), }; +import { Low, JSONFile } from 'lowdb'; +export const jsonDb = async file => { + const db = new Low(new JSONFile(dataDir(file))); + await db.read(); + return db; +} + +export const datetime = (d = new Date()) => d.toISOString(); + // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 const newStealthContext = async (browser, contextOptions = {}, debug = false) => { if (!debug) { // only need to fix userAgent in headless mode From 02a49a5a19dc0cef6c5edd07724200a990d559f6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 13 Jul 2022 15:57:04 +0200 Subject: [PATCH 044/520] indent prime-gaming --- prime-gaming.js | 88 ++++++++++++++++++++++++------------------------- 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index ff114f2..6bd672c 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -44,55 +44,54 @@ const clickIfExists = async selector => { }; try { -await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); // default 'load' takes forever -// need to wait for some elements to exist before checking if signed in or accepting cookies: -await Promise.any(['button:has-text("Sign in")', '[data-a-target="user-dropdown-first-name-text"]'].map(s => page.waitForSelector(s))); -await clickIfExists('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")'); // to not waste screen space in --debug -while (await page.locator('button:has-text("Sign in")').count() > 0) { - console.error('Not signed in anymore.'); - if (headless) { - console.log('Please run `node prime-gaming show` to login in the opened browser.'); - await context.close(); // not needed? - process.exit(1); + await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); // default 'load' takes forever + // need to wait for some elements to exist before checking if signed in or accepting cookies: + await Promise.any(['button:has-text("Sign in")', '[data-a-target="user-dropdown-first-name-text"]'].map(s => page.waitForSelector(s))); + await clickIfExists('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")'); // to not waste screen space in --debug + while (await page.locator('button:has-text("Sign in")').count() > 0) { + console.error('Not signed in anymore.'); + if (headless) { + console.log('Please run `node prime-gaming show` to login in the opened browser.'); + await context.close(); // not needed? + process.exit(1); + } + await page.click('button:has-text("Sign in")'); + if (!debug) context.setDefaultTimeout(0); // give user time to log in without timeout + await page.waitForNavigation({url: 'https://gaming.amazon.com/home?signedIn=true'}); + if (!debug) context.setDefaultTimeout(TIMEOUT); } - await page.click('button:has-text("Sign in")'); - if (!debug) context.setDefaultTimeout(0); // give user time to log in without timeout - await page.waitForNavigation({url: 'https://gaming.amazon.com/home?signedIn=true'}); - if (!debug) context.setDefaultTimeout(TIMEOUT); -} -console.log('Signed in.'); -await page.click('button[data-type="Game"]'); -const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; -await page.waitForSelector(games_sel); -console.log('Number of already claimed games (total):', await page.locator(`${games_sel} p:has-text("Collected")`).count()); -const game_sel = `${games_sel} [data-a-target="item-card"]:has-text("Claim game")`; -run.n_internal = await page.locator(game_sel).count(); -console.log('Number of free unclaimed games (Prime Gaming):', run.n_internal); -const games = await page.$$(game_sel); -// for (let i=1; i<=n; i++) { -for (const card of games) { - // const card = page.locator(`:nth-match(${game_sel}, ${i})`); // this will reevaluate after games are claimed and index will be wrong - // const title = await card.locator('h3').first().innerText(); - const title = await (await card.$('.item-card-details__body__primary')).innerText(); - console.log('Current free game:', title); - await (await card.$('button:has-text("Claim game")')).click(); - db.data.claimed.push({title, time: datetime(), store: 'internal'}); - run.c_internal++; - // const img = await (await card.$('img.tw-image')).getAttribute('src'); - // console.log('Image:', img); - const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); - await card.screenshot({ path: p }); - // await page.pause(); -} -// claim games in linked stores. Origin: key, Epic Games Store: linked -{ + console.log('Signed in.'); + await page.click('button[data-type="Game"]'); + const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; + await page.waitForSelector(games_sel); + console.log('Number of already claimed games (total):', await page.locator(`${games_sel} p:has-text("Collected")`).count()); + const game_sel = `${games_sel} [data-a-target="item-card"]:has-text("Claim game")`; + run.n_internal = await page.locator(game_sel).count(); + console.log('Number of free unclaimed games (Prime Gaming):', run.n_internal); + const games = await page.$$(game_sel); + // for (let i=1; i<=n; i++) { + for (const card of games) { + // const card = page.locator(`:nth-match(${game_sel}, ${i})`); // this will reevaluate after games are claimed and index will be wrong + // const title = await card.locator('h3').first().innerText(); + const title = await (await card.$('.item-card-details__body__primary')).innerText(); + console.log('Current free game:', title); + await (await card.$('button:has-text("Claim game")')).click(); + db.data.claimed.push({title, time: datetime(), store: 'internal'}); + run.c_internal++; + // const img = await (await card.$('img.tw-image')).getAttribute('src'); + // console.log('Image:', img); + const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); + await card.screenshot({ path: p }); + // await page.pause(); + } + // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com let n; - const game_sel = `${games_sel} [data-a-target="item-card"]:has(p:text-is("Claim"))`; + const game_sel_ext = `${games_sel} [data-a-target="item-card"]:has(p:text-is("Claim"))`; do { - n = await page.locator(game_sel).count(); + n = await page.locator(game_sel_ext).count(); run.n_external ||= n; console.log('Number of free unclaimed games (external stores):', n); - const card = await page.$(game_sel); + const card = await page.$(game_sel_ext); if (!card) break; const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); @@ -135,7 +134,6 @@ for (const card of games) { } while (n); const p = path.resolve(dirs.screenshots, 'prime-gaming', `${datetime()}.png`); await page.screenshot({ path: p, fullPage: true }); -} } catch(error) { console.error(error); run.error = error.toString(); From 87df6d0e261e7480c933c9a9d2af20b35e1bef9c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 14 Jul 2022 16:48:05 +0200 Subject: [PATCH 045/520] prime-gaming: remove try-finally TODO --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 6bd672c..e89936d 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -141,7 +141,7 @@ try { // write out json db run.endTime = datetime(); db.data.runs.push(run); - await db.write(); // TODO try-finally to always write out any updates + await db.write(); await context.close(); } From 0d847c479a79d000fa0583d16f30329b4ba8c134 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 14 Jul 2022 16:48:38 +0200 Subject: [PATCH 046/520] use lowdb for data/epic-games.json --- epic-games.js | 161 ++++++++++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 69 deletions(-) diff --git a/epic-games.js b/epic-games.js index ac93d59..daa0699 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,6 +1,6 @@ import { chromium } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { dirs, stealth } from './util.js'; +import { dirs, jsonDb, datetime, stealth } from './util.js'; const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector @@ -10,6 +10,15 @@ const TIMEOUT = 20 * 1000; // 20s, default is 30s const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) - 80 || 1280; const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; +const db = await jsonDb('epic-games.json'); +db.data ||= { claimed: [], runs: [] }; +const run = { + startTime: datetime(), + endTime: null, + n: null, // unclaimed games at beginning + c: 0, // claimed games at end +}; + // https://playwright.dev/docs/auth#multi-factor-authentication const context = await chromium.launchPersistentContext(dirs.browser, { // chrome will not work in linux arm64, only chromium @@ -32,78 +41,92 @@ await stealth(context); if (!debug) context.setDefaultTimeout(TIMEOUT); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist -console.log('userAgent:', await page.evaluate(() => navigator.userAgent)); -await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever -// Accept cookies to get rid of banner to save space on screen. Will only appear for a fresh context, so we don't await, but let it time out if it does not exist and catch the exception. clickIfExists by checking selector's count > 0 did not work. -page.click('button:has-text("Accept All Cookies")').catch(_ => {}); // _ => console.info('Cookies already accepted') -while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { // TODO also check alternative for signed-in state - console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page. If using docker, open http://localhost:6080"); - context.setDefaultTimeout(0); // give user time to log in without timeout - await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); - // after login it just reloads the login page... - await page.waitForNavigation({ url: URL_CLAIM }); - context.setDefaultTimeout(TIMEOUT); - // process.exit(1); -} -console.log('Signed in.'); -// click on each banner with 'Free Now'. TODO just extract the URLs and go to them in the loop -const game_sel = 'a:has-text("Free Now")'; -await page.waitForSelector(game_sel); -// const games = await page.$$(game_sel); // 'Element is not attached to the DOM' after navigation; had `for (const game of games) { await game.click(); ... } -const n = await page.locator(game_sel).count(); -console.log('Number of free games:', n); -for (let i = 1; i <= n; i++) { - await page.click(`:nth-match(${game_sel}, ${i})`); - const title = await page.locator('h1').first().innerText(); - console.log('Current free game:', title); - // click Continue if 'This game contains mature content recommended only for ages 18+' - if (await page.locator('button:has-text("Continue")').count() > 0) { - console.log('This game contains mature content recommended only for ages 18+'); - await page.click('button:has-text("Continue")'); +console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); + +try { + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever + // Accept cookies to get rid of banner to save space on screen. Will only appear for a fresh context, so we don't await, but let it time out if it does not exist and catch the exception. clickIfExists by checking selector's count > 0 did not work. + page.click('button:has-text("Accept All Cookies")').catch(_ => {}); // _ => console.info('Cookies already accepted') + while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { // TODO also check alternative for signed-in state + console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page. If using docker, open http://localhost:6080"); + context.setDefaultTimeout(0); // give user time to log in without timeout + await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); + // after login it just reloads the login page... + await page.waitForNavigation({ url: URL_CLAIM }); + context.setDefaultTimeout(TIMEOUT); + // process.exit(1); } - const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); - if (btnText.toLowerCase() == 'in library') { - console.log('Already in library! Nothing to claim.'); - } else { - console.log('Not in library yet! Click GET.'); - await page.click('[data-testid="purchase-cta-button"]'); - // click Continue if 'Device not supported. This product is not compatible with your current device.' - await Promise.any(['button:has-text("Continue")', '#webPurchaseContainer iframe'].map(s => page.waitForSelector(s))); // wait for Continue xor iframe + console.log('Signed in.'); + // click on each banner with 'Free Now'. TODO just extract the URLs and go to them in the loop + const game_sel = 'a:has-text("Free Now")'; + await page.waitForSelector(game_sel); + // const games = await page.$$(game_sel); // 'Element is not attached to the DOM' after navigation; had `for (const game of games) { await game.click(); ... } + const n = run.n = await page.locator(game_sel).count(); + console.log('Number of free games:', n); + for (let i = 1; i <= n; i++) { + await page.click(`:nth-match(${game_sel}, ${i})`); + const title = await page.locator('h1').first().innerText(); + console.log('Current free game:', title); + // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { - // console.log('Device not supported. This product is not compatible with your current device.'); + console.log('This game contains mature content recommended only for ages 18+'); await page.click('button:has-text("Continue")'); } - // it then creates an iframe for the rest - // await page.frame({ url: /.*store\/purchase.*/ }).click('button:has-text("Place Order")'); // not found because it does not wait for iframe - const iframe = page.frameLocator('#webPurchaseContainer iframe') - await iframe.locator('button:has-text("Place Order")').click(); - // await page.pause(); - // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 - const btnAgree = iframe.locator('button:has-text("I Agree")'); - try { - await Promise.any([btnAgree.waitFor().then(() => btnAgree.click()), page.waitForSelector('text=Thank you for buying').then(_ => { })]); // EU: wait for agree button, non-EU: potentially done - // TODO check for hcaptcha - the following is even true when no captcha is shown... - // if (await iframe.frameLocator('#talon_frame_checkout_free_prod').locator('text=Please complete a security check to continue').count() > 0) { - // console.error('Encountered hcaptcha. Giving up :('); - // await page.pause(); - // process.exit(1); - // } - // await page.waitForTimeout(3000); - await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again - console.log('Claimed successfully!'); - } catch (e) { - console.log(e); - const p = path.resolve(dirs.screenshots, 'epic-games', `${new Date().toISOString()}.png`); - await page.screenshot({ path: p, fullPage: true }); - console.info('Saved a screenshot of hcaptcha challenge to', p); - console.error('Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? + const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); + if (btnText.toLowerCase() == 'in library') { + console.log('Already in library! Nothing to claim.'); + } else { + console.log('Not in library yet! Click GET.'); + await page.click('[data-testid="purchase-cta-button"]'); + // click Continue if 'Device not supported. This product is not compatible with your current device.' + await Promise.any(['button:has-text("Continue")', '#webPurchaseContainer iframe'].map(s => page.waitForSelector(s))); // wait for Continue xor iframe + if (await page.locator('button:has-text("Continue")').count() > 0) { + // console.log('Device not supported. This product is not compatible with your current device.'); + await page.click('button:has-text("Continue")'); + } + // it then creates an iframe for the rest + // await page.frame({ url: /.*store\/purchase.*/ }).click('button:has-text("Place Order")'); // not found because it does not wait for iframe + const iframe = page.frameLocator('#webPurchaseContainer iframe') + await iframe.locator('button:has-text("Place Order")').click(); + // await page.pause(); + // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 + const btnAgree = iframe.locator('button:has-text("I Agree")'); + try { + await Promise.any([btnAgree.waitFor().then(() => btnAgree.click()), page.waitForSelector('text=Thank you for buying').then(_ => { })]); // EU: wait for agree button, non-EU: potentially done + // TODO check for hcaptcha - the following is even true when no captcha is shown... + // if (await iframe.frameLocator('#talon_frame_checkout_free_prod').locator('text=Please complete a security check to continue').count() > 0) { + // console.error('Encountered hcaptcha. Giving up :('); + // await page.pause(); + // process.exit(1); + // } + // await page.waitForTimeout(3000); + await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again + db.data.claimed.push({title, time: datetime()}); + run.c++; + console.log('Claimed successfully!'); + } catch (e) { + console.log(e); + const p = path.resolve(dirs.screenshots, 'epic-games', `${datetime()}.png`); + await page.screenshot({ path: p, fullPage: true }); + console.info('Saved a screenshot of hcaptcha challenge to', p); + console.error('Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? + } + // await page.pause(); + } + if (i < n) { // no need to go back if it's the last game + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); + await page.waitForSelector(game_sel); } - // await page.pause(); - } - if (i < n) { // no need to go back if it's the last game - await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); - await page.waitForSelector(game_sel); } +} catch(error) { + console.error(error); + run.error = error.toString(); +} finally { + // write out json db + run.endTime = datetime(); + db.data.runs.push(run); + await db.write(); + + // await context.waitForEvent("close"); + await context.close(); } -// await context.waitForEvent("close"); -await context.close(); From 2ae513f6c0cfc95e038e2a7ee3625b0f4e4710c8 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 14 Jul 2022 16:49:03 +0200 Subject: [PATCH 047/520] epic-games: save data/screenshots/epic-games/title.png for each game, not fullPage --- epic-games.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/epic-games.js b/epic-games.js index daa0699..ff22708 100644 --- a/epic-games.js +++ b/epic-games.js @@ -73,6 +73,8 @@ try { await page.click('button:has-text("Continue")'); } const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); + const p = path.resolve(dirs.screenshots, 'epic-games', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); + await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... if (btnText.toLowerCase() == 'in library') { console.log('Already in library! Nothing to claim.'); } else { From f88898141520a7f3341654fee24744ea612bb3fa Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 14 Jul 2022 17:04:00 +0200 Subject: [PATCH 048/520] extract sanitizeFilename --- prime-gaming.js | 6 +++--- util.js | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index e89936d..79e95cf 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,6 +1,6 @@ import { chromium } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { dirs, jsonDb, datetime, stealth } from './util.js'; +import { dirs, jsonDb, datetime, sanitizeFilename, stealth } from './util.js'; const debug = process.env.PWDEBUG == '1'; // runs headful and opens https://playwright.dev/docs/inspector const show = process.argv.includes('show', 2); @@ -80,7 +80,7 @@ try { run.c_internal++; // const img = await (await card.$('img.tw-image')).getAttribute('src'); // console.log('Image:', img); - const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); + const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${sanitizeFilename(title)}.png`); await card.screenshot({ path: p }); // await page.pause(); } @@ -123,7 +123,7 @@ try { } db.data.claimed.push({title, time: datetime(), store, code}); // save screenshot of potential code just in case - const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); + const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${sanitizeFilename(title)}.png`); await page.screenshot({ path: p, fullPage: true }); console.info('Saved a screenshot of page to', p); run.c_external++; diff --git a/util.js b/util.js index 2db70d2..62fc42e 100644 --- a/util.js +++ b/util.js @@ -20,6 +20,7 @@ export const jsonDb = async file => { } export const datetime = (d = new Date()) => d.toISOString(); +export const sanitizeFilename = s => s.replace(/[^a-z0-9_\-]/gi, '_'); // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 const newStealthContext = async (browser, contextOptions = {}, debug = false) => { From 4868ba66f9c4697a79b3f418369df5ceb396cc8a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 14 Jul 2022 17:09:41 +0200 Subject: [PATCH 049/520] epic-games: title_url as filename, GET-button as loading barrier --- epic-games.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/epic-games.js b/epic-games.js index ff22708..4470e71 100644 --- a/epic-games.js +++ b/epic-games.js @@ -64,20 +64,21 @@ try { const n = run.n = await page.locator(game_sel).count(); console.log('Number of free games:', n); for (let i = 1; i <= n; i++) { - await page.click(`:nth-match(${game_sel}, ${i})`); - const title = await page.locator('h1').first().innerText(); - console.log('Current free game:', title); + await page.click(`:nth-match(${game_sel}, ${i})`); // navigates to page for game + const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); // barrier to block until page is loaded // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { console.log('This game contains mature content recommended only for ages 18+'); await page.click('button:has-text("Continue")'); } - const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); - const p = path.resolve(dirs.screenshots, 'epic-games', `${title.replace(/[^a-z0-9]/gi, '_')}.png`); + const title = await page.locator('h1').first().innerText(); + console.log('Current free game:', title); + const title_url = page.url().split('/').pop(); + const p = path.resolve(dirs.screenshots, 'epic-games', `${title_url}.png`); await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... if (btnText.toLowerCase() == 'in library') { console.log('Already in library! Nothing to claim.'); - } else { + } else { // GET console.log('Not in library yet! Click GET.'); await page.click('[data-testid="purchase-cta-button"]'); // click Continue if 'Device not supported. This product is not compatible with your current device.' From f770ef55800e5a4e7017a5aadef854ed11365540 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 14 Jul 2022 17:12:15 +0200 Subject: [PATCH 050/520] include URL to game in .json --- epic-games.js | 2 +- prime-gaming.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index 4470e71..4ef1b47 100644 --- a/epic-games.js +++ b/epic-games.js @@ -104,7 +104,7 @@ try { // } // await page.waitForTimeout(3000); await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again - db.data.claimed.push({title, time: datetime()}); + db.data.claimed.push({ title, time: datetime(), url: page.url() }); run.c++; console.log('Claimed successfully!'); } catch (e) { diff --git a/prime-gaming.js b/prime-gaming.js index 79e95cf..0bb4196 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -76,7 +76,7 @@ try { const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); await (await card.$('button:has-text("Claim game")')).click(); - db.data.claimed.push({title, time: datetime(), store: 'internal'}); + db.data.claimed.push({ title, time: datetime(), store: 'internal' }); run.c_internal++; // const img = await (await card.$('img.tw-image')).getAttribute('src'); // console.log('Image:', img); @@ -121,7 +121,7 @@ try { } console.log('URL to redeem game:', redeem[store]); } - db.data.claimed.push({title, time: datetime(), store, code}); + db.data.claimed.push({ title, time: datetime(), store, code, url: page.url() }); // save screenshot of potential code just in case const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${sanitizeFilename(title)}.png`); await page.screenshot({ path: p, fullPage: true }); From cf8746dc6c26e481d04411d1a42dbe1a3feb7a5f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 28 Jul 2022 16:54:02 +0200 Subject: [PATCH 051/520] epic-games: fix title sometimes being duplicated due to responsive alternative --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 4ef1b47..27a6dd5 100644 --- a/epic-games.js +++ b/epic-games.js @@ -71,7 +71,7 @@ try { console.log('This game contains mature content recommended only for ages 18+'); await page.click('button:has-text("Continue")'); } - const title = await page.locator('h1').first().innerText(); + const title = await page.locator('h1 div').first().innerText(); console.log('Current free game:', title); const title_url = page.url().split('/').pop(); const p = path.resolve(dirs.screenshots, 'epic-games', `${title_url}.png`); From 4ff208a6b0047f4cfebbf77250383ebc31b01644 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 3 Aug 2022 00:43:54 +0200 Subject: [PATCH 052/520] prime-gaming: screenshots: internal before claim, end-of-run just games instead of full page --- prime-gaming.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 0bb4196..4999449 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -75,13 +75,13 @@ try { // const title = await card.locator('h3').first().innerText(); const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); - await (await card.$('button:has-text("Claim game")')).click(); - db.data.claimed.push({ title, time: datetime(), store: 'internal' }); - run.c_internal++; // const img = await (await card.$('img.tw-image')).getAttribute('src'); // console.log('Image:', img); const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${sanitizeFilename(title)}.png`); await card.screenshot({ path: p }); + await (await card.$('button:has-text("Claim game")')).click(); + db.data.claimed.push({ title, time: datetime(), store: 'internal' }); + run.c_internal++; // await page.pause(); } // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com @@ -99,7 +99,7 @@ try { // await page.waitForNavigation(); await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")')]); // waits for navigation const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); - // FULL GAME FOR PC ON: GOG.COM, ORIGIN, LEGACY GAMES, EPIC GAMES + // Full game for PC [and MAC] on: gog.com, Origin, Legacy Games, EPIC GAMES, Battle.net // 3 Full PC Games on Legacy Games const store = store_text.toLowerCase().replace(/.* on /, ''); console.log('External store:', store); @@ -133,7 +133,8 @@ try { await page.click('button[data-type="Game"]'); } while (n); const p = path.resolve(dirs.screenshots, 'prime-gaming', `${datetime()}.png`); - await page.screenshot({ path: p, fullPage: true }); + // await page.screenshot({ path: p, fullPage: true }); + await page.locator(games_sel).screenshot({ path: p }); } catch(error) { console.error(error); run.error = error.toString(); From 90af31a2106046c06b409cbe65669e9f49d0dfc3 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 18 Aug 2022 16:57:25 +0200 Subject: [PATCH 053/520] epic-games: don't click on 'play free now' button, but only 'free now' --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 27a6dd5..07ed9c5 100644 --- a/epic-games.js +++ b/epic-games.js @@ -58,7 +58,7 @@ try { } console.log('Signed in.'); // click on each banner with 'Free Now'. TODO just extract the URLs and go to them in the loop - const game_sel = 'a:has-text("Free Now")'; + const game_sel = 'span:text-is("Free Now")'; await page.waitForSelector(game_sel); // const games = await page.$$(game_sel); // 'Element is not attached to the DOM' after navigation; had `for (const game of games) { await game.click(); ... } const n = run.n = await page.locator(game_sel).count(); From a468ed5fed2e0d164a9086dacaa0c0fcc97d9585 Mon Sep 17 00:00:00 2001 From: Kilian von Pflugk Date: Sun, 28 Aug 2022 13:53:15 +0200 Subject: [PATCH 054/520] fix restart issue --- Dockerfile | 12 ++++-------- docker/entrypoint.sh | 10 +++++----- docker/vnc-start.sh | 11 ----------- 3 files changed, 9 insertions(+), 24 deletions(-) delete mode 100755 docker/vnc-start.sh diff --git a/Dockerfile b/Dockerfile index a28b7cc..b3b686f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,13 +7,12 @@ ARG DEBIAN_FRONTEND=noninteractive ENV SCREEN_WIDTH 1440 ENV SCREEN_HEIGHT 900 ENV SCREEN_DEPTH 24 -ENV DISPLAY :60 # Configure VNC via environment variables: -ENV VNC_ENABLED true ENV VNC_PASSWORD secret ENV VNC_PORT 5900 ENV NOVNC_PORT 6080 +ENV NOVNC_HOME /usr/share/novnc EXPOSE 5900 EXPOSE 6080 @@ -54,8 +53,8 @@ RUN apt-get update \ /usr/share/doc/* \ /var/cache/* \ /var/lib/apt/lists/* \ - /var/tmp/* - + /var/tmp/* \ + && ln -s $NOVNC_HOME/vnc_auto.html $NOVNC_HOME/index.html WORKDIR /fgc COPY package.json . @@ -70,10 +69,7 @@ COPY . . # Shell scripts RUN dos2unix ./docker/*.sh RUN mv ./docker/entrypoint.sh /usr/local/bin/entrypoint \ - && chmod +x /usr/local/bin/entrypoint \ - && mv ./docker/vnc-start.sh /usr/local/bin/vnc-start \ - && chmod +x /usr/local/bin/vnc-start - + && chmod +x /usr/local/bin/entrypoint ENTRYPOINT ["entrypoint"] CMD ["node", "epic-games.js"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 35fd467..6de4434 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -12,10 +12,10 @@ rm -f /fgc/data/browser/SingletonLock # Options passed directly to the Xvfb server: # -ac disables host-based access control mechanisms # −screen NUM WxHxD creates the screen and sets its width, height, and depth -Xvfb "$DISPLAY" -ac -screen 0 "${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}" >/dev/null 2>&1 & - -if [ "$VNC_ENABLED" = true ]; then - vnc-start >/dev/null 2>&1 & -fi +Xvfb :1 -ac -screen 0 "${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}" >/dev/null 2>&1 & +x11vnc -display :1.0 -forever -shared -rfbport "${VNC_PORT:-5900}" -passwd "${VNC_PASSWORD:-secret}" -bg +websockify -D --web "$NOVNC_HOME" "$NOVNC_PORT" "localhost:$VNC_PORT" & +DISPLAY=:1.0 +export DISPLAY exec tini -g -- "$@" diff --git a/docker/vnc-start.sh b/docker/vnc-start.sh deleted file mode 100755 index 92423ea..0000000 --- a/docker/vnc-start.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/sh - -# Start VNC in a background process: -x11vnc -display "$DISPLAY" -forever -shared -rfbport "${VNC_PORT:-5900}" \ - -passwd "${VNC_PASSWORD:-secret}" -bg -NOVNC_HOME=/usr/share/novnc -ln -s $NOVNC_HOME/vnc_auto.html $NOVNC_HOME/index.html -websockify -D --web "$NOVNC_HOME" "$NOVNC_PORT" "localhost:$VNC_PORT" & - -# Execute the given command: -exec "$@" From 08da100646edac2d8fa2b95bbb2b2ec55cc5ad43 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 28 Aug 2022 20:28:56 +0100 Subject: [PATCH 055/520] no : in filenames on Windows! closes #21, ref #20 --- epic-games.js | 2 +- prime-gaming.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index 07ed9c5..4bc9a82 100644 --- a/epic-games.js +++ b/epic-games.js @@ -109,7 +109,7 @@ try { console.log('Claimed successfully!'); } catch (e) { console.log(e); - const p = path.resolve(dirs.screenshots, 'epic-games', `${datetime()}.png`); + const p = path.resolve(dirs.screenshots, 'epic-games', `${datetime().replaceAll(':', '.')}.png`); await page.screenshot({ path: p, fullPage: true }); console.info('Saved a screenshot of hcaptcha challenge to', p); console.error('Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? diff --git a/prime-gaming.js b/prime-gaming.js index 4999449..8a87a17 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -132,7 +132,7 @@ try { await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); await page.click('button[data-type="Game"]'); } while (n); - const p = path.resolve(dirs.screenshots, 'prime-gaming', `${datetime()}.png`); + const p = path.resolve(dirs.screenshots, 'prime-gaming', `${datetime().replaceAll(':', '.')}.png`); // await page.screenshot({ path: p, fullPage: true }); await page.locator(games_sel).screenshot({ path: p }); } catch(error) { From 6f2271168dc70304ed47f0fad63ae67a412b7889 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 28 Aug 2022 20:35:02 +0100 Subject: [PATCH 056/520] `clickIfExists` only if `isVisible`, closes #20 --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 8a87a17..baa2b46 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -39,7 +39,7 @@ const page = context.pages()[0]; console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); const clickIfExists = async selector => { - if (await page.locator(selector).count() > 0) + if (await page.locator(selector).count() > 0 && await page.locator(selector).isVisible()) await page.click(selector); }; From 51f2fbfb53904120ef5f5c4190dc69f16a7725b0 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 2 Sep 2022 14:37:41 +0200 Subject: [PATCH 057/520] upgrade deps via ncu -u MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @playwright/test ^1.20.1 → ^1.25.1 playwright ^1.20.1 → ^1.25.1 puppeteer-extra-plugin-stealth ^2.9.0 → ^2.11.1 --- package-lock.json | 4438 +++------------------------------------------ package.json | 6 +- 2 files changed, 277 insertions(+), 4167 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4fda962..24842aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,1166 +5,79 @@ "packages": { "": { "devDependencies": { - "@playwright/test": "^1.20.1", + "@playwright/test": "^1.25.1", "cross-env": "^7.0.3", "lowdb": "^3.0.0", - "playwright": "^1.20.1", - "puppeteer-extra-plugin-stealth": "^2.9.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", - "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.16.12", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", - "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.12", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.10", - "@babel/types": "^7.16.8", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", - "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", - "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", - "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", - "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", - "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.17.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", - "dev": true, - "dependencies": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", - "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "dependencies": { - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.17.8", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.8.tgz", - "integrity": "sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.17.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz", - "integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", - "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", - "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", - "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", - "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.10", - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", - "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.14.5" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", - "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz", - "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-typescript": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/preset-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz", - "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-transform-typescript": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", - "@babel/types": "^7.17.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@jest/types": { - "version": "27.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/@jest/types/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@jest/types/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@jest/types/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@jest/types/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/types/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "playwright": "^1.25.1", + "puppeteer-extra-plugin-stealth": "^2.11.1" } }, "node_modules/@playwright/test": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.20.1.tgz", - "integrity": "sha512-muk3KZXfA7sXTwUEXfL3m4tusj/MBGYjxIFmooi+F2Pf6hKjjVl4+8niy77Xujk4jpL7hZbbqq9v5bRl2m+C8Q==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.25.1.tgz", + "integrity": "sha512-IJ4X0yOakXtwkhbnNzKkaIgXe6df7u3H3FnuhI9Jqh+CdO0e/lYQlDLYiyI9cnXK8E7UAppAWP+VqAv6VX7HQg==", "dev": true, "dependencies": { - "@babel/code-frame": "7.16.7", - "@babel/core": "7.16.12", - "@babel/helper-plugin-utils": "7.16.7", - "@babel/plugin-proposal-class-properties": "7.16.7", - "@babel/plugin-proposal-dynamic-import": "7.16.7", - "@babel/plugin-proposal-export-namespace-from": "7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7", - "@babel/plugin-proposal-numeric-separator": "7.16.7", - "@babel/plugin-proposal-optional-chaining": "7.16.7", - "@babel/plugin-proposal-private-methods": "7.16.11", - "@babel/plugin-proposal-private-property-in-object": "7.16.7", - "@babel/plugin-syntax-async-generators": "7.8.4", - "@babel/plugin-syntax-json-strings": "7.8.3", - "@babel/plugin-syntax-object-rest-spread": "7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "7.8.3", - "@babel/plugin-transform-modules-commonjs": "7.16.8", - "@babel/preset-typescript": "7.16.7", - "colors": "1.4.0", - "commander": "8.3.0", - "debug": "4.3.3", - "expect": "27.2.5", - "jest-matcher-utils": "27.2.5", - "json5": "2.2.1", - "mime": "3.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "open": "8.4.0", - "pirates": "4.0.4", - "playwright-core": "1.20.1", - "rimraf": "3.0.2", - "source-map-support": "0.4.18", - "stack-utils": "2.0.5", - "yazl": "2.5.1" + "@types/node": "*", + "playwright-core": "1.25.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/@types/debug": { "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", "dev": true, - "license": "MIT", "dependencies": { "@types/ms": "*" } }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.4", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, "node_modules/@types/ms": { "version": "0.7.31", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true }, "node_modules/@types/node": { "version": "17.0.5", "dev": true, "license": "MIT" }, - "node_modules/@types/puppeteer": { - "version": "5.4.3", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/stack-utils": { - "version": "2.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yargs": { - "version": "16.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "20.2.1", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/yauzl": { - "version": "2.9.2", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/arr-union": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/bl": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true }, "node_modules/brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "node_modules/braces": { - "version": "3.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browserslist": { - "version": "4.20.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", - "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001317", - "electron-to-chromium": "^1.4.84", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001322", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001322.tgz", - "integrity": "sha512-neRmrmIrCGuMnxGSoh+x7zYtQFFgnSY2jaomjU56sCkTA6JINqQrxutF459JpWcWRajvoyn95sOXq4Pqrnyjew==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - } - ] - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "dev": true, - "license": "ISC", - "peer": true - }, "node_modules/clone-deep": { "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", "dev": true, - "license": "MIT", "dependencies": { "for-own": "^0.1.3", "is-plain-object": "^2.0.1", @@ -1176,60 +89,11 @@ "node": ">=0.10.0" } }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/colors": { - "version": "1.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.1.90" - } - }, - "node_modules/commander": { - "version": "8.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, "node_modules/concat-map": { "version": "0.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "1.8.0", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.1" - } + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true }, "node_modules/cross-env": { "version": "7.0.3", @@ -1264,9 +128,10 @@ } }, "node_modules/debug": { - "version": "4.3.3", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -1279,175 +144,29 @@ } } }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "dev": true, - "license": "MIT" - }, "node_modules/deepmerge": { "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.937139", - "dev": true, - "license": "BSD-3-Clause", - "peer": true - }, - "node_modules/diff-sequences": { - "version": "27.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.4.101", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.101.tgz", - "integrity": "sha512-XJH+XmJjACx1S7ASl/b//KePcda5ocPnFH2jErztXcIS8LpP0SE6rX8ZxiY5/RaDPnaF1rj0fPaHfppzb0e2Aw==", - "dev": true - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "dev": true, - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/expect": { - "version": "27.2.5", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.2.5", - "ansi-styles": "^5.0.0", - "jest-get-type": "^27.0.6", - "jest-matcher-utils": "^27.2.5", - "jest-message-util": "^27.2.5", - "jest-regex-util": "^27.0.6" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/expect/node_modules/ansi-styles": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/extract-zip": { - "version": "2.0.1", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, - "node_modules/fill-range": { - "version": "7.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/for-in": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/for-own": { "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", "dev": true, - "license": "MIT", "dependencies": { "for-in": "^1.0.1" }, @@ -1455,16 +174,11 @@ "node": ">=0.10.0" } }, - "node_modules/fs-constants": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/fs-extra": { - "version": "10.0.0", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, - "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -1476,60 +190,20 @@ }, "node_modules/fs.realpath": { "version": "1.0.0", - "dev": true, - "license": "ISC" - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/glob": { - "version": "7.2.0", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, - "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" }, @@ -1540,89 +214,17 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, "node_modules/graceful-fs": { - "version": "4.2.8", - "dev": true, - "license": "ISC" - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause", - "peer": true + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true }, "node_modules/inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, - "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1630,59 +232,35 @@ }, "node_modules/inherits": { "version": "2.0.4", - "dev": true, - "license": "ISC" - }, - "node_modules/ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, "node_modules/is-buffer": { "version": "1.1.6", - "dev": true, - "license": "MIT" - }, - "node_modules/is-docker": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true }, "node_modules/is-extendable": { "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "7.0.0", + "node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-wsl": { - "version": "2.2.0", - "dev": true, - "license": "MIT", "dependencies": { - "is-docker": "^2.0.0" + "isobject": "^3.0.1" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, "node_modules/isexe": { @@ -1693,307 +271,18 @@ }, "node_modules/isobject": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/jest-diff": { - "version": "27.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^27.4.0", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.2" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-diff/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-diff/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-diff/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-diff/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-diff/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-get-type": { - "version": "27.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils": { - "version": "27.2.5", - "dev": true, - "license": "MIT", - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^27.2.5", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.2.5" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-matcher-utils/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-matcher-utils/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-matcher-utils/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util": { - "version": "27.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.4.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.4.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jest-message-util/node_modules/ansi-styles": { - "version": "4.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/chalk": { - "version": "4.1.2", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/jest-message-util/node_modules/color-convert": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/jest-message-util/node_modules/color-name": { - "version": "1.1.4", - "dev": true, - "license": "MIT" - }, - "node_modules/jest-message-util/node_modules/has-flag": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-message-util/node_modules/supports-color": { - "version": "7.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest-regex-util": { - "version": "27.4.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/jpeg-js": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz", - "integrity": "sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==", - "dev": true - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsonfile": { "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, - "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -2003,8 +292,9 @@ }, "node_modules/kind-of": { "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", "dev": true, - "license": "MIT", "dependencies": { "is-buffer": "^1.1.5" }, @@ -2014,24 +304,13 @@ }, "node_modules/lazy-cache": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/locate-path": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/lowdb": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-3.0.0.tgz", @@ -2049,8 +328,9 @@ }, "node_modules/merge-deep": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", + "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", "dev": true, - "license": "MIT", "dependencies": { "arr-union": "^3.1.0", "clone-deep": "^0.2.4", @@ -2060,34 +340,11 @@ "node": ">=0.10.0" } }, - "node_modules/micromatch": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true, - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/minimatch": { - "version": "3.0.4", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -2097,8 +354,9 @@ }, "node_modules/mixin-object": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", "dev": true, - "license": "MIT", "dependencies": { "for-in": "^0.1.3", "is-extendable": "^0.1.1" @@ -2109,141 +367,33 @@ }, "node_modules/mixin-object/node_modules/for-in": { "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/mkdirp-classic": { - "version": "0.5.3", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/ms": { - "version": "2.1.3", - "dev": true, - "license": "MIT" - }, - "node_modules/node-fetch": { - "version": "2.6.5", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - } - }, - "node_modules/node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, - "license": "ISC", "dependencies": { "wrappy": "1" } }, - "node_modules/open": { - "version": "8.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, "node_modules/path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2257,230 +407,39 @@ "node": ">=8" } }, - "node_modules/pend": { - "version": "1.2.0", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pirates": { - "version": "4.0.4", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pixelmatch": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz", - "integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==", - "dev": true, - "dependencies": { - "pngjs": "^4.0.1" - }, - "bin": { - "pixelmatch": "bin/pixelmatch" - } - }, - "node_modules/pixelmatch/node_modules/pngjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz", - "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/pkg-dir": { - "version": "4.2.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/playwright": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.20.1.tgz", - "integrity": "sha512-d/25SFUk6Rkt3h+RU13T7h6o0UTCLKXKYJILWVlC+NmrE7Tvn3LlXxoREfFXVNFikRZWTV60WBCZKgNbj7RfrA==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.25.1.tgz", + "integrity": "sha512-kOlW7mllnQ70ALTwAor73q/FhdH9EEXLUqjdzqioYLcSVC4n4NBfDqeCikGuayFZrLECLkU6Hcbziy/szqTXSA==", "dev": true, "hasInstallScript": true, "dependencies": { - "playwright-core": "1.20.1" + "playwright-core": "1.25.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=12" + "node": ">=14" } }, "node_modules/playwright-core": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.20.1.tgz", - "integrity": "sha512-A8ZsZ09gaSbxP0UijoLyzp3LJc0kWMxDooLPi+mm4/5iYnTbd6PF5nKjoFw1a7KwjZIEgdhJduah4BcUIh+IPA==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.25.1.tgz", + "integrity": "sha512-lSvPCmA2n7LawD2Hw7gSCLScZ+vYRkhU8xH0AapMyzwN+ojoDqhkH/KIEUxwNu2PjPoE/fcE0wLAksdOhJ2O5g==", "dev": true, - "dependencies": { - "colors": "1.4.0", - "commander": "8.3.0", - "debug": "4.3.3", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.0", - "jpeg-js": "0.4.3", - "mime": "3.0.0", - "pixelmatch": "5.2.1", - "pngjs": "6.0.0", - "progress": "2.0.3", - "proper-lockfile": "4.1.2", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "socks-proxy-agent": "6.1.1", - "stack-utils": "2.0.5", - "ws": "8.4.2", - "yauzl": "2.10.0", - "yazl": "2.5.1" - }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=12" - } - }, - "node_modules/pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", - "dev": true, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/pretty-format": { - "version": "27.4.2", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^27.4.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/progress": { - "version": "2.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/puppeteer": { - "version": "13.0.1", - "dev": true, - "hasInstallScript": true, - "license": "Apache-2.0", - "peer": true, - "dependencies": { - "debug": "4.3.2", - "devtools-protocol": "0.0.937139", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.0", - "node-fetch": "2.6.5", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.2.3" - }, - "engines": { - "node": ">=10.18.1" - } - }, - "node_modules/puppeteer-extra": { - "version": "3.2.3", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@types/debug": "^4.1.0", - "@types/puppeteer": "*", - "debug": "^4.1.1", - "deepmerge": "^4.2.2" - }, - "engines": { - "node": ">=8" - }, - "peerDependencies": { - "puppeteer": "*" + "node": ">=14" } }, "node_modules/puppeteer-extra-plugin": { - "version": "3.2.0", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", + "integrity": "sha512-0uatQxzuVn8yegbrEwSk03wvwpMB5jNs7uTTnermylLZzoT+1rmAQaJXwlS3+vADUbw6ELNgNEHC7Skm0RqHbQ==", "dev": true, - "license": "MIT", "dependencies": { "@types/debug": "^4.1.0", "debug": "^4.1.1", @@ -2490,121 +449,98 @@ "node": ">=9.11.2" }, "peerDependencies": { + "playwright-extra": "*", "puppeteer-extra": "*" + }, + "peerDependenciesMeta": { + "playwright-extra": { + "optional": true + }, + "puppeteer-extra": { + "optional": true + } } }, "node_modules/puppeteer-extra-plugin-stealth": { - "version": "2.9.0", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.1.tgz", + "integrity": "sha512-n0wdC0Ilc9tk5L6FWLyd0P2gT8b2fp+2NuB+KB0oTSw3wXaZ0D6WNakjJsayJ4waGzIJFCUHkmK9zgx5NKMoFw==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.1.1", - "puppeteer-extra-plugin": "^3.2.0", - "puppeteer-extra-plugin-user-preferences": "^2.3.1" + "puppeteer-extra-plugin": "^3.2.2", + "puppeteer-extra-plugin-user-preferences": "^2.4.0" }, "engines": { "node": ">=8" + }, + "peerDependencies": { + "playwright-extra": "*", + "puppeteer-extra": "*" + }, + "peerDependenciesMeta": { + "playwright-extra": { + "optional": true + }, + "puppeteer-extra": { + "optional": true + } } }, "node_modules/puppeteer-extra-plugin-user-data-dir": { - "version": "2.3.1", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.0.tgz", + "integrity": "sha512-qrhYPTGIqzL2hpeJ5DXjf8xMy5rt1UvcqSgpGTTOUOjIMz1ROWnKHjBoE9fNBJ4+ToRZbP8MzIDXWlEk/e1zJA==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.1.1", "fs-extra": "^10.0.0", - "puppeteer-extra-plugin": "^3.2.0" + "puppeteer-extra-plugin": "^3.2.2", + "rimraf": "^3.0.2" }, "engines": { "node": ">=8" + }, + "peerDependencies": { + "playwright-extra": "*", + "puppeteer-extra": "*" + }, + "peerDependenciesMeta": { + "playwright-extra": { + "optional": true + }, + "puppeteer-extra": { + "optional": true + } } }, "node_modules/puppeteer-extra-plugin-user-preferences": { - "version": "2.3.1", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.0.tgz", + "integrity": "sha512-4XxMhMkJ+qqLsPY9ULF90qS9Bj1Qrwwgp1TY9zTdp1dJuy7QSgYE7xlyamq3cKrRuzg3QUOqygJo52sVeXSg5A==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^4.1.1", "deepmerge": "^4.2.2", - "puppeteer-extra-plugin": "^3.2.0", - "puppeteer-extra-plugin-user-data-dir": "^2.3.1" + "puppeteer-extra-plugin": "^3.2.2", + "puppeteer-extra-plugin-user-data-dir": "^2.4.0" }, "engines": { "node": ">=8" - } - }, - "node_modules/puppeteer/node_modules/debug": { - "version": "4.3.2", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/puppeteer/node_modules/ms": { - "version": "2.1.2", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/puppeteer/node_modules/ws": { - "version": "8.2.3", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10.0.0" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "playwright-extra": "*", + "puppeteer-extra": "*" }, "peerDependenciesMeta": { - "bufferutil": { + "playwright-extra": { "optional": true }, - "utf-8-validate": { + "puppeteer-extra": { "optional": true } } }, - "node_modules/react-is": { - "version": "17.0.2", - "dev": true, - "license": "MIT" - }, - "node_modules/readable-stream": { - "version": "3.6.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true, - "engines": { - "node": ">= 4" - } - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -2620,24 +556,11 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/safe-buffer": { - "version": "5.1.2", - "dev": true, - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/shallow-clone": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", "dev": true, - "license": "MIT", "dependencies": { "is-extendable": "^0.1.1", "kind-of": "^2.0.1", @@ -2650,8 +573,9 @@ }, "node_modules/shallow-clone/node_modules/kind-of": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", "dev": true, - "license": "MIT", "dependencies": { "is-buffer": "^1.0.2" }, @@ -2661,8 +585,9 @@ }, "node_modules/shallow-clone/node_modules/lazy-cache": { "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", "dev": true, - "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -2688,93 +613,6 @@ "node": ">=8" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true, - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", - "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", - "dev": true, - "dependencies": { - "ip": "^1.1.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.13.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", - "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "debug": "^4.3.1", - "socks": "^2.6.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/source-map": { - "version": "0.5.7", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-support": { - "version": "0.4.18", - "dev": true, - "license": "MIT", - "dependencies": { - "source-map": "^0.5.6" - } - }, - "node_modules/stack-utils": { - "version": "2.0.5", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/steno": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/steno/-/steno-2.1.0.tgz", @@ -2787,147 +625,15 @@ "url": "https://github.com/sponsors/typicode" } }, - "node_modules/string_decoder": { - "version": "1.3.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.2.1", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "peer": true - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/tar-fs": { - "version": "2.1.1", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/tar-stream": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/through": { - "version": "2.3.8", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/tr46": { - "version": "0.0.3", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/unbzip2-stream": { - "version": "1.4.3", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, "node_modules/universalify": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true, - "license": "MIT", "engines": { "node": ">= 10.0.0" } }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "dev": true, - "license": "BSD-2-Clause", - "peer": true - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -2945,850 +651,67 @@ }, "node_modules/wrappy": { "version": "1.0.2", - "dev": true, - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz", - "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yazl": { - "version": "2.5.1", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3" - } + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true } }, "dependencies": { - "@babel/code-frame": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz", - "integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==", - "dev": true, - "requires": { - "@babel/highlight": "^7.16.7" - } - }, - "@babel/compat-data": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.17.7.tgz", - "integrity": "sha512-p8pdE6j0a29TNGebNm7NzYZWB3xVZJBZ7XGs42uAKzQo8VQ3F0By/cQCtUEABwIqw5zo6WA4NbmxsfzADzMKnQ==", - "dev": true - }, - "@babel/core": { - "version": "7.16.12", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.16.12.tgz", - "integrity": "sha512-dK5PtG1uiN2ikk++5OzSYsitZKny4wOCD0nrO4TqnW4BVBTQ2NGS3NgilvT/TEyxTST7LNyWV/T4tXDoD3fOgg==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.8", - "@babel/helper-compilation-targets": "^7.16.7", - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helpers": "^7.16.7", - "@babel/parser": "^7.16.12", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.16.10", - "@babel/types": "^7.16.8", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.1.2", - "semver": "^6.3.0", - "source-map": "^0.5.0" - } - }, - "@babel/generator": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.17.7.tgz", - "integrity": "sha512-oLcVCTeIFadUoArDTwpluncplrYBmTCCZZgXCbgNGvOBBiSDDK3eWO4b/+eOTli5tKv1lg+a5/NAXg+nTcei1w==", - "dev": true, - "requires": { - "@babel/types": "^7.17.0", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-compilation-targets": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.17.7.tgz", - "integrity": "sha512-UFzlz2jjd8kroj0hmCFV5zr+tQPi1dpC2cRsDV/3IEW8bJfCPrPpmcSN6ZS8RqIq4LXcmpipCQFPddyFA5Yc7w==", - "dev": true, - "requires": { - "@babel/compat-data": "^7.17.7", - "@babel/helper-validator-option": "^7.16.7", - "browserslist": "^4.17.5", - "semver": "^6.3.0" - } - }, - "@babel/helper-create-class-features-plugin": { - "version": "7.17.6", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.17.6.tgz", - "integrity": "sha512-SogLLSxXm2OkBbSsHZMM4tUi8fUzjs63AT/d0YQIzr6GSd8Hxsbk2KYDX0k0DweAzGMj/YWeiCsorIdtdcW8Eg==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/helper-replace-supers": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7" - } - }, - "@babel/helper-environment-visitor": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz", - "integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-function-name": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz", - "integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==", - "dev": true, - "requires": { - "@babel/helper-get-function-arity": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-get-function-arity": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz", - "integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-hoist-variables": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz", - "integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-member-expression-to-functions": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.17.7.tgz", - "integrity": "sha512-thxXgnQ8qQ11W2wVUObIqDL4p148VMxkt5T/qpN5k2fboRyzFGFmKsTGViquyM5QHKUy48OZoca8kw4ajaDPyw==", - "dev": true, - "requires": { - "@babel/types": "^7.17.0" - } - }, - "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-module-transforms": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.17.7.tgz", - "integrity": "sha512-VmZD99F3gNTYB7fJRDTi+u6l/zxY0BE6OIxPSU7a50s6ZUQkHwSDmV92FfM+oCG0pZRVojGYhkR8I0OGeCVREw==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-module-imports": "^7.16.7", - "@babel/helper-simple-access": "^7.17.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/helper-validator-identifier": "^7.16.7", - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0" - } - }, - "@babel/helper-optimise-call-expression": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz", - "integrity": "sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-plugin-utils": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz", - "integrity": "sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==", - "dev": true - }, - "@babel/helper-replace-supers": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.16.7.tgz", - "integrity": "sha512-y9vsWilTNaVnVh6xiJfABzsNpgDPKev9HnAgz6Gb1p6UUwf9NepdlsV7VXGCftJM+jqD5f7JIEubcpLjZj5dBw==", - "dev": true, - "requires": { - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-member-expression-to-functions": "^7.16.7", - "@babel/helper-optimise-call-expression": "^7.16.7", - "@babel/traverse": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-simple-access": { - "version": "7.17.7", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.17.7.tgz", - "integrity": "sha512-txyMCGroZ96i+Pxr3Je3lzEJjqwaRC9buMUgtomcrLe5Nd0+fk1h0LLA+ixUF5OW7AhHuQ7Es1WcQJZmZsz2XA==", - "dev": true, - "requires": { - "@babel/types": "^7.17.0" - } - }, - "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.16.0.tgz", - "integrity": "sha512-+il1gTy0oHwUsBQZyJvukbB4vPMdcYBrFHa0Uc4AizLxbq6BOYC51Rv4tWocX9BLBDLZ4kc6qUFpQ6HRgL+3zw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.0" - } - }, - "@babel/helper-split-export-declaration": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz", - "integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==", - "dev": true, - "requires": { - "@babel/types": "^7.16.7" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz", - "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==", - "dev": true - }, - "@babel/helper-validator-option": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.16.7.tgz", - "integrity": "sha512-TRtenOuRUVo9oIQGPC5G9DgK4743cdxvtOw0weQNpZXaS16SCBi5MNjZF8vba3ETURjZpTbVn7Vvcf2eAwFozQ==", - "dev": true - }, - "@babel/helpers": { - "version": "7.17.8", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.17.8.tgz", - "integrity": "sha512-QcL86FGxpfSJwGtAvv4iG93UL6bmqBdmoVY0CMCU2g+oD2ezQse3PT5Pa+jiD6LJndBQi0EDlpzOWNlLuhz5gw==", - "dev": true, - "requires": { - "@babel/template": "^7.16.7", - "@babel/traverse": "^7.17.3", - "@babel/types": "^7.17.0" - } - }, - "@babel/highlight": { - "version": "7.16.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", - "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "@babel/parser": { - "version": "7.17.8", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.17.8.tgz", - "integrity": "sha512-BoHhDJrJXqcg+ZL16Xv39H9n+AqJ4pcDrQBGZN+wHxIysrLZ3/ECwCBUch/1zUNhnsXULcONU3Ei5Hmkfk6kiQ==", - "dev": true - }, - "@babel/plugin-proposal-class-properties": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.16.7.tgz", - "integrity": "sha512-IobU0Xme31ewjYOShSIqd/ZGM/r/cuOz2z0MDbNrhF5FW+ZVgi0f2lyeoj9KFPDOAqsYxmLWZte1WOwlvY9aww==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-proposal-dynamic-import": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.16.7.tgz", - "integrity": "sha512-I8SW9Ho3/8DRSdmDdH3gORdyUuYnk1m4cMxUAdu5oy4n3OfN8flDEH+d60iG7dUfi0KkYwSvoalHzzdRzpWHTg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-dynamic-import": "^7.8.3" - } - }, - "@babel/plugin-proposal-export-namespace-from": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.16.7.tgz", - "integrity": "sha512-ZxdtqDXLRGBL64ocZcs7ovt71L3jhC1RGSyR996svrCi3PYqHNkb3SwPJCs8RIzD86s+WPpt2S73+EHCGO+NUA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.16.7.tgz", - "integrity": "sha512-K3XzyZJGQCr00+EtYtrDjmwX7o7PLK6U9bi1nCwkQioRFVUv6dJoxbQjtWVtP+bCPy82bONBKG8NPyQ4+i6yjg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.16.7.tgz", - "integrity": "sha512-aUOrYU3EVtjf62jQrCj63pYZ7k6vns2h/DQvHPWGmsJRYzWXZ6/AsfgpiRy6XiuIDADhJzP2Q9MwSMKauBQ+UQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" - } - }, - "@babel/plugin-proposal-numeric-separator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.16.7.tgz", - "integrity": "sha512-vQgPMknOIgiuVqbokToyXbkY/OmmjAzr/0lhSIbG/KmnzXPGwW/AdhdKpi+O4X/VkWiWjnkKOBiqJrTaC98VKw==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "@babel/plugin-proposal-optional-chaining": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.16.7.tgz", - "integrity": "sha512-eC3xy+ZrUcBtP7x+sq62Q/HYd674pPTb/77XZMb5wbDPGWIdUbSr4Agr052+zaUPSb+gGRnjxXfKFvx5iMJ+DA==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.16.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.3" - } - }, - "@babel/plugin-proposal-private-methods": { - "version": "7.16.11", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.16.11.tgz", - "integrity": "sha512-F/2uAkPlXDr8+BHpZvo19w3hLFKge+k75XUprE6jaqKxjGkSYcK+4c+bup5PdW/7W/Rpjwql7FTVEDW+fRAQsw==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.10", - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-proposal-private-property-in-object": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.16.7.tgz", - "integrity": "sha512-rMQkjcOFbm+ufe3bTZLyOfsOUOxyvLXZJCTARhJr+8UMSoZmqTe1K1BgkFcrW37rAchWg57yI69ORxiWvUINuQ==", - "dev": true, - "requires": { - "@babel/helper-annotate-as-pure": "^7.16.7", - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-private-property-in-object": "^7.14.5" - } - }, - "@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "@babel/plugin-syntax-private-property-in-object": { - "version": "7.14.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz", - "integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.14.5" - } - }, - "@babel/plugin-syntax-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.16.7.tgz", - "integrity": "sha512-YhUIJHHGkqPgEcMYkPCKTyGUdoGKWtopIycQyjJH8OjvRgOYsXsaKehLVPScKJWAULPxMa4N1vCe6szREFlZ7A==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7" - } - }, - "@babel/plugin-transform-modules-commonjs": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.8.tgz", - "integrity": "sha512-oflKPvsLT2+uKQopesJt3ApiaIS2HW+hzHFcwRNtyDGieAeC/dIHZX8buJQ2J2X1rxGPy4eRcUijm3qcSPjYcA==", - "dev": true, - "requires": { - "@babel/helper-module-transforms": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-simple-access": "^7.16.7", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "@babel/plugin-transform-typescript": { - "version": "7.16.8", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.16.8.tgz", - "integrity": "sha512-bHdQ9k7YpBDO2d0NVfkj51DpQcvwIzIusJ7mEUaMlbZq3Kt/U47j24inXZHQ5MDiYpCs+oZiwnXyKedE8+q7AQ==", - "dev": true, - "requires": { - "@babel/helper-create-class-features-plugin": "^7.16.7", - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/plugin-syntax-typescript": "^7.16.7" - } - }, - "@babel/preset-typescript": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.16.7.tgz", - "integrity": "sha512-WbVEmgXdIyvzB77AQjGBEyYPZx+8tTsO50XtfozQrkW8QB2rLJpH2lgx0TRw5EJrBxOZQ+wCcyPVQvS8tjEHpQ==", - "dev": true, - "requires": { - "@babel/helper-plugin-utils": "^7.16.7", - "@babel/helper-validator-option": "^7.16.7", - "@babel/plugin-transform-typescript": "^7.16.7" - } - }, - "@babel/template": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz", - "integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7" - } - }, - "@babel/traverse": { - "version": "7.17.3", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.17.3.tgz", - "integrity": "sha512-5irClVky7TxRWIRtxlh2WPUUOLhcPN06AGgaQSB8AEwuyEBgJVuJ5imdHm5zxk8w0QS5T+tDfnDxAlhWjpb7cw==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.17.3", - "@babel/helper-environment-visitor": "^7.16.7", - "@babel/helper-function-name": "^7.16.7", - "@babel/helper-hoist-variables": "^7.16.7", - "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.17.3", - "@babel/types": "^7.17.0", - "debug": "^4.1.0", - "globals": "^11.1.0" - } - }, - "@babel/types": { - "version": "7.17.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.17.0.tgz", - "integrity": "sha512-TmKSNO4D5rzhL5bjWFcVHHLETzfQ/AmbKpKPOSjlP0WoHZ6L911fgoOKY4Alp/emzG4cHJdyN49zpgkbXFEHHw==", - "dev": true, - "requires": { - "@babel/helper-validator-identifier": "^7.16.7", - "to-fast-properties": "^2.0.0" - } - }, - "@jest/types": { - "version": "27.4.2", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^16.0.0", - "chalk": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "@playwright/test": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.20.1.tgz", - "integrity": "sha512-muk3KZXfA7sXTwUEXfL3m4tusj/MBGYjxIFmooi+F2Pf6hKjjVl4+8niy77Xujk4jpL7hZbbqq9v5bRl2m+C8Q==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.25.1.tgz", + "integrity": "sha512-IJ4X0yOakXtwkhbnNzKkaIgXe6df7u3H3FnuhI9Jqh+CdO0e/lYQlDLYiyI9cnXK8E7UAppAWP+VqAv6VX7HQg==", "dev": true, "requires": { - "@babel/code-frame": "7.16.7", - "@babel/core": "7.16.12", - "@babel/helper-plugin-utils": "7.16.7", - "@babel/plugin-proposal-class-properties": "7.16.7", - "@babel/plugin-proposal-dynamic-import": "7.16.7", - "@babel/plugin-proposal-export-namespace-from": "7.16.7", - "@babel/plugin-proposal-logical-assignment-operators": "7.16.7", - "@babel/plugin-proposal-nullish-coalescing-operator": "7.16.7", - "@babel/plugin-proposal-numeric-separator": "7.16.7", - "@babel/plugin-proposal-optional-chaining": "7.16.7", - "@babel/plugin-proposal-private-methods": "7.16.11", - "@babel/plugin-proposal-private-property-in-object": "7.16.7", - "@babel/plugin-syntax-async-generators": "7.8.4", - "@babel/plugin-syntax-json-strings": "7.8.3", - "@babel/plugin-syntax-object-rest-spread": "7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "7.8.3", - "@babel/plugin-transform-modules-commonjs": "7.16.8", - "@babel/preset-typescript": "7.16.7", - "colors": "1.4.0", - "commander": "8.3.0", - "debug": "4.3.3", - "expect": "27.2.5", - "jest-matcher-utils": "27.2.5", - "json5": "2.2.1", - "mime": "3.0.0", - "minimatch": "3.0.4", - "ms": "2.1.3", - "open": "8.4.0", - "pirates": "4.0.4", - "playwright-core": "1.20.1", - "rimraf": "3.0.2", - "source-map-support": "0.4.18", - "stack-utils": "2.0.5", - "yazl": "2.5.1" + "@types/node": "*", + "playwright-core": "1.25.1" } }, "@types/debug": { "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", + "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", "dev": true, "requires": { "@types/ms": "*" } }, - "@types/istanbul-lib-coverage": { - "version": "2.0.4", - "dev": true - }, - "@types/istanbul-lib-report": { - "version": "3.0.0", - "dev": true, - "requires": { - "@types/istanbul-lib-coverage": "*" - } - }, - "@types/istanbul-reports": { - "version": "3.0.1", - "dev": true, - "requires": { - "@types/istanbul-lib-report": "*" - } - }, "@types/ms": { "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", "dev": true }, "@types/node": { "version": "17.0.5", "dev": true }, - "@types/puppeteer": { - "version": "5.4.3", - "dev": true, - "peer": true, - "requires": { - "@types/node": "*" - } - }, - "@types/stack-utils": { - "version": "2.0.1", - "dev": true - }, - "@types/yargs": { - "version": "16.0.4", - "dev": true, - "requires": { - "@types/yargs-parser": "*" - } - }, - "@types/yargs-parser": { - "version": "20.2.1", - "dev": true - }, - "@types/yauzl": { - "version": "2.9.2", - "dev": true, - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "agent-base": { - "version": "6.0.2", - "dev": true, - "requires": { - "debug": "4" - } - }, - "ansi-regex": { - "version": "5.0.1", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, "arr-union": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", "dev": true }, - "babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "requires": { - "object.assign": "^4.1.0" - } - }, "balanced-match": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "base64-js": { - "version": "1.5.1", - "dev": true, - "peer": true - }, - "bl": { - "version": "4.1.0", - "dev": true, - "peer": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, "brace-expansion": { "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "braces": { - "version": "3.0.2", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browserslist": { - "version": "4.20.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.20.2.tgz", - "integrity": "sha512-CQOBCqp/9pDvDbx3xfMi+86pr4KXIf2FDkTTdeuYw8OxS9t898LA1Khq57gtufFILXpfgsSx5woNgsBgvGjpsA==", - "dev": true, - "requires": { - "caniuse-lite": "^1.0.30001317", - "electron-to-chromium": "^1.4.84", - "escalade": "^3.1.1", - "node-releases": "^2.0.2", - "picocolors": "^1.0.0" - } - }, - "buffer": { - "version": "5.7.1", - "dev": true, - "peer": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "buffer-crc32": { - "version": "0.2.13", - "dev": true - }, - "call-bind": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", - "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.2" - } - }, - "caniuse-lite": { - "version": "1.0.30001322", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001322.tgz", - "integrity": "sha512-neRmrmIrCGuMnxGSoh+x7zYtQFFgnSY2jaomjU56sCkTA6JINqQrxutF459JpWcWRajvoyn95sOXq4Pqrnyjew==", - "dev": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "chownr": { - "version": "1.1.4", - "dev": true, - "peer": true - }, "clone-deep": { "version": "0.2.4", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", + "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", "dev": true, "requires": { "for-own": "^0.1.3", @@ -3796,51 +719,14 @@ "kind-of": "^3.0.2", "lazy-cache": "^1.0.3", "shallow-clone": "^0.1.2" - }, - "dependencies": { - "is-plain-object": { - "version": "2.0.4", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - } } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "colors": { - "version": "1.4.0", - "dev": true - }, - "commander": { - "version": "8.3.0", - "dev": true - }, "concat-map": { "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "convert-source-map": { - "version": "1.8.0", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -3862,138 +748,39 @@ } }, "debug": { - "version": "4.3.3", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, "requires": { "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "dev": true - } } }, "deepmerge": { "version": "4.2.2", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", "dev": true }, - "define-lazy-prop": { - "version": "2.0.0", - "dev": true - }, - "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "requires": { - "object-keys": "^1.0.12" - } - }, - "devtools-protocol": { - "version": "0.0.937139", - "dev": true, - "peer": true - }, - "diff-sequences": { - "version": "27.4.0", - "dev": true - }, - "electron-to-chromium": { - "version": "1.4.101", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.101.tgz", - "integrity": "sha512-XJH+XmJjACx1S7ASl/b//KePcda5ocPnFH2jErztXcIS8LpP0SE6rX8ZxiY5/RaDPnaF1rj0fPaHfppzb0e2Aw==", - "dev": true - }, - "end-of-stream": { - "version": "1.4.4", - "dev": true, - "requires": { - "once": "^1.4.0" - } - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "expect": { - "version": "27.2.5", - "dev": true, - "requires": { - "@jest/types": "^27.2.5", - "ansi-styles": "^5.0.0", - "jest-get-type": "^27.0.6", - "jest-matcher-utils": "^27.2.5", - "jest-message-util": "^27.2.5", - "jest-regex-util": "^27.0.6" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "dev": true - } - } - }, - "extract-zip": { - "version": "2.0.1", - "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "fd-slicer": { - "version": "1.1.0", - "dev": true, - "requires": { - "pend": "~1.2.0" - } - }, - "fill-range": { - "version": "7.0.1", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "find-up": { - "version": "4.1.0", - "dev": true, - "peer": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, "for-in": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", "dev": true }, "for-own": { "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", "dev": true, "requires": { "for-in": "^1.0.1" } }, - "fs-constants": { - "version": "1.0.0", - "dev": true, - "peer": true - }, "fs-extra": { - "version": "10.0.0", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "dev": true, "requires": { "graceful-fs": "^4.2.0", @@ -4003,94 +790,34 @@ }, "fs.realpath": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "dev": true }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "gensync": { - "version": "1.0.0-beta.2", - "dev": true - }, - "get-intrinsic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", - "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", - "dev": true, - "requires": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "get-stream": { - "version": "5.2.0", - "dev": true, - "requires": { - "pump": "^3.0.0" - } - }, "glob": { - "version": "7.2.0", + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", - "minimatch": "^3.0.4", + "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, - "globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true - }, "graceful-fs": { - "version": "4.2.8", + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", "dev": true }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true - }, - "https-proxy-agent": { - "version": "5.0.0", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "ieee754": { - "version": "1.2.1", - "dev": true, - "peer": true - }, "inflight": { "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "dev": true, "requires": { "once": "^1.3.0", @@ -4099,35 +826,29 @@ }, "inherits": { "version": "2.0.4", - "dev": true - }, - "ip": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz", - "integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, "is-buffer": { "version": "1.1.6", - "dev": true - }, - "is-docker": { - "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-extendable": { "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", "dev": true }, - "is-number": { - "version": "7.0.0", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "is-docker": "^2.0.0" + "isobject": "^3.0.1" } }, "isexe": { @@ -4138,194 +859,14 @@ }, "isobject": { "version": "3.0.1", - "dev": true - }, - "jest-diff": { - "version": "27.4.2", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "diff-sequences": "^27.4.0", - "jest-get-type": "^27.4.0", - "pretty-format": "^27.4.2" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-get-type": { - "version": "27.4.0", - "dev": true - }, - "jest-matcher-utils": { - "version": "27.2.5", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "jest-diff": "^27.2.5", - "jest-get-type": "^27.0.6", - "pretty-format": "^27.2.5" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-message-util": { - "version": "27.4.2", - "dev": true, - "requires": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^27.4.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.4", - "pretty-format": "^27.4.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.3" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "color-convert": { - "version": "2.0.1", - "dev": true, - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "dev": true - }, - "has-flag": { - "version": "4.0.0", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "jest-regex-util": { - "version": "27.4.0", - "dev": true - }, - "jpeg-js": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.3.tgz", - "integrity": "sha512-ru1HWKek8octvUHFHvE5ZzQ1yAsJmIvRdGWvSoKV52XKyuyYA437QWDttXT8eZXDSbuMpHlLzPDZUPd6idIz+Q==", - "dev": true - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true - }, - "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", "dev": true }, "jsonfile": { "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, "requires": { "graceful-fs": "^4.1.6", @@ -4334,6 +875,8 @@ }, "kind-of": { "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", "dev": true, "requires": { "is-buffer": "^1.1.5" @@ -4341,16 +884,10 @@ }, "lazy-cache": { "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", "dev": true }, - "locate-path": { - "version": "5.0.0", - "dev": true, - "peer": true, - "requires": { - "p-locate": "^4.1.0" - } - }, "lowdb": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-3.0.0.tgz", @@ -4362,6 +899,8 @@ }, "merge-deep": { "version": "3.0.3", + "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", + "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", "dev": true, "requires": { "arr-union": "^3.1.0", @@ -4369,22 +908,10 @@ "kind-of": "^3.0.2" } }, - "micromatch": { - "version": "4.0.4", - "dev": true, - "requires": { - "braces": "^3.0.1", - "picomatch": "^2.2.3" - } - }, - "mime": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", - "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", - "dev": true - }, "minimatch": { - "version": "3.0.4", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -4392,6 +919,8 @@ }, "mixin-object": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", + "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", "dev": true, "requires": { "for-in": "^0.1.3", @@ -4400,95 +929,31 @@ "dependencies": { "for-in": { "version": "0.1.8", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", "dev": true } } }, - "mkdirp-classic": { - "version": "0.5.3", - "dev": true, - "peer": true - }, "ms": { - "version": "2.1.3", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, - "node-fetch": { - "version": "2.6.5", - "dev": true, - "peer": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "node-releases": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.2.tgz", - "integrity": "sha512-XxYDdcQ6eKqp/YjI+tb2C5WM2LgjnZrfYg4vgQt49EK268b6gYCHsBLrK2qvJo4FmCtqmKezb0WZFK4fkrZNsg==", - "dev": true - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true - }, - "object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - } - }, "once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "dev": true, "requires": { "wrappy": "1" } }, - "open": { - "version": "8.4.0", - "dev": true, - "requires": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" - } - }, - "p-limit": { - "version": "2.3.0", - "dev": true, - "peer": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "dev": true, - "peer": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "p-try": { - "version": "2.2.0", - "dev": true, - "peer": true - }, - "path-exists": { - "version": "4.0.0", - "dev": true, - "peer": true - }, "path-is-absolute": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true }, "path-key": { @@ -4497,186 +962,25 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", "dev": true }, - "pend": { - "version": "1.2.0", - "dev": true - }, - "picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", - "dev": true - }, - "picomatch": { - "version": "2.3.0", - "dev": true - }, - "pirates": { - "version": "4.0.4", - "dev": true - }, - "pixelmatch": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz", - "integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==", - "dev": true, - "requires": { - "pngjs": "^4.0.1" - }, - "dependencies": { - "pngjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz", - "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==", - "dev": true - } - } - }, - "pkg-dir": { - "version": "4.2.0", - "dev": true, - "peer": true, - "requires": { - "find-up": "^4.0.0" - } - }, "playwright": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.20.1.tgz", - "integrity": "sha512-d/25SFUk6Rkt3h+RU13T7h6o0UTCLKXKYJILWVlC+NmrE7Tvn3LlXxoREfFXVNFikRZWTV60WBCZKgNbj7RfrA==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.25.1.tgz", + "integrity": "sha512-kOlW7mllnQ70ALTwAor73q/FhdH9EEXLUqjdzqioYLcSVC4n4NBfDqeCikGuayFZrLECLkU6Hcbziy/szqTXSA==", "dev": true, "requires": { - "playwright-core": "1.20.1" + "playwright-core": "1.25.1" } }, "playwright-core": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.20.1.tgz", - "integrity": "sha512-A8ZsZ09gaSbxP0UijoLyzp3LJc0kWMxDooLPi+mm4/5iYnTbd6PF5nKjoFw1a7KwjZIEgdhJduah4BcUIh+IPA==", - "dev": true, - "requires": { - "colors": "1.4.0", - "commander": "8.3.0", - "debug": "4.3.3", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.0", - "jpeg-js": "0.4.3", - "mime": "3.0.0", - "pixelmatch": "5.2.1", - "pngjs": "6.0.0", - "progress": "2.0.3", - "proper-lockfile": "4.1.2", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "socks-proxy-agent": "6.1.1", - "stack-utils": "2.0.5", - "ws": "8.4.2", - "yauzl": "2.10.0", - "yazl": "2.5.1" - } - }, - "pngjs": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", - "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "version": "1.25.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.25.1.tgz", + "integrity": "sha512-lSvPCmA2n7LawD2Hw7gSCLScZ+vYRkhU8xH0AapMyzwN+ojoDqhkH/KIEUxwNu2PjPoE/fcE0wLAksdOhJ2O5g==", "dev": true }, - "pretty-format": { - "version": "27.4.2", - "dev": true, - "requires": { - "@jest/types": "^27.4.2", - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "5.2.0", - "dev": true - } - } - }, - "progress": { - "version": "2.0.3", - "dev": true - }, - "proper-lockfile": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", - "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==", - "dev": true, - "requires": { - "graceful-fs": "^4.2.4", - "retry": "^0.12.0", - "signal-exit": "^3.0.2" - } - }, - "proxy-from-env": { - "version": "1.1.0", - "dev": true - }, - "pump": { - "version": "3.0.0", - "dev": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "puppeteer": { - "version": "13.0.1", - "dev": true, - "peer": true, - "requires": { - "debug": "4.3.2", - "devtools-protocol": "0.0.937139", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.0", - "node-fetch": "2.6.5", - "pkg-dir": "4.2.0", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.2.3" - }, - "dependencies": { - "debug": { - "version": "4.3.2", - "dev": true, - "peer": true, - "requires": { - "ms": "2.1.2" - } - }, - "ms": { - "version": "2.1.2", - "dev": true, - "peer": true - }, - "ws": { - "version": "8.2.3", - "dev": true, - "peer": true, - "requires": {} - } - } - }, - "puppeteer-extra": { - "version": "3.2.3", - "dev": true, - "peer": true, - "requires": { - "@types/debug": "^4.1.0", - "@types/puppeteer": "*", - "debug": "^4.1.1", - "deepmerge": "^4.2.2" - } - }, "puppeteer-extra-plugin": { - "version": "3.2.0", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", + "integrity": "sha512-0uatQxzuVn8yegbrEwSk03wvwpMB5jNs7uTTnermylLZzoT+1rmAQaJXwlS3+vADUbw6ELNgNEHC7Skm0RqHbQ==", "dev": true, "requires": { "@types/debug": "^4.1.0", @@ -4685,53 +989,40 @@ } }, "puppeteer-extra-plugin-stealth": { - "version": "2.9.0", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.1.tgz", + "integrity": "sha512-n0wdC0Ilc9tk5L6FWLyd0P2gT8b2fp+2NuB+KB0oTSw3wXaZ0D6WNakjJsayJ4waGzIJFCUHkmK9zgx5NKMoFw==", "dev": true, "requires": { "debug": "^4.1.1", - "puppeteer-extra-plugin": "^3.2.0", - "puppeteer-extra-plugin-user-preferences": "^2.3.1" + "puppeteer-extra-plugin": "^3.2.2", + "puppeteer-extra-plugin-user-preferences": "^2.4.0" } }, "puppeteer-extra-plugin-user-data-dir": { - "version": "2.3.1", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.0.tgz", + "integrity": "sha512-qrhYPTGIqzL2hpeJ5DXjf8xMy5rt1UvcqSgpGTTOUOjIMz1ROWnKHjBoE9fNBJ4+ToRZbP8MzIDXWlEk/e1zJA==", "dev": true, "requires": { "debug": "^4.1.1", "fs-extra": "^10.0.0", - "puppeteer-extra-plugin": "^3.2.0" + "puppeteer-extra-plugin": "^3.2.2", + "rimraf": "^3.0.2" } }, "puppeteer-extra-plugin-user-preferences": { - "version": "2.3.1", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.0.tgz", + "integrity": "sha512-4XxMhMkJ+qqLsPY9ULF90qS9Bj1Qrwwgp1TY9zTdp1dJuy7QSgYE7xlyamq3cKrRuzg3QUOqygJo52sVeXSg5A==", "dev": true, "requires": { "debug": "^4.1.1", "deepmerge": "^4.2.2", - "puppeteer-extra-plugin": "^3.2.0", - "puppeteer-extra-plugin-user-data-dir": "^2.3.1" + "puppeteer-extra-plugin": "^3.2.2", + "puppeteer-extra-plugin-user-data-dir": "^2.4.0" } }, - "react-is": { - "version": "17.0.2", - "dev": true - }, - "readable-stream": { - "version": "3.6.0", - "dev": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "retry": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", - "dev": true - }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -4741,18 +1032,10 @@ "glob": "^7.1.3" } }, - "safe-buffer": { - "version": "5.1.2", - "dev": true - }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true - }, "shallow-clone": { "version": "0.1.2", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", + "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", "dev": true, "requires": { "is-extendable": "^0.1.1", @@ -4763,6 +1046,8 @@ "dependencies": { "kind-of": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", + "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", "dev": true, "requires": { "is-buffer": "^1.0.2" @@ -4770,6 +1055,8 @@ }, "lazy-cache": { "version": "0.2.7", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", + "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", "dev": true } } @@ -4789,175 +1076,18 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true - }, - "slash": { - "version": "3.0.0", - "dev": true - }, - "smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "dev": true - }, - "socks": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.6.2.tgz", - "integrity": "sha512-zDZhHhZRY9PxRruRMR7kMhnf3I8hDs4S3f9RecfnGxvcBHQcKcIH/oUcEWffsfl1XxdYlA7nnlGbbTvPz9D8gA==", - "dev": true, - "requires": { - "ip": "^1.1.5", - "smart-buffer": "^4.2.0" - } - }, - "socks-proxy-agent": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.1.1.tgz", - "integrity": "sha512-t8J0kG3csjA4g6FTbsMOWws+7R7vuRC8aQ/wy3/1OWmsgwA68zs/+cExQ0koSitUDXqhufF/YJr9wtNMZHw5Ew==", - "dev": true, - "requires": { - "agent-base": "^6.0.2", - "debug": "^4.3.1", - "socks": "^2.6.1" - } - }, - "source-map": { - "version": "0.5.7", - "dev": true - }, - "source-map-support": { - "version": "0.4.18", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, - "stack-utils": { - "version": "2.0.5", - "dev": true, - "requires": { - "escape-string-regexp": "^2.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "2.0.0", - "dev": true - } - } - }, "steno": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/steno/-/steno-2.1.0.tgz", "integrity": "sha512-mauOsiaqTNGFkWqIfwcm3y/fq+qKKaIWf1vf3ocOuTdco9XoHCO2AGF1gFYXuZFSWuP38Q8LBHBGJv2KnJSXyA==", "dev": true }, - "string_decoder": { - "version": "1.3.0", - "dev": true, - "peer": true, - "requires": { - "safe-buffer": "~5.2.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.2.1", - "dev": true, - "peer": true - } - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, - "tar-fs": { - "version": "2.1.1", - "dev": true, - "peer": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "tar-stream": { - "version": "2.2.0", - "dev": true, - "peer": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "through": { - "version": "2.3.8", - "dev": true, - "peer": true - }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true - }, - "to-regex-range": { - "version": "5.0.1", - "dev": true, - "requires": { - "is-number": "^7.0.0" - } - }, - "tr46": { - "version": "0.0.3", - "dev": true, - "peer": true - }, - "unbzip2-stream": { - "version": "1.4.3", - "dev": true, - "peer": true, - "requires": { - "buffer": "^5.2.1", - "through": "^2.3.8" - } - }, "universalify": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", "dev": true }, - "util-deprecate": { - "version": "1.0.2", - "dev": true, - "peer": true - }, - "webidl-conversions": { - "version": "3.0.1", - "dev": true, - "peer": true - }, - "whatwg-url": { - "version": "5.0.0", - "dev": true, - "peer": true, - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -4969,29 +1099,9 @@ }, "wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true - }, - "ws": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.4.2.tgz", - "integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==", - "dev": true, - "requires": {} - }, - "yauzl": { - "version": "2.10.0", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "yazl": { - "version": "2.5.1", - "dev": true, - "requires": { - "buffer-crc32": "~0.2.3" - } } } } diff --git a/package.json b/package.json index 8d24498..eab13f0 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,11 @@ "docker:epic-games": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name free-games-claimer free-games-claimer" }, "devDependencies": { - "@playwright/test": "^1.20.1", + "@playwright/test": "^1.25.1", "cross-env": "^7.0.3", "lowdb": "^3.0.0", - "playwright": "^1.20.1", - "puppeteer-extra-plugin-stealth": "^2.9.0" + "playwright": "^1.25.1", + "puppeteer-extra-plugin-stealth": "^2.11.1" }, "type": "module" } From 93d01bf5cf4263fd2ae5e55620ab7f4b874ebd32 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 2 Sep 2022 14:42:12 +0200 Subject: [PATCH 058/520] mention `playwright install chromium --with-deps` --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 446cf47..8b0c8cc 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ Claims free games on 3. Run `npm install && npx playwright install chromium` This downloads Chromium (343 MB) 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 chromium --with-deps`. ## Usage Both scripts start an automated Chromium instance, either with the browser GUI shown or hidden (*headless mode*). From 4fcbd6be6a5129ad45255d61c818b1407b6d453a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 11 Sep 2022 22:08:26 +0200 Subject: [PATCH 059/520] comment: use filenamify? --- util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util.js b/util.js index 62fc42e..266b7cc 100644 --- a/util.js +++ b/util.js @@ -20,7 +20,7 @@ export const jsonDb = async file => { } export const datetime = (d = new Date()) => d.toISOString(); -export const sanitizeFilename = s => s.replace(/[^a-z0-9_\-]/gi, '_'); +export const sanitizeFilename = s => s.replace(/[^a-z0-9_\-]/gi, '_'); // alternative: https://www.npmjs.com/package/filenamify // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 const newStealthContext = async (browser, contextOptions = {}, debug = false) => { From af374551b79fa1c6ce2dcce17a4afd1331540942 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 11 Sep 2022 22:10:06 +0200 Subject: [PATCH 060/520] recordVideo will record a .webm video for each page navigated --- epic-games.js | 1 + 1 file changed, 1 insertion(+) diff --git a/epic-games.js b/epic-games.js index 4bc9a82..5b92398 100644 --- a/epic-games.js +++ b/epic-games.js @@ -27,6 +27,7 @@ const context = await chromium.launchPersistentContext(dirs.browser, { viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO update if browser is updated! locale: "en-US", // ignore OS locale to be sure to have english text for locators + // recordVideo: { dir: 'data/videos/' }, // will record a .webm video for each page navigated args: [ // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.', but flags below don't work. '--disable-session-crashed-bubble', '--restore-last-session', From f109782a7a09a6b9aea974dc0d66fd9f3fde43fb Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 11 Sep 2022 22:11:08 +0200 Subject: [PATCH 061/520] epic-games: chromium args: --hide-crash-restore-bubble, no --enable-automation to hide info bar --- epic-games.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index 5b92398..f873180 100644 --- a/epic-games.js +++ b/epic-games.js @@ -28,10 +28,12 @@ const context = await chromium.launchPersistentContext(dirs.browser, { userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO update if browser is updated! locale: "en-US", // ignore OS locale to be sure to have english text for locators // recordVideo: { dir: 'data/videos/' }, // will record a .webm video for each page navigated - args: [ // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.', but flags below don't work. - '--disable-session-crashed-bubble', - '--restore-last-session', + args: [ // https://peter.sh/experiments/chromium-command-line-switches + // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' + // '--restore-last-session', // does not apply for crash/killed + '--hide-crash-restore-bubble', ], + ignoreDefaultArgs: ['--enable-automation'], // remove default arg that shows the info bar with 'Chrome is being controlled by automated test software.' }); // 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. From cbe789b08d3cb04e52e9bb975ad79b1a1274675e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 15 Sep 2022 16:40:18 +0200 Subject: [PATCH 062/520] temporarily fix #25 by waitUntil networkidle should wait for some element/attribute/event instead --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index f873180..0680e57 100644 --- a/epic-games.js +++ b/epic-games.js @@ -47,7 +47,7 @@ const page = context.pages().length ? context.pages()[0] : await context.newPage console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); try { - await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever + await page.goto(URL_CLAIM, { waitUntil: 'networkidle' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto - changed to 'networkidle' temporarily due to race https://github.com/vogler/free-games-claimer/issues/25 // Accept cookies to get rid of banner to save space on screen. Will only appear for a fresh context, so we don't await, but let it time out if it does not exist and catch the exception. clickIfExists by checking selector's count > 0 did not work. page.click('button:has-text("Accept All Cookies")').catch(_ => {}); // _ => console.info('Cookies already accepted') while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { // TODO also check alternative for signed-in state From c519ce0ce56d0a6b679425345949f0051f7bffbe Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 16 Sep 2022 15:26:05 +0200 Subject: [PATCH 063/520] use .nth() instead of :nth-match --- epic-games.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index 0680e57..e64553f 100644 --- a/epic-games.js +++ b/epic-games.js @@ -66,8 +66,8 @@ try { // const games = await page.$$(game_sel); // 'Element is not attached to the DOM' after navigation; had `for (const game of games) { await game.click(); ... } const n = run.n = await page.locator(game_sel).count(); console.log('Number of free games:', n); - for (let i = 1; i <= n; i++) { - await page.click(`:nth-match(${game_sel}, ${i})`); // navigates to page for game + for (let i = 0; i < n; i++) { + await page.locator(game_sel).nth(i).click(); // navigates to page for game const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); // barrier to block until page is loaded // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { @@ -119,7 +119,7 @@ try { } // await page.pause(); } - if (i < n) { // no need to go back if it's the last game + if (i < n-1) { // no need to go back if it's the last game await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); await page.waitForSelector(game_sel); } From 2791112fd611eefad2b5ad0a138aefe719bd3178 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 16 Sep 2022 15:33:05 +0200 Subject: [PATCH 064/520] sanitizeFilename -> filenamify, use for datetime --- epic-games.js | 4 ++-- prime-gaming.js | 8 ++++---- util.js | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/epic-games.js b/epic-games.js index e64553f..93654e5 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,6 +1,6 @@ import { chromium } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { dirs, jsonDb, datetime, stealth } from './util.js'; +import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector @@ -112,7 +112,7 @@ try { console.log('Claimed successfully!'); } catch (e) { console.log(e); - const p = path.resolve(dirs.screenshots, 'epic-games', `${datetime().replaceAll(':', '.')}.png`); + const p = path.resolve(dirs.screenshots, 'epic-games', `${filenamify(datetime())}.png`); await page.screenshot({ path: p, fullPage: true }); console.info('Saved a screenshot of hcaptcha challenge to', p); console.error('Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? diff --git a/prime-gaming.js b/prime-gaming.js index baa2b46..d30f6cf 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,6 +1,6 @@ import { chromium } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { dirs, jsonDb, datetime, sanitizeFilename, stealth } from './util.js'; +import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; const debug = process.env.PWDEBUG == '1'; // runs headful and opens https://playwright.dev/docs/inspector const show = process.argv.includes('show', 2); @@ -77,7 +77,7 @@ try { console.log('Current free game:', title); // const img = await (await card.$('img.tw-image')).getAttribute('src'); // console.log('Image:', img); - const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${sanitizeFilename(title)}.png`); + const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); await card.screenshot({ path: p }); await (await card.$('button:has-text("Claim game")')).click(); db.data.claimed.push({ title, time: datetime(), store: 'internal' }); @@ -123,7 +123,7 @@ try { } db.data.claimed.push({ title, time: datetime(), store, code, url: page.url() }); // save screenshot of potential code just in case - const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${sanitizeFilename(title)}.png`); + const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${filenamify(title)}.png`); await page.screenshot({ path: p, fullPage: true }); console.info('Saved a screenshot of page to', p); run.c_external++; @@ -132,7 +132,7 @@ try { await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); await page.click('button[data-type="Game"]'); } while (n); - const p = path.resolve(dirs.screenshots, 'prime-gaming', `${datetime().replaceAll(':', '.')}.png`); + const p = path.resolve(dirs.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); // await page.screenshot({ path: p, fullPage: true }); await page.locator(games_sel).screenshot({ path: p }); } catch(error) { diff --git a/util.js b/util.js index 266b7cc..1ac2deb 100644 --- a/util.js +++ b/util.js @@ -19,8 +19,8 @@ export const jsonDb = async file => { return db; } -export const datetime = (d = new Date()) => d.toISOString(); -export const sanitizeFilename = s => s.replace(/[^a-z0-9_\-]/gi, '_'); // alternative: https://www.npmjs.com/package/filenamify +export const datetime = (d = new Date()) => d.toISOString().replace('T', ' ').replace('Z', ''); +export const filenamify = s => s.replaceAll(':', '.').replace(/[^a-z0-9 _\-.]/gi, '_'); // alternative: https://www.npmjs.com/package/filenamify - On Unix-like systems, / is reserved. On Windows, <>:"/\|?* along with trailing periods are reserved. // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 const newStealthContext = async (browser, contextOptions = {}, debug = false) => { From 1dc3b3db6af5314b611eb3d9972d83fb451009a0 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 22 Sep 2022 16:25:21 +0200 Subject: [PATCH 065/520] resort to 3s timeout for now for #25 --- epic-games.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 93654e5..a1ceb3e 100644 --- a/epic-games.js +++ b/epic-games.js @@ -47,7 +47,7 @@ const page = context.pages().length ? context.pages()[0] : await context.newPage console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); try { - await page.goto(URL_CLAIM, { waitUntil: 'networkidle' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto - changed to 'networkidle' temporarily due to race https://github.com/vogler/free-games-claimer/issues/25 + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto // Accept cookies to get rid of banner to save space on screen. Will only appear for a fresh context, so we don't await, but let it time out if it does not exist and catch the exception. clickIfExists by checking selector's count > 0 did not work. page.click('button:has-text("Accept All Cookies")').catch(_ => {}); // _ => console.info('Cookies already accepted') while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { // TODO also check alternative for signed-in state @@ -61,14 +61,20 @@ try { } console.log('Signed in.'); // click on each banner with 'Free Now'. TODO just extract the URLs and go to them in the loop + // This json contains all promotions: https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions + // Could 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 const game_sel = 'span:text-is("Free Now")'; await page.waitForSelector(game_sel); // const games = await page.$$(game_sel); // 'Element is not attached to the DOM' after navigation; had `for (const game of games) { await game.click(); ... } const n = run.n = await page.locator(game_sel).count(); console.log('Number of free games:', n); + // fixes for https://github.com/vogler/free-games-claimer/issues/25 when URL of game is changed by JS: + // await page.waitForResponse(/freeGamesPromotions/); // not enough for (let i = 0; i < n; i++) { + await page.waitForTimeout(3000); await page.locator(game_sel).nth(i).click(); // navigates to page for game const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); // barrier to block until page is loaded + // await page.pause(); // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { console.log('This game contains mature content recommended only for ages 18+'); From bf647936eb3acba1da51a5236b31ac2b85a93cd3 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 26 Sep 2022 20:08:52 +0200 Subject: [PATCH 066/520] vscode format dode --- epic-games.js | 8 ++++---- prime-gaming.js | 10 +++++----- util.js | 8 ++++---- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/epic-games.js b/epic-games.js index a1ceb3e..450e317 100644 --- a/epic-games.js +++ b/epic-games.js @@ -49,7 +49,7 @@ console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto // Accept cookies to get rid of banner to save space on screen. Will only appear for a fresh context, so we don't await, but let it time out if it does not exist and catch the exception. clickIfExists by checking selector's count > 0 did not work. - page.click('button:has-text("Accept All Cookies")').catch(_ => {}); // _ => console.info('Cookies already accepted') + page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // _ => console.info('Cookies already accepted') while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { // TODO also check alternative for signed-in state console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page. If using docker, open http://localhost:6080"); context.setDefaultTimeout(0); // give user time to log in without timeout @@ -98,7 +98,7 @@ try { } // it then creates an iframe for the rest // await page.frame({ url: /.*store\/purchase.*/ }).click('button:has-text("Place Order")'); // not found because it does not wait for iframe - const iframe = page.frameLocator('#webPurchaseContainer iframe') + const iframe = page.frameLocator('#webPurchaseContainer iframe'); await iframe.locator('button:has-text("Place Order")').click(); // await page.pause(); // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 @@ -125,12 +125,12 @@ try { } // await page.pause(); } - if (i < n-1) { // no need to go back if it's the last game + if (i < n - 1) { // no need to go back if it's the last game await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); await page.waitForSelector(game_sel); } } -} catch(error) { +} catch (error) { console.error(error); run.error = error.toString(); } finally { diff --git a/prime-gaming.js b/prime-gaming.js index d30f6cf..5abf540 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -44,7 +44,7 @@ const clickIfExists = async selector => { }; try { - await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); // default 'load' takes forever + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever // need to wait for some elements to exist before checking if signed in or accepting cookies: await Promise.any(['button:has-text("Sign in")', '[data-a-target="user-dropdown-first-name-text"]'].map(s => page.waitForSelector(s))); await clickIfExists('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")'); // to not waste screen space in --debug @@ -57,7 +57,7 @@ try { } await page.click('button:has-text("Sign in")'); if (!debug) context.setDefaultTimeout(0); // give user time to log in without timeout - await page.waitForNavigation({url: 'https://gaming.amazon.com/home?signedIn=true'}); + await page.waitForNavigation({ url: 'https://gaming.amazon.com/home?signedIn=true' }); if (!debug) context.setDefaultTimeout(TIMEOUT); } console.log('Signed in.'); @@ -103,7 +103,7 @@ try { // 3 Full PC Games on Legacy Games const store = store_text.toLowerCase().replace(/.* on /, ''); console.log('External store:', store); - if(await page.locator('div:has-text("Link game account")').count()) { + if (await page.locator('div:has-text("Link game account")').count()) { console.error('Account linking is required to claim this offer!'); } else { // print code if there is one @@ -129,13 +129,13 @@ try { run.c_external++; } // await page.pause(); - await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); await page.click('button[data-type="Game"]'); } while (n); const p = path.resolve(dirs.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); // await page.screenshot({ path: p, fullPage: true }); await page.locator(games_sel).screenshot({ path: p }); -} catch(error) { +} catch (error) { console.error(error); run.error = error.toString(); } finally { diff --git a/util.js b/util.js index 1ac2deb..e684a03 100644 --- a/util.js +++ b/util.js @@ -17,7 +17,7 @@ export const jsonDb = async file => { const db = new Low(new JSONFile(dataDir(file))); await db.read(); return db; -} +}; export const datetime = (d = new Date()) => d.toISOString().replace('T', ' ').replace('Z', ''); export const filenamify = s => s.replaceAll(':', '.').replace(/[^a-z0-9 _\-.]/gi, '_'); // alternative: https://www.npmjs.com/package/filenamify - On Unix-like systems, / is reserved. On Windows, <>:"/\|?* along with trailing periods are reserved. @@ -61,9 +61,9 @@ export const stealth = async (context) => { const stealth = { callbacks: [], async evaluateOnNewDocument(...args) { - this.callbacks.push({ cb: args[0], a: args[1] }) + this.callbacks.push({ cb: args[0], a: args[1] }); } - } + }; for (const e of enabledEvasions) { const evasion = await import(`puppeteer-extra-plugin-stealth/evasions/${e}/index.js`); evasion.default().onPageCreated(stealth); @@ -71,4 +71,4 @@ export const stealth = async (context) => { for (let evasion of stealth.callbacks) { await context.addInitScript(evasion.cb, evasion.a); } -} +}; From e5ae4b631689ff4f4d13a797ec94f65cd97c5ff1 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Sep 2022 16:26:24 +0200 Subject: [PATCH 067/520] epic-games: only save screenshot if none exists for game --- epic-games.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 450e317..e69977e 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,6 +1,7 @@ import { chromium } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; +import { existsSync } from 'fs'; const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector @@ -84,7 +85,8 @@ try { console.log('Current free game:', title); const title_url = page.url().split('/').pop(); const p = path.resolve(dirs.screenshots, 'epic-games', `${title_url}.png`); - await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... + if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... + continue; if (btnText.toLowerCase() == 'in library') { console.log('Already in library! Nothing to claim.'); } else { // GET From 69d771b38c9aeebb8e4850c6ea983aef535b6fb6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Sep 2022 16:28:06 +0200 Subject: [PATCH 068/520] log urlSlug, #25 --- epic-games.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index e69977e..b0c01dc 100644 --- a/epic-games.js +++ b/epic-games.js @@ -64,7 +64,7 @@ try { // click on each banner with 'Free Now'. TODO just extract the URLs and go to them in the loop // This json contains all promotions: https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions // Could 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 - const game_sel = 'span:text-is("Free Now")'; + const game_sel = 'a:has(span:text-is("Free Now"))'; await page.waitForSelector(game_sel); // const games = await page.$$(game_sel); // 'Element is not attached to the DOM' after navigation; had `for (const game of games) { await game.click(); ... } const n = run.n = await page.locator(game_sel).count(); @@ -72,6 +72,7 @@ try { // fixes for https://github.com/vogler/free-games-claimer/issues/25 when URL of game is changed by JS: // await page.waitForResponse(/freeGamesPromotions/); // not enough for (let i = 0; i < n; i++) { + console.log('urlSlug', await page.locator(game_sel).nth(i).getAttribute('href')); await page.waitForTimeout(3000); await page.locator(game_sel).nth(i).click(); // navigates to page for game const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); // barrier to block until page is loaded From 30451b5f410afa975b5303885847ec212e317ecd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Sep 2022 16:28:51 +0200 Subject: [PATCH 069/520] oops, remove debug contine --- epic-games.js | 1 - 1 file changed, 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index b0c01dc..ae698f6 100644 --- a/epic-games.js +++ b/epic-games.js @@ -87,7 +87,6 @@ try { const title_url = page.url().split('/').pop(); const p = path.resolve(dirs.screenshots, 'epic-games', `${title_url}.png`); if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... - continue; if (btnText.toLowerCase() == 'in library') { console.log('Already in library! Nothing to claim.'); } else { // GET From 2f17bcf4bfb5ddede5f958570914e54d04008fde Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Sep 2022 17:00:24 +0200 Subject: [PATCH 070/520] epic-games cleanup and make more readable --- epic-games.js | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/epic-games.js b/epic-games.js index ae698f6..087628e 100644 --- a/epic-games.js +++ b/epic-games.js @@ -49,8 +49,10 @@ console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto + // Accept cookies to get rid of banner to save space on screen. Will only appear for a fresh context, so we don't await, but let it time out if it does not exist and catch the exception. clickIfExists by checking selector's count > 0 did not work. page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // _ => console.info('Cookies already accepted') + while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { // TODO also check alternative for signed-in state console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page. If using docker, open http://localhost:6080"); context.setDefaultTimeout(0); // give user time to log in without timeout @@ -61,6 +63,7 @@ try { // process.exit(1); } console.log('Signed in.'); + // click on each banner with 'Free Now'. TODO just extract the URLs and go to them in the loop // This json contains all promotions: https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions // Could 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 @@ -69,63 +72,70 @@ try { // const games = await page.$$(game_sel); // 'Element is not attached to the DOM' after navigation; had `for (const game of games) { await game.click(); ... } const n = run.n = await page.locator(game_sel).count(); console.log('Number of free games:', n); - // fixes for https://github.com/vogler/free-games-claimer/issues/25 when URL of game is changed by JS: - // await page.waitForResponse(/freeGamesPromotions/); // not enough + + // https://github.com/vogler/free-games-claimer/issues/25 sometimes URL of game is changed by JS - in these cases we need to wait, but unclear for what: + // await page.waitForResponse(/freeGamesPromotions/); // not enough, needs to be evaluated + for (let i = 0; i < n; i++) { - console.log('urlSlug', await page.locator(game_sel).nth(i).getAttribute('href')); - await page.waitForTimeout(3000); + console.log('urlSlug', await page.locator(game_sel).nth(i).getAttribute('href')); // debug #25 + await page.waitForTimeout(3000); // preliminary fix for #25 await page.locator(game_sel).nth(i).click(); // navigates to page for game const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); // barrier to block until page is loaded - // await page.pause(); + // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { console.log('This game contains mature content recommended only for ages 18+'); await page.click('button:has-text("Continue")'); } + const title = await page.locator('h1 div').first().innerText(); - console.log('Current free game:', title); const title_url = page.url().split('/').pop(); - const p = path.resolve(dirs.screenshots, 'epic-games', `${title_url}.png`); - if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... + console.log('Current free game:', title, title_url); + if (btnText.toLowerCase() == 'in library') { console.log('Already in library! Nothing to claim.'); } else { // GET console.log('Not in library yet! Click GET.'); await page.click('[data-testid="purchase-cta-button"]'); + // click Continue if 'Device not supported. This product is not compatible with your current device.' await Promise.any(['button:has-text("Continue")', '#webPurchaseContainer iframe'].map(s => page.waitForSelector(s))); // wait for Continue xor iframe if (await page.locator('button:has-text("Continue")').count() > 0) { // console.log('Device not supported. This product is not compatible with your current device.'); await page.click('button:has-text("Continue")'); } - // it then creates an iframe for the rest - // await page.frame({ url: /.*store\/purchase.*/ }).click('button:has-text("Place Order")'); // not found because it does not wait for iframe + + // if (process.env.DRYRUN) continue; // TODO can't continue yet due to redirect at bottom + if (debug) await page.pause(); + + // it then creates an iframe for the purchase const iframe = page.frameLocator('#webPurchaseContainer iframe'); await iframe.locator('button:has-text("Place Order")').click(); - // await page.pause(); + // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 const btnAgree = iframe.locator('button:has-text("I Agree")'); try { await Promise.any([btnAgree.waitFor().then(() => btnAgree.click()), page.waitForSelector('text=Thank you for buying').then(_ => { })]); // EU: wait for agree button, non-EU: potentially done - // TODO check for hcaptcha - the following is even true when no captcha is shown... + + // TODO check for hcaptcha - the following is even true when no captcha challenge is shown... // if (await iframe.frameLocator('#talon_frame_checkout_free_prod').locator('text=Please complete a security check to continue').count() > 0) { // console.error('Encountered hcaptcha. Giving up :('); - // await page.pause(); - // process.exit(1); // } - // await page.waitForTimeout(3000); - await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again + + await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again = no-op db.data.claimed.push({ title, time: datetime(), url: page.url() }); run.c++; console.log('Claimed successfully!'); } catch (e) { console.log(e); - const p = path.resolve(dirs.screenshots, 'epic-games', `${filenamify(datetime())}.png`); + const p = path.resolve(dirs.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); await page.screenshot({ path: p, fullPage: true }); console.info('Saved a screenshot of hcaptcha challenge to', p); console.error('Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? } - // await page.pause(); + + const p = path.resolve(dirs.screenshots, 'epic-games', `${title_url}.png`); + if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... } if (i < n - 1) { // no need to go back if it's the last game await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); @@ -140,7 +150,5 @@ try { run.endTime = datetime(); db.data.runs.push(run); await db.write(); - - // await context.waitForEvent("close"); - await context.close(); } +await context.close(); From edb90fe9f846fe3e40dd38131d445eadee20a76a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Sep 2022 18:11:30 +0200 Subject: [PATCH 071/520] epig-games: goto href instead of clicking games, fixes #25, fixes #28 --- epic-games.js | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/epic-games.js b/epic-games.js index 087628e..e95a45e 100644 --- a/epic-games.js +++ b/epic-games.js @@ -64,22 +64,21 @@ try { } console.log('Signed in.'); - // click on each banner with 'Free Now'. TODO just extract the URLs and go to them in the loop - // This json contains all promotions: https://store-site-backend-static-ipv4.ak.epicgames.com/freeGamesPromotions - // Could 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 - const game_sel = 'a:has(span:text-is("Free Now"))'; - await page.waitForSelector(game_sel); - // const games = await page.$$(game_sel); // 'Element is not attached to the DOM' after navigation; had `for (const game of games) { await game.click(); ... } - const n = run.n = await page.locator(game_sel).count(); - console.log('Number of free games:', n); + // Detect free games + const game_loc = await page.locator('a:has(span:text-is("Free Now"))'); + await game_loc.last().waitFor(); + // clicking on `game_sel` sometimes led to a 404, see https://github.com/vogler/free-games-claimer/issues/25 + // 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 + // 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 urls = urlSlugs.map(s => 'https://store.epicgames.com' + s); + const n = run.n = await game_loc.count(); + // console.log('Number of free games:', n); + console.log('Free games:', urls); - // https://github.com/vogler/free-games-claimer/issues/25 sometimes URL of game is changed by JS - in these cases we need to wait, but unclear for what: - // await page.waitForResponse(/freeGamesPromotions/); // not enough, needs to be evaluated - - for (let i = 0; i < n; i++) { - console.log('urlSlug', await page.locator(game_sel).nth(i).getAttribute('href')); // debug #25 - await page.waitForTimeout(3000); // preliminary fix for #25 - await page.locator(game_sel).nth(i).click(); // navigates to page for game + for (const url of urls) { + await page.goto(url, { waitUntil: 'domcontentloaded' }); const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); // barrier to block until page is loaded // click Continue if 'This game contains mature content recommended only for ages 18+' @@ -105,7 +104,7 @@ try { await page.click('button:has-text("Continue")'); } - // if (process.env.DRYRUN) continue; // TODO can't continue yet due to redirect at bottom + if (process.env.DRYRUN) continue; if (debug) await page.pause(); // it then creates an iframe for the purchase @@ -137,10 +136,6 @@ try { const p = path.resolve(dirs.screenshots, 'epic-games', `${title_url}.png`); if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... } - if (i < n - 1) { // no need to go back if it's the last game - await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); - await page.waitForSelector(game_sel); - } } } catch (error) { console.error(error); From f949e8effd26577379430fe3c1158162ea56db87 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 30 Sep 2022 14:34:56 +0200 Subject: [PATCH 072/520] rm stealth dup. comments --- epic-games.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index e95a45e..94d7f53 100644 --- a/epic-games.js +++ b/epic-games.js @@ -38,8 +38,6 @@ const context = await chromium.launchPersistentContext(dirs.browser, { }); // 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. -// stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 -// https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth/evasions await stealth(context); if (!debug) context.setDefaultTimeout(TIMEOUT); From 777b00b3fb3780bcdd59194fd394a19fd96025ce Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 30 Sep 2022 17:02:41 +0200 Subject: [PATCH 073/520] log signed in user --- epic-games.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 94d7f53..327f600 100644 --- a/epic-games.js +++ b/epic-games.js @@ -60,7 +60,8 @@ try { context.setDefaultTimeout(TIMEOUT); // process.exit(1); } - console.log('Signed in.'); + const user = await page.locator('#user span').first().innerHTML(); + console.log(`Signed in as ${user}`); // Detect free games const game_loc = await page.locator('a:has(span:text-is("Free Now"))'); From 548ba67e1081b2a57c26069e8abb17b936744a2f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 30 Sep 2022 17:13:31 +0200 Subject: [PATCH 074/520] indent logging per game --- epic-games.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/epic-games.js b/epic-games.js index 327f600..ce9dbb6 100644 --- a/epic-games.js +++ b/epic-games.js @@ -26,7 +26,7 @@ const context = await chromium.launchPersistentContext(dirs.browser, { // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge headless: false, viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, - userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO update if browser is updated! + userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? locale: "en-US", // ignore OS locale to be sure to have english text for locators // recordVideo: { dir: 'data/videos/' }, // will record a .webm video for each page navigated args: [ // https://peter.sh/experiments/chromium-command-line-switches @@ -43,7 +43,7 @@ await stealth(context); if (!debug) context.setDefaultTimeout(TIMEOUT); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist -console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); +// console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto @@ -88,18 +88,18 @@ try { const title = await page.locator('h1 div').first().innerText(); const title_url = page.url().split('/').pop(); - console.log('Current free game:', title, title_url); + console.log('Current free game:', title); if (btnText.toLowerCase() == 'in library') { - console.log('Already in library! Nothing to claim.'); + console.log(' Already in library! Nothing to claim.'); } else { // GET - console.log('Not in library yet! Click GET.'); + console.log(' Not in library yet! Click GET.'); await page.click('[data-testid="purchase-cta-button"]'); - // click Continue if 'Device not supported. This product is not compatible with your current device.' + // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoidable by Windows userAgent? await Promise.any(['button:has-text("Continue")', '#webPurchaseContainer iframe'].map(s => page.waitForSelector(s))); // wait for Continue xor iframe if (await page.locator('button:has-text("Continue")').count() > 0) { - // console.log('Device not supported. This product is not compatible with your current device.'); + // console.log(' Device not supported. This product is not compatible with your current device.'); await page.click('button:has-text("Continue")'); } @@ -117,19 +117,19 @@ try { // TODO check for hcaptcha - the following is even true when no captcha challenge is shown... // if (await iframe.frameLocator('#talon_frame_checkout_free_prod').locator('text=Please complete a security check to continue').count() > 0) { - // console.error('Encountered hcaptcha. Giving up :('); + // console.error(' Encountered hcaptcha. Giving up :('); // } await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again = no-op db.data.claimed.push({ title, time: datetime(), url: page.url() }); run.c++; - console.log('Claimed successfully!'); + console.log(' Claimed successfully!'); } catch (e) { console.log(e); const p = path.resolve(dirs.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); await page.screenshot({ path: p, fullPage: true }); - console.info('Saved a screenshot of hcaptcha challenge to', p); - console.error('Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? + console.info(' Saved a screenshot of hcaptcha challenge to', p); + console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? } const p = path.resolve(dirs.screenshots, 'epic-games', `${title_url}.png`); From 61af4e35f62ed570507c2836d52b6eb5dc10f708 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 6 Oct 2022 14:20:50 +0200 Subject: [PATCH 075/520] util.datetimeLocal --- util.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/util.js b/util.js index e684a03..644382f 100644 --- a/util.js +++ b/util.js @@ -19,7 +19,10 @@ export const jsonDb = async file => { return db; }; +// date and time as UTC (no timezone offset) in nicely readable and sortable format, e.g., 2022-10-06 12:05:27.313 export const datetime = (d = new Date()) => d.toISOString().replace('T', ' ').replace('Z', ''); +// same as datetime() but for local timezone, e.g., UTC + 2h for the above in DE +export const datetimeLocal = (d = new Date()) => datetime(new Date(d.getTime() - new Date().getTimezoneOffset() * 60000)); export const filenamify = s => s.replaceAll(':', '.').replace(/[^a-z0-9 _\-.]/gi, '_'); // alternative: https://www.npmjs.com/package/filenamify - On Unix-like systems, / is reserved. On Windows, <>:"/\|?* along with trailing periods are reserved. // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 From ac758d39e45f6b8d9be455b5fff7d901350afc85 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 6 Oct 2022 14:53:07 +0200 Subject: [PATCH 076/520] epic-games: migrateDb: rm .runs, .claimed[] -> .[user][game_id], closes #27 If you'd like to keep the .runs data: `cp -a data/epic-games.{json, v1.json}` Objects also have insertion order for non-number strings, so there's not need for a list: https://stackoverflow.com/questions/5525795/does-javascript-guarantee-object-property-order --- epic-games.js | 41 ++++++++++++++++++++++------------------- util.js | 1 + 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/epic-games.js b/epic-games.js index ce9dbb6..f00a647 100644 --- a/epic-games.js +++ b/epic-games.js @@ -12,13 +12,16 @@ const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) - 80 || 1280; const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; const db = await jsonDb('epic-games.json'); -db.data ||= { claimed: [], runs: [] }; -const run = { - startTime: datetime(), - endTime: null, - n: null, // unclaimed games at beginning - c: 0, // claimed games at end -}; +const migrateDb = (user) => { + if (user in db.data || !('claimed' in db.data)) return; + db.data[user] = {}; + for (const e of db.data.claimed) { + const k = e.url.split('/').pop(); + db.data[user][k] = e; + } + delete db.data.claimed; + delete db.data.runs; +} // https://playwright.dev/docs/auth#multi-factor-authentication const context = await chromium.launchPersistentContext(dirs.browser, { @@ -62,6 +65,8 @@ try { } const user = await page.locator('#user span').first().innerHTML(); console.log(`Signed in as ${user}`); + migrateDb(user); // TODO remove this after some time since it will run fine without and people can still use this commit to adjust their data epic-games.json + db.data[user] ||= {}; // Detect free games const game_loc = await page.locator('a:has(span:text-is("Free Now"))'); @@ -72,8 +77,6 @@ try { // 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 urls = urlSlugs.map(s => 'https://store.epicgames.com' + s); - const n = run.n = await game_loc.count(); - // console.log('Number of free games:', n); console.log('Free games:', urls); for (const url of urls) { @@ -87,11 +90,14 @@ try { } const title = await page.locator('h1 div').first().innerText(); - const title_url = page.url().split('/').pop(); + const game_id = page.url().split('/').pop(); + db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! console.log('Current free game:', title); if (btnText.toLowerCase() == 'in library') { console.log(' Already in library! Nothing to claim.'); + db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed + if (db.data[user][game_id].status == 'failed') db.data[user][game_id].status = 'manual'; // was failed but now it's claimed } else { // GET console.log(' Not in library yet! Click GET.'); await page.click('[data-testid="purchase-cta-button"]'); @@ -121,28 +127,25 @@ try { // } await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again = no-op - db.data.claimed.push({ title, time: datetime(), url: page.url() }); - run.c++; + db.data[user][game_id].status = 'claimed'; + db.data[user][game_id].time = datetime(); // claimed time overwrites failed time console.log(' Claimed successfully!'); } catch (e) { console.log(e); const p = path.resolve(dirs.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); await page.screenshot({ path: p, fullPage: true }); + db.data[user][game_id].status = 'failed'; console.info(' Saved a screenshot of hcaptcha challenge to', p); console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? } - const p = path.resolve(dirs.screenshots, 'epic-games', `${title_url}.png`); + const p = path.resolve(dirs.screenshots, 'epic-games', `${game_id}.png`); if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... } } } catch (error) { - console.error(error); - run.error = error.toString(); + console.error(error); // .toString()? } finally { - // write out json db - run.endTime = datetime(); - db.data.runs.push(run); - await db.write(); + await db.write(); // write out json db } await context.close(); diff --git a/util.js b/util.js index 644382f..a8c5fc7 100644 --- a/util.js +++ b/util.js @@ -16,6 +16,7 @@ import { Low, JSONFile } from 'lowdb'; export const jsonDb = async file => { const db = new Low(new JSONFile(dataDir(file))); await db.read(); + db.data ||= {}; return db; }; From 32d432deb62c05fbbe9f743713574efc45181d17 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 6 Oct 2022 17:02:05 +0200 Subject: [PATCH 077/520] epic-games: Windows userAgent avoids 'Device not supported'-Continue? --- epic-games.js | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/epic-games.js b/epic-games.js index f00a647..8492878 100644 --- a/epic-games.js +++ b/epic-games.js @@ -29,7 +29,7 @@ const context = await chromium.launchPersistentContext(dirs.browser, { // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge headless: false, viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, - userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? + userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? locale: "en-US", // ignore OS locale to be sure to have english text for locators // recordVideo: { dir: 'data/videos/' }, // will record a .webm video for each page navigated args: [ // https://peter.sh/experiments/chromium-command-line-switches @@ -102,12 +102,8 @@ try { console.log(' Not in library yet! Click GET.'); await page.click('[data-testid="purchase-cta-button"]'); - // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoidable by Windows userAgent? - await Promise.any(['button:has-text("Continue")', '#webPurchaseContainer iframe'].map(s => page.waitForSelector(s))); // wait for Continue xor iframe - if (await page.locator('button:has-text("Continue")').count() > 0) { - // console.log(' Device not supported. This product is not compatible with your current device.'); - await page.click('button:has-text("Continue")'); - } + // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoided by Windows userAgent? + // page.click('button:has-text("Continue")').catch(_ => { }); if (process.env.DRYRUN) continue; if (debug) await page.pause(); From f28c465f8f6b50d000214b5b119f5d2db0f1c8bc Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 6 Oct 2022 20:18:27 +0200 Subject: [PATCH 078/520] eg: check for hcaptcha challenge --- epic-games.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/epic-games.js b/epic-games.js index 8492878..175b9e3 100644 --- a/epic-games.js +++ b/epic-games.js @@ -115,24 +115,28 @@ try { // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 const btnAgree = iframe.locator('button:has-text("I Agree")'); try { - await Promise.any([btnAgree.waitFor().then(() => btnAgree.click()), page.waitForSelector('text=Thank you for buying').then(_ => { })]); // EU: wait for agree button, non-EU: potentially done + context.setDefaultTimeout(100 * 1000); // give time to solve captcha, iframe goes blank after 60s? + await Promise.any([btnAgree.click(), page.waitForSelector('text=Thank you for buying').then(_ => { })]); // EU: wait for agree button, non-EU: potentially done - // TODO check for hcaptcha - the following is even true when no captcha challenge is shown... - // if (await iframe.frameLocator('#talon_frame_checkout_free_prod').locator('text=Please complete a security check to continue').count() > 0) { - // console.error(' Encountered hcaptcha. Giving up :('); - // } + const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); + await captcha.waitFor().then(async () => { + await page.waitForTimeout(2000); + const p = path.resolve(dirs.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); + await captcha.screenshot({ path: p }); + console.info(' Saved a screenshot of hcaptcha challenge to', p); + console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? + }); await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again = no-op db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed time console.log(' Claimed successfully!'); + context.setDefaultTimeout(TIMEOUT); } catch (e) { console.log(e); - const p = path.resolve(dirs.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); + const p = path.resolve(dirs.screenshots, 'epic-games', 'failed', `${game_id}_${filenamify(datetime())}.png`); await page.screenshot({ path: p, fullPage: true }); db.data[user][game_id].status = 'failed'; - console.info(' Saved a screenshot of hcaptcha challenge to', p); - console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? } const p = path.resolve(dirs.screenshots, 'epic-games', `${game_id}.png`); From d67fb59355b710d9c5f4812ce893efb86526fb9f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 6 Oct 2022 20:24:23 +0200 Subject: [PATCH 079/520] eg: use NopeCHA extension to solve hcaptcha challenges 20 credits refill every 24h, but should not be needed even if there are several games to claim every day. --- epic-games.js | 17 ++- nopecha/background.js | 1 + nopecha/hcaptcha.js | 1 + nopecha/hcaptcha_fast.js | 1 + nopecha/hcaptcha_language.js | 1 + nopecha/icon/128.png | Bin 0 -> 9225 bytes nopecha/icon/16.png | Bin 0 -> 1103 bytes nopecha/icon/32.png | Bin 0 -> 1928 bytes nopecha/icon/48.png | Bin 0 -> 2828 bytes nopecha/manifest.json | 1 + nopecha/popup.css | 289 +++++++++++++++++++++++++++++++++++ nopecha/popup.html | 149 ++++++++++++++++++ nopecha/popup.js | 1 + nopecha/recaptcha.js | 1 + nopecha/recaptcha_fast.js | 1 + nopecha/recaptcha_voice.js | 1 + nopecha/setup.js | 1 + nopecha/utils.js | 1 + 18 files changed, 461 insertions(+), 5 deletions(-) create mode 100644 nopecha/background.js create mode 100644 nopecha/hcaptcha.js create mode 100644 nopecha/hcaptcha_fast.js create mode 100644 nopecha/hcaptcha_language.js create mode 100644 nopecha/icon/128.png create mode 100644 nopecha/icon/16.png create mode 100644 nopecha/icon/32.png create mode 100644 nopecha/icon/48.png create mode 100644 nopecha/manifest.json create mode 100644 nopecha/popup.css create mode 100644 nopecha/popup.html create mode 100644 nopecha/popup.js create mode 100644 nopecha/recaptcha.js create mode 100644 nopecha/recaptcha_fast.js create mode 100644 nopecha/recaptcha_voice.js create mode 100644 nopecha/setup.js create mode 100644 nopecha/utils.js diff --git a/epic-games.js b/epic-games.js index 175b9e3..18f729c 100644 --- a/epic-games.js +++ b/epic-games.js @@ -23,6 +23,9 @@ const migrateDb = (user) => { delete db.data.runs; } +// https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 +const ext = path.resolve('nopecha'); + // https://playwright.dev/docs/auth#multi-factor-authentication const context = await chromium.launchPersistentContext(dirs.browser, { // chrome will not work in linux arm64, only chromium @@ -36,6 +39,8 @@ const context = await chromium.launchPersistentContext(dirs.browser, { // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' // '--restore-last-session', // does not apply for crash/killed '--hide-crash-restore-bubble', + `--disable-extensions-except=${ext}`, + `--load-extension=${ext}`, ], ignoreDefaultArgs: ['--enable-automation'], // remove default arg that shows the info bar with 'Chrome is being controlled by automated test software.' }); @@ -120,11 +125,12 @@ try { const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); await captcha.waitFor().then(async () => { - await page.waitForTimeout(2000); - const p = path.resolve(dirs.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); - await captcha.screenshot({ path: p }); - console.info(' Saved a screenshot of hcaptcha challenge to', p); - console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? + console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') + // await page.waitForTimeout(2000); + // const p = path.resolve(dirs.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); + // await captcha.screenshot({ path: p }); + // console.info(' Saved a screenshot of hcaptcha challenge to', p); + // console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? }); await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again = no-op @@ -134,6 +140,7 @@ try { context.setDefaultTimeout(TIMEOUT); } catch (e) { console.log(e); + console.error(' Failed to claim! Try again if NopeCHA timed out. Click the extension to see if you ran out of credits (refill after 24h). To avoid captchas try to get a new IP or set a cookie from https://www.hcaptcha.com/accessibility'); const p = path.resolve(dirs.screenshots, 'epic-games', 'failed', `${game_id}_${filenamify(datetime())}.png`); await page.screenshot({ path: p, fullPage: true }); db.data[user][game_id].status = 'failed'; diff --git a/nopecha/background.js b/nopecha/background.js new file mode 100644 index 0000000..df00fce --- /dev/null +++ b/nopecha/background.js @@ -0,0 +1 @@ +(()=>{function a(t){return JSON.parse(JSON.stringify(t))}class s{static cache={};static async set({tab_id:t,data:{name:a,value:e,tab_specific:c}}){return c&&(a=t+"_"+a),s.cache[a]=e,s.cache[a]}static async get({tab_id:t,data:{name:a,tab_specific:e}}){return e&&(a=t+"_"+a),s.cache[a]}static async remove({tab_id:t,data:{name:a,tab_specific:e}}){e&&(a=t+"_"+a);e=s.cache[a];return delete s.cache[a],e}static async append({tab_id:t,data:{name:a,value:e,tab_specific:c}}){return(a=c?t+"_"+a:a)in s.cache||(s.cache[a]=[]),s.cache[a].push(e),s.cache[a]}static async empty({tab_id:t,data:{name:a,tab_specific:e}}){e&&(a=t+"_"+a);e=s.cache[a];return s.cache[a]=[],e}static async inc({tab_id:t,data:{name:a,tab_specific:e}}){return(a=e?t+"_"+a:a)in s.cache||(s.cache[a]=0),s.cache[a]++,s.cache[a]}static async dec({tab_id:t,data:{name:a,tab_specific:e}}){return(a=e?t+"_"+a:a)in s.cache||(s.cache[a]=0),s.cache[a]--,s.cache[a]}static async zero({tab_id:t,data:{name:a,tab_specific:e}}){return e&&(a=t+"_"+a),s.cache[a]=0,s.cache[a]}}class n{static reloads={};static _reload({tab_id:a}){return new Promise(t=>chrome.tabs.reload(a,{bypassCache:!0},t))}static async reload({tab_id:t,data:{delay:a,overwrite:e}={delay:0,overwrite:!0}}){a=parseInt(a);let c=n.reloads[t]?.delay-(Date.now()-n.reloads[t]?.start);return c=isNaN(c)||c<0?0:c,!!(e||0==c||a<=c)&&(clearTimeout(n.reloads[t]?.timer),n.reloads[t]={delay:a,start:Date.now(),timer:setTimeout(()=>n._reload({tab_id:t}),a)},!0)}static close({tab_id:a}){return new Promise(t=>chrome.tabs.remove(a,t))}static async open({data:{url:t}}){chrome.tabs.create({url:t})}static info({tab_id:t}){return new Promise(a=>{try{chrome.tabs.get(t,t=>a(t))}catch(t){a(!1)}})}}class e{static DEFAULT={version:2,hcaptcha_auto_solve:!0,hcaptcha_solve_delay:3e3,hcaptcha_auto_open:!0,hcaptcha_open_delay:1e3,recaptcha_auto_solve:!0,recaptcha_solve_delay:1e3,recaptcha_auto_open:!0,recaptcha_open_delay:1e3,recaptcha_solve_method:"image",debug:!1};static data={};static _save(){return new Promise(t=>chrome.storage.sync.set({settings:e.data},t))}static load(){return new Promise(a=>{chrome.storage.sync.get(["settings"],async({settings:t})=>{t?(e.data=t,e.data.version!==e.DEFAULT.version&&await e.reset()):await e.reset(),a()})})}static async get(){return e.data}static async set({data:{id:t,value:a}}){e.data[t]=a,await e._save()}static async reset(){e.data=a(e.DEFAULT);var t=chrome.runtime.getManifest();t.key&&(e.data.key=t.key),await e._save()}}class r{static inject({tab_id:t,data:{func:a,args:e}}){const c={target:{tabId:t,allFrames:!0},world:"MAIN",injectImmediately:!0,func:a,args:e};return new Promise(t=>chrome.scripting.executeScript(c,t))}}class t{static async reset({tab_id:t}){return await r.inject({tab_id:t,data:{func:function(){try{window.grecaptcha?.reset()}catch{}},args:[]}}),!0}static fetch({tab_id:t}){return new Promise(async a=>{const e="recaptcha_response",c=(await r.inject({tab_id:t,data:{func:function(t){window.grecaptcha&&window.postMessage({method:"set_cache",data:{name:t,value:window.grecaptcha.getResponse()}})},args:[e]}}),setInterval(async()=>{var t=await s.get({data:{name:e}});if(t)return clearInterval(c),await s.remove({data:{name:e}}),a(t)},1e3))})}}class i{static STATUS_URL="https://api.nopecha.com/status?v="+chrome.runtime.getManifest().version;static STATUS_CHECK_INTERVAL=1e4;static status="Online";static checking_status=!1;static async run_status_check(){return setInterval(()=>{i.check_status()},i.STATUS_CHECK_INTERVAL),!0}static async check_status(){if(i.checking_status)return!1;i.checking_status=!0;let t="Offline";try{const a=await fetch(i.STATUS_URL);t=await a.text()}catch{}return await i.set_status({data:{status:t}}),i.checking_status=!1,t}static async set_status({data:{status:c}}){if(i.status!==c){let t,a=[0,0,0,0],e="";if("Online"===(i.status=c))t={16:"icon/16.png",32:"icon/32.png",48:"icon/48.png",128:"icon/128.png"};else if("Offline"===c)t={16:"icon/16.png",32:"icon/32.png",48:"icon/48.png",128:"icon/128.png"},e="Off",a="#a44";else if("Slow"===c)t={16:"icon/16.png",32:"icon/32.png",48:"icon/48.png",128:"icon/128.png"},e="Slow",a="#f8d66d";else{if("Update Required"!==c)return!1;t={16:"icon/16.png",32:"icon/32.png",48:"icon/48.png",128:"icon/128.png"},e="Update",a="#f8d66d"}return chrome.action.setIcon({path:t}),chrome.action.setBadgeText({text:e}),chrome.action.setBadgeBackgroundColor({color:a}),!0}}static async get_status(){return await i.check_status(),i.status}static async check_plan({data:{key:t}}){if(i.checking_plan)return!1;i.checking_plan=!0;let a={plan:"free",credit:0};try{"undefined"===t&&(t="");const e=await fetch(i.STATUS_URL+"&k="+t);a=JSON.parse(await e.text())}catch{}return i.checking_plan=!1,a}static async get_plan({data:{key:t}}){return await i.check_plan({data:{key:t}})}}const o={set_cache:s.set,get_cache:s.get,remove_cache:s.remove,append_cache:s.append,empty_cache:s.empty,inc_cache:s.inc,dec_cache:s.dec,zero_cache:s.zero,fetch:class{static async fetch({data:{url:t,options:a}}){try{const e=await fetch(t,a);return await e.text()}catch{return null}}}.fetch,reload_tab:n.reload,close_tab:n.close,open_tab:n.open,info_tab:n.info,get_settings:e.get,set_settings:e.set,reset_settings:e.reset,reset_recaptcha:t.reset,fetch_recaptcha:t.fetch,translate:class d{static base_url="https://translate.googleapis.com/translate_a/single";static async translate({data:{from:t,to:a,text:e}}){let c=await fetch(d.base_url+`?client=gtx&sl=${t}&tl=${a}&dt=t&q=`+encodeURI(e)).then(t=>t.json());return c=c&&c[0]&&c[0][0]&&c[0].map(t=>t[0]).join("")}}.translate,get_server_plan:i.get_plan};(async()=>{chrome.declarativeNetRequest.updateDynamicRules({addRules:[{id:1,priority:1,action:{type:"redirect",redirect:{transform:{queryTransform:{addOrReplaceParams:[{key:"hl",value:"en-US"}]}}}},condition:{regexFilter:"^https://[^\\.]*\\.(google|recaptcha)\\.(com|net)/recaptcha",resourceTypes:["sub_frame","script"]}}],removeRuleIds:[1]}),await e.load(),chrome.runtime.onMessage.addListener((t,a,e)=>{const c=!["get_settings","set_settings","set_cache"].includes(t.method);return c,o[t.method]({tab_id:a?.tab?.id,data:t.data}).then(t=>{c;try{e(t)}catch(t){}}),!0})})()})(); \ No newline at end of file diff --git a/nopecha/hcaptcha.js b/nopecha/hcaptcha.js new file mode 100644 index 0000000..e35f1f2 --- /dev/null +++ b/nopecha/hcaptcha.js @@ -0,0 +1 @@ +(async()=>{class d{static time(){return Date.now||(Date.now=()=>(new Date).getTime()),Date.now()}static sleep(t=1e3){return new Promise(e=>setTimeout(e,t))}static async random_sleep(e,t){t=Math.floor(Math.random()*(t-e)+e);return d.sleep(t)}static pad(e){var t=2-String(e).length+1;return 0{try{chrome.runtime.sendMessage({method:e,data:a},t)}catch(e){t()}})}}class h{static async fetch(e,t){return p.exec("fetch",{url:e,options:t})}}class g{static INFERENCE_URL="https://api.nopecha.com";static MAX_WAIT_POST=60;static MAX_WAIT_GET=60;static ERRORS={UNKNOWN:9,INVALID_REQUEST:10,RATE_LIIMTED:11,BANNED_USER:12,NO_JOB:13,INCOMPLETE_JOB:14,INVALID_KEY:15,NO_CREDIT:16,UPDATE_REQUIRED:17};static async post({captcha_type:e,task:t,image_urls:a,grid:r,key:n}){for(var i=Date.now(),c=await p.exec("info_tab");!(Date.now()-i>1e3*g.MAX_WAIT_POST);){const u={type:e,task:t,image_urls:a,v:chrome.runtime.getManifest().version,key:n,url:c.url};r&&(u.grid=r);var o=await h.fetch(g.INFERENCE_URL,{method:"POST",body:JSON.stringify(u),headers:{"Content-Type":"application/json"}});try{var s=JSON.parse(o);if("error"in s){if(s.error===g.ERRORS.RATE_LIMITED){await d.sleep(2e3);continue}if(s.error===g.ERRORS.INVALID_KEY)break;if(s.error===g.ERRORS.NO_CREDIT)break;break}var l="id"in s?s.id:s.data;return await g.get({job_id:l,key:n})}catch(e){break}}return{job_id:null,clicks:null}}static async get({key:e,job_id:t}){for(var a=Date.now();!(Date.now()-a>1e3*g.MAX_WAIT_GET);){await d.sleep(500);var r=await h.fetch(g.INFERENCE_URL+`?id=${t}&key=`+e);try{var n=JSON.parse(r);if("error"in n){if(n.error!==g.ERRORS.INCOMPLETE_JOB)return{job_id:t,clicks:null};continue}return{job_id:t,clicks:n.data}}catch(e){break}}return{job_id:t,clicks:null}}}function u(e){const t=e?.style.background?.trim()?.match(/(?!^)".*?"/g);return t&&0!==t.length?t[0].replaceAll('"',""):null}async function y(){let e=document.querySelector("h2.prompt-text")?.innerText?.replace(/\s+/g," ")?.trim();if(!e)return null;var t={"0430":"a","0441":"c","0501":"d","0435":"e","04bb":"h","0456":"i","0458":"j","04cf":"l","03bf":"o","043e":"o","0440":"p","0455":"s","0445":"x","0443":"y","0065":"e","0069":"i","30fc":"一","571f":"士"};const a=[];for(const i of e){var r=function(e,t,a){for(;(""+e).length{let s=!1;const l=setInterval(async()=>{if(!s){s=!0;var e=await y();if(e){var t=document.querySelector(".challenge-example > .image > .image"),t=u(t);if(t&&""!==t){var a=document.querySelectorAll(".task-image");if(9!==a.length)s=!1;else{const n=[],i=[];for(const c of a){var r=c.querySelector("div.image");if(!r)return void(s=!1);r=u(r);if(!r||""===r)return void(s=!1);n.push(c),i.push(r)}a=JSON.stringify(i);if(f!==a)return f=a,clearInterval(l),s=!1,o({task:e,task_url:t,cells:n,urls:i});s=!1}}else s=!1}else s=!1}},n)});var n,i=d.time(),c=(await g.post({captcha_type:"hcaptcha",task:t,image_urls:r,key:e.key}))["clicks"];if(c){e=e.hcaptcha_solve_delay-(d.time()-i);0{let a=null,t=!1,r=!1;function n(e,t,r=!1){e&&(r||a!==e)&&(!0===t&&"false"===e.getAttribute("aria-pressed")||!1===t&&"true"===e.getAttribute("aria-pressed"))&&e.click()}document.addEventListener("mousedown",e=>{"false"===e?.target?.parentNode?.getAttribute("aria-pressed")?(t=!0,r=!0):"true"===e?.target?.parentNode?.getAttribute("aria-pressed")&&(t=!0,r=!1),a=e?.target?.parentNode}),document.addEventListener("mouseup",e=>{t=!1,a=null}),document.addEventListener("mousemove",e=>{t&&(a!==e?.target?.parentNode&&null!==a&&n(a,r,!0),n(e?.target?.parentNode,r))})})(); \ No newline at end of file diff --git a/nopecha/hcaptcha_language.js b/nopecha/hcaptcha_language.js new file mode 100644 index 0000000..a757835 --- /dev/null +++ b/nopecha/hcaptcha_language.js @@ -0,0 +1 @@ +(()=>{let e;function t(){var e=navigator.language.split("-")[0];for(const t of document.querySelectorAll('script[src*=".hcaptcha.com/1/api.js"]')){const r=new URL(t.src);"en"!==(r.searchParams.get("hl")||e)&&(r.searchParams.set("hl","en"),t.src=r.toString())}}e=new MutationObserver(t),setTimeout(()=>{t(),e.observe(document.head,{childList:!0})},0)})(); \ No newline at end of file diff --git a/nopecha/icon/128.png b/nopecha/icon/128.png new file mode 100644 index 0000000000000000000000000000000000000000..b002b52806d63c724f1b3c62b1042ceab0168278 GIT binary patch literal 9225 zcmV+kB=*~hP)EX>4Tx04R}tkv&MmKpe$iQ>9WW3U&~2$WWauh>8duLvQCFs9KfGs~Ejq!fI|*F6G!y^HfK|8swiZZ&T)ARrRYFvGNo*NG=L zZG-bZag>#0mH3=^#H0%nKXP61_>FVXWr1f#%}jcZI7%!Q+gNF1Rx&l>3F4Tl>69;I zJytnyan{OJ*1RWwVI;3FFL9mbAd*&ouk{0bF=;p3E1OmjD0&24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&L@3kMb(ob9#%03ZNKL_t(|+U=croLxtC=f9`w z-uGVbo4O@ymn_TjzRP&QyRn_H7;u7Pa7=&%l8}&)kW2f==8D(3fenFm-8eOkf z6cs=Xf`C9wRwGlhHX`@381qbEYFCGu=H=Nl?%iDhz;ULo=YMxfXx%*lJ--^ngNu!7 zjra&b(V(An+X7ic-N+VCm@0_MfvYeI6>6gN--4(~#u7zI#?tg_v8^jcQunl~-sdyf z#~aT5r}hc}CWw01{ow*r?0JX8eb)sHUKz$wwebOJJt{FsjEYBmCV4G&h`1C87F5%Z z17grod>25{r4Rv3@~0TQ3Pv1)pgxfwN&pfd6hRf$5N9(u8?Gow>qAPzSDMcMQcnc{ zW@R#Sll} z92OUZk<*$7 zowWX~x3a47ysu~l07prEFaPmnS-1O}Chj@k>VOD}H|b_olQ3HpFCK{^bZ`ham`8iM z(4hiSiZI?|)F)bBkNjnI)XFG=vx3WnNKFG$Q%6u&i_}yhHbjlZrQnlAdgjTHbK0d=o>UwuaV|7Ym>-($59ipIz&qYM?)#F!|?59TTD?Sj5u zf`L5NClaCTFO}d26+w-`qmav?O)c1#CQNM&Y66@Qj2Mgu6^BueYJk(4D9SZF;xecG zLEV{O*j53+LEX1Mmr|+g7Au|K$wvK6h{I?RFeql9rRE$_PY-TK2Vs8?#>FG*TNCY_ zGHaiUCt8kW(lP(^0#3k*C8%wJscqQidNc?z8l-kcfCAn`2oOojhGMqm_sLHENyCD_ zIU&?SaH0SpD(?Px*6sd;F$194RBRhmLR1CgBm6)=#jV>3`g+OvIAuy=)q6?AwGNAW z9p;rUiwICLBV%g%XQ&XWqTPFOeci;hjhNZ9(5AY?E>WM(!5|niy7zR7g(dk&J^}o7 zMFOPMv+{TTIOjXR5RgYj5gd4%h+GkD98=o46SsXUnK(*=G>J$SO{K%3_LG!pqv=7G zY}D8)9@J0>LNc>XBb+u3j3hs;;EW<hr(#vDX{`e&fske%_WHdoHie z%IgQbRGFxcYudJ6(ULJU-F~}OP`n7f7*pKSPZ)bd%!tT8amZtcvEJY@6s-`p))QpI z#M4NP$$iT$?C95(%X?eb*cMePEtd8{f730WxxeM;%>|7x?W zqvh@EI(nm3-+9C7Wfi4m6D=Qna&6NKYj*zLK>yH(Hgxq)Q4p^czppIPR0IP$zol_c zU0wCxoqy)EuYUFFGrCV00KEO-^|Ll?+WOeq-Mbf5SU$m00hrNT|58g!^9{@HdEK5# zHv&KK#QLVyYt}rnro#N=V;2D1y86!B)7kmh?>xD&Y0?3(ar2IkZR+knufm$+OFMc7 z&wFXjj*m?$0Iqp(rP&fwa( z{&z8|0AR9duoO+a{ODNr5i^wUcyn!W*Kg)rbdi?=MN`j{rR!gM*K>2;todq=ZZ2D+TVZ$eLx*c(~poVJM zLxa6&t}v&W15r+l5jVoDRlMMhO0$(JIs=-y`eJdH=Nltpcm4EJ|Q^zd<+(5@U*|@LPl9X4A zW)L3`7{M4*_B*BPbfy5UR%Xk zGpBNTYa`PdbJ)OBh=twVc{c6t<`+Bmuz9dh_VsciC;{FIVh0d!kf@QI%jX9B_Ra!! zj|%{{Bm@pbY+7hn0u*nOtln}_LumQc^X75Wh4VRoS{*a8mYgU?WTXzjqZB-Jl$4h{ z2Y6!TX8!Y~O>7@3P72eTvmtN0XaTpKKZkj(wX|g|HF%7qC29yxg&|cs3yv3e^|EAD zJO81r zy`5^(o@G=w4-bTR8ZwUgGpf0D=6T$G^&;+jdJW%xW)0DCN<7V3!(FdCllLw@lSOq| z!XU<~MKlJZ#8`r~I0RL40*yJ#8FTBnZO%E|bM+$bfASR`T)Ba`Tyqvs6}$vkEnsoD zBZ$!)9spq@o($+v*%$J(>KuRhmc`t%a4J)6oR<8Vksdt;8C%+yHgiw{KnPJTY6>{x z*7LYw(MFTG^l_h zq{hKDO(EyM=>o1?IGuZc^bFevhsuNj#cBy9z?lGN4v&CWH3D}n`Sokq+J0{~9w_@l zUeH|6*M93p?mVlFY2r~rtS0rnN$pHhJ>+HINAPKeQS$vj!C+`G%5C!-_{KdqaPesq zcQkS3X>I)dyRYXB3u|dGo~&lD2x1~EeoXr*<241Z0W4Mpi{h;Vfxx}Nw*efRfod9Y#K(nqWMm)(pEjnelzZry)o zQ$1h2>soG@T0<6t*Z_;5YAB_>c+?wIC8-|sf~pWhS}JamMoo~mixILIE@{s2<-4wC z@oCe_&isp}Hu3p)Ucu{H41u@%M3Okt?Tz<|odVzhRmG?#<%?>t&S2BZW`mG5hHIx) z@x{Ad&%)-0GD!iCRfiEzuH&hTr|w?1qVxXizBcf)|5krQ0MMuZq(0cPVo7$_vR}`+ z&Wyp0`mymF0`-}Id*6N)=T6NrI@eJ}@uo!9K_muWDq*&7qx6ec=zs2c;+I~=wC_TO zia6s?Tp}aVGI1oS!aut);1hQ&W>#HI*_c10zJ|}e<0{Ur%b-4ow^7nUP7SIS(I8P@ z14FQR8--uI%)pB0iB_$KExXV{fk-^wB)R@d0IE2N3GrMpCCC4G>lHMHVHswth-y+$ zs}Z5!o~hsS;s>Jk=a=M{{#E^}+5iK~KOsF^R{le6@3Jeb4&eoiFCC<_-m%jMZn%hR zXVp-p9+Bkns)@K)4bZ^?Ln~I&_4v=|S-%PA_MJD1F*MAXN$WLN(71RpuB9HMic5Et zLCC0bd0W6A-F7J-{@&7YyImr}M{c=@OQvKIB8<V;cOmRJHFO^ zZ(KB+x12qF#1&QJfB-9CnW2wS(=Or^(@?tY9c6OBo0g7Thl!Aba_L3Rg_7{EMf9rqN|Jp;#4p0F@OaEMg z-c5g56>S#llP(=bJtIBy%HZz3`>HdUnKPJSQ_d?f4ppVJdM&Fz`{hG2e}C%Rwv|^t z|5XNGd;xr_ht&ffqn4>b%w1P4!i;0?0=)Bz`7~Qe7_I?|1+O@bDXdt@>Mwrdkj&qo zdRK4cm!JIyO6%5BL{asqCaTP+Fgv5%^ZIkf$@c@BU}l_xQL3Y@LD;kIFAI-gL&3} z^8sA@R>UcB>M4N5AY44Vk;|qxAM>0qnbFFnv!`HF6@vjIfKhbQR@Q&>0ZRF!bK#18 zeXRfLzYy%+gNgyMBYYDPu0CTb^BT%k3OLL+m-4a%QT|s3dOPMG0D#O;=lipz_Oi9q z*`IDXYZf!JiF-8M&dH@E@;m-x3Hgp=%Wy9B4Y2w94S4{9~T; zTh5=u)TFp=pJfk7VTkSj`Vhtb!DAXX-?@j5|MP#*QjAA1Dya=*m>UW=oilsFT-h*c z#}>NZe*ge%VeopHpq48NTz2|&avI~sfGWm=S5LHkJG-Cy`MAdKUAda#hK(4ncr~eL z6|BOAGg`36paw*Qb7xJ#2v{BNOR0!citDzpch%}~&3V_)m!sRaCf-ci*UpL|BXIfY z(l%mJ=c!`KX~SG|NEXIxyp()G+L z;s8JzqyWy>4c?tEE4t28(R8@)%yU@zo*UkQmd#>;_qcXvudr%GPOEsu$*W-iy&C< z8Q4&s4yuD|H(=u!0~iT0#t;b9WrgPI+)&p%Z5Wq|wt+HbAzj9%Hj(zf(pkdQZuXT{r=-Lax59EYHZ+)j;;ZLi)%X z1`0=?FVI&iF*v$CunOW8A6Tjz$`iK_>uSN81X2p91q_i_3KO&;6~JZ*tLygxATxb# zm)NQvuuOy>xMQfuu0jkN69Z_BQ%`J#x_R@<@{HQ~^U=WIM3TgTwC8(&Vqy~{E}QYFI0Vo3 z?m@bb!OQ55VmiAAvEJdtgMw8-14HvAXOCYJ8$I6x^|x716LQ8EF~ZeEIJapmRt=p@4Ov*jFx() zrkd$*zYWc12~x(ZB;vm_iuuV)6L+~tWLvQ{Q@?)z0CLrJYopBcr>uAAh`C7Gqj&sx z{AEWs&u!_VBm%@FC)Sq8STb{`Gw-+Fg$<5wL5(rYyYqI!bIwdVuns4QM5qX_bPVv= z#$Cre=cOBV@_gq&5&%MyNFW{&$eg>7`S1KS%u#l5vSGmK@4gF}cY1o)QG}>XlU|hP zwsf<4Z-1HOZo2DDdY^(3O1YU&Wvat92LPb{_J46SE%hrBY%FR8AxdxB8Xv`~ zeEaG3?C~Bi2Akf;7o<~dxa@q+{Lp);p3-t;yQr?3Gv9kB^*6tsVvrqmL4*>KqZiKu zPp_rmj@_D$yz=0)>*+!<#DGVQ;EbXsr2fXMSondv$Aru=HEirXxK5%e3F&t5Q^usgrTc3Ztq5s(hmOOD)j(*S>@N-Y%kiksuRN z-8O}A#tckD6BvsRhW}@h7UorPc-Hhee&@c&c(Jp$ENgjDTQgs}=UOhQ2}g4NhwrfR zDflFg(5_xe+jo%f+Jh@bgqbY4);2P;XW$xZK_roQYLXNG4(kcPd}k*z7!%uo?xxGvSI<2A`nnswJ_cb2DBbFtzp$-i z$@{PG-%@mNtxSr82{jjhLT;9|;|BlRl%pYR|jHb3*|JSyLS?9cYVEWA;>~FYYn-Wq^dUES7{^H-3@p6v? zi5avdtV=Fk46pf2`hIcI)|} z)m!<*51(W0P?Gsuk|e9idcmqCAixeUrxnBu&pEw;)CyJv(I83P#-Qq0IS}#RzW;Nc z+S*xGzuQTM{`yO{4Nbk~gLd{=S7+Y*&4cQeLz><8=6{NTuM{7@`?@ADAs3n(XHN~kP|LU7Wyrj3BIz*L710LTs$lpA)j2Cv5^Oo6U{Ma-R z&RBFu%{Bl0s#&Io^}7bO%2i8dLQ2C{?C#>d|NK*a`|5M~jm2}BQDdm~Nk?HFcIozu zV-Fvecq|dZj>z%7RonRbPgl{ApZFTV$G7Za{XhPMci(Ux@3>$(Gebd4hVU?vyVxY> z2@#gHMdguaSM%kiuh3g6casf|7mHDd)E?G-J1hV~?L(w6a$U>t%_!x%zNNPm@wbn? zz(dci;~iI?$&HI7Wiwj2cwQUx+v;hm$q^Vs zte)PX5?eZZS-!E8=eKn*ILR$)R(0%U)gybU{c*tJxzo8|Ruc=_8fmP~U_^+$rz;<^ zad$V*Z0X?ntsNBIM4a*s-$P{6i?#xsF+?^TKLFzFwDm#X)FB)1s6q{D6g35`#u&=~ zG6m;&vV9j%w(mMn^q-(K5XU^WzMaR`w;%U88x+(d>Oqql1A`;BRZ*s{y7Ne&^QzQ* zZE6p+QYP355dA1W8)+aiEj&vYF#GXEiQH94ZdP6S$a2EJJ4A=Q0=n{g!b{ z0Dzv%tUnA4>s)EzW7#;aQ&XfQmdNM26{b+3Ng)NqH$CC(jO$}bD!EG8n*KuA_LTwC z_Q|pXb?;;EHDPH;FnJ<@w{`rFZH@66;^P8< zn95y%lTFr4VyoC(P+j4`7n;3)ICt5P;z5HGhDXa+E7gx$N^QAez?v){Ue`` z=^sq<2GaaKm)8C_)MTlx4iVK?MP$jr-k$tH$H-+Y%?(va¬f<0Z)?QoyS+&{tqZ zdpA#QYG+$te%$>APAUML-cZlm7N5=HX<6DVES%AbF~bd(54~fy?dqgIKMDY8c8Ecl zJEMkNCOqf}I_meH-a&SE4JApPX*(G&g7uEJmMqPU^@lFa)g%LSs1(y~2SzHKRU{ch zJ#|$fGp02jAk&Nv&pF9!@W91wmN#8Elb5#^dEnXSY4002^#O44tTx{N`tw+13ZO9= zO$sfHDqj4dA&~AgF9%m3l783^Mrlu{QC~~y6NejB!EimSU^JPBa)^11l(;9^$D<`n z(il3u@cCfxB8(){Bv6|>)T8UOhB%dFVZd3+`D@qF01ZO#1NwHI)% z4C0A!M5y>Q+eZ;i8YYc=(3B~H@iNjwcL=GGSl~Gw$iK;TI1wv+8DN!$8b79txZv7uIc4nSDiskU{5^&+fXs_ydUG89BClHr2oewc$`>f*Bh$C@YDgo zX}K&lVVDSNnSelul&zojag6Ycg@4kEFllaC6HZi{xJhyY10z&22ad!LOye4&$JE{Q zgp~lh3q|sk?4ehe2LfIcXM}tdv$s$@bpX&?irCR#B$fc1wp>xs1IoU}vvZ6;+0GMW z)SD+ENzs6e7q;h1^p#4d4gd`B!j@hJ!+>I#r)Vu<8CwyFo`kgnI`%Ir;B1M3(DKyg z?&Id4AGbKbk~N#yJOH^^2wj@WsO5{yoa{D+s1U@$ra|G+Uv4;V&pmDc=q?rc!Sao4 zmzh|U*ci&2Q-0DzfL+qccbBgtA0NM($Kx7}S-NE_Rk?t7E}qMDiYRf~omNq7Qr|X4 zOd3Wy=m`fo>KV42efk%Fcp#*~M`i!kwaFMFiXPUHPj6J&Pk3-_U z;ISA=L}J*h0rx-iB2TvOJg%@i?%}v4Yc|o*H^4hCJ(shaYRUN_0JTj3pIS+u4+JoF((U>++PA}@EX9rnHOvZB4jvzyv^ zZfhq_{baCW@-l*A?^&^R7b~{zD+3of_JC12%CQeOh8}^naYSS2iBnN`LY>IyI2WcI zi>hV#nUg6VQOOE^jZ*~x6#!J|WTr_i4z1ASlOLz|a70Gn2Y-ukVl-?pW@|8wpA{qqOoQCGRAsnq)-Vigcf772nMzTi@!E@PQd zEh}5I)gNAb+2SWY^|}^4CIGzQt3UbFi@SS2+UMNZpR4Tp6BY^yZxXdo7leBC+$r}h zoIUk@pSfYtLC;?gnSXrKSAP8N=R5lTcECAvL=Uad2}1ieW=U0c_6=QVip9M_>NS_dNJ)%Mk-$!`h8EZ5kM= zISNyuLX*1bU;Q@Ijs3-%?K}6}bi@F#$lJ#5>dFp3nWckMk6Qb-BL=|wz9BjBJM>oQ zXiS1l0|Roz0MH{j_NvfHgaE-GqSol305BDaf69TNzbHozfC{~Kr~-i3UU=Z}0H~<` zQ_mAPRIKix0I05X06pa>bBN154*J!W+U$PrK!r|j@vqEotUYo7)Q7rCq+%YNGKQWQ zS~7ms5d)y1s_MRJRk=JB5S$7I0BzZ@zp0x0j>_J9`+ZN}xTB-%cf%mKz2H5SaPY~- zb_lFts95@8U3K+mm;S-c&mNP(|Hlu%Z2O{U?JPs#K^# f6{=8$4nqGQ-IY)^404Gh00000NkvXXu0mjfq9Nm) literal 0 HcmV?d00001 diff --git a/nopecha/icon/16.png b/nopecha/icon/16.png new file mode 100644 index 0000000000000000000000000000000000000000..1dd1d1e68238bad31e98328ebbf5d0701ffe5774 GIT binary patch literal 1103 zcmV-V1hD&wP)EX>4Tx04R}tkv&MmKpe$iQ>9WW3U&~2$WWauh>8duLvQCFs9KfGs~Ejq!fI|*F6G!y^HfK|8swiZZ&T)ARrRYFvGNo*NG=L zZG-bZag>#0mH3=^#H0%nKXP61_>FVXWr1f#%}jcZI7%!Q+gNF1Rx&l>3F4Tl>69;I zJytnyan{OJ*1RWwVI;3FFL9mbAd*&ouk{0bF=;p3E1OmjD0&24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&L@3kV1Hw9$(I00K)%L_t(I%axN$NK|nY#(#J2 zy>p%M>gd>D%1kyoW)GstKrGD`ZhAlzlq3d05iUdu3IdU|$aYbbE;6Wu77<|&0)vp* zWVk4#2N>Fn`uOzV2Z%j1&_92vtJZO-v+g&(6Nx*x|c+Y;Xm@+1%|M zk0`!mDszPl2uh5x;*rkAF#u(;F=NU082LYqg9ljq8wiA-Z10hv2%y1{ON-lqF3U>> zU6yHauc5(VU0k~;__V4AK#AVSKT6zR4{*KOMO~?t@bnCI6*aiKJpjBsu@lRf7n9pX z#i*ZC1776#kNRlm>KBTENRtq86`*E|lTWP|Nz4avwVz?H!-^1)H~$@9*BxX%lU^*N_5%XHPQ$7V5ecVL7E({lhEF0^pGY8|sb!!-8~6No49S6rJi?;o@;WoBx< zFGxe-OGl)X7y(X}*f>3Z7yz28G8a^^X6ek975p1;Z_q~|5FrpvU^c465=oFil?{XEX>4Tx04R}tkv&MmKpe$iQ>9WW3U&~2$WWauh>8duLvQCFs9KfGs~Ejq!fI|*F6G!y^HfK|8swiZZ&T)ARrRYFvGNo*NG=L zZG-bZag>#0mH3=^#H0%nKXP61_>FVXWr1f#%}jcZI7%!Q+gNF1Rx&l>3F4Tl>69;I zJytnyan{OJ*1RWwVI;3FFL9mbAd*&ouk{0bF=;p3E1OmjD0&24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&L@3kNvIO5k|_00o3eL_t(o!_AgyY!qb}$A9na z>@nMx#V*~hQlZcmY!Kww0v3!hSR|2H}YPKxl|YqsC|; z8iRsy;tXVZu&d*iGuEG~qCANh^gKEGd({Ec!GBog`J4>$O zfvSo4y>6PL3Er+hMmVMq>0q``V`cRu#us`ylkDT;@NvFq`75{RWa>?~G{QhtmO!#@ zPOUK8ONG;)W4wOeBv#c-!zBb526HEmWzmc>p8cer51Kl1Y#%Bs=EWs*@p;^!8{l!V zaC#|KI}fpT@6p^;WD*nNb@OWr9^SJ}B(}^RuO$vOir{E1?yevhUJbx4MFo7edLgpC zlj9pUqeo8Rn?0QgD;}iXg34FF!agsLjZfcB&>=aoaVxQ38qi9|GGWaMl;Qw)zx55f zyW;?~hBo0djv(86^uD}`O2uWymnt%?4Gia!8?MFUP-w3E2tCp-X0d$->Dcu>B|b09 z$^%*3WfjGY_2tp=?QUYfH1tb%( z2-CLN+Y-&L)we{kq{Lr6C&#wvj)frLkGANL?lYEMuR5pR|Fzk$wcZKU=Wo8W|0MU` zIGM7=i|_=C(K|ZvRaB5)S;4N~PV&>4#6JYiB>4VtGc`9%Vbb$Y6Fb_3Rvf?|n$EGV zUS9rnkOE^kuBms;Sp2mBNbY=GbH#V;PbtC5b43U+%je~lyF!E}1W|<`nX&k;;RFwF zKSH9m^}8j^w4FP|0*^Cw~KitJ{r4{9O+FB+HVWJt9z8xm!3QJ z-8V>zuiv~!ZHly*BPfQHmyUBDdbBz-NQwbmY91pEGb1iMj?ZD{C@)Ki(E$8;y1hTFV}k&+M|+6&CMdfmfZOFF-Dfhbq>%MH53}dI zuE`Ok;RxJ1u?UYUbEfvdTag%I+{zpqiKfDWvf%LdgyA;2^}Zo*K;klHa5R~|^c1}J zdn@DpKKw-iOb7I31iDm7S_<*A5|EM(K=i*$NZJazbdazFrUl79Nw}q#jg76BPQb8j zp84T0&-`$BM7tuJf~wcPx~b>%$>k&6hI)6~^6J;VoS&1@>tEi!qA8x*$Q49+zP9qv zbG4fkfJh=ehbxRo(wLJ?fka+u|5#^zssI6(8tE~uD@wqpDv=2P1N;ruh9zVSLVJS% O0000EX>4Tx04R}tkv&MmKpe$iQ>9WW3U&~2$WWauh>8duLvQCFs9KfGs~Ejq!fI|*F6G!y^HfK|8swiZZ&T)ARrRYFvGNo*NG=L zZG-bZag>#0mH3=^#H0%nKXP61_>FVXWr1f#%}jcZI7%!Q+gNF1Rx&l>3F4Tl>69;I zJytnyan{OJ*1RWwVI;3FFL9mbAd*&ouk{0bF=;p3E1OmjD0&24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&L@3kNLSkTe7U00{_5L_t(&-rbmca8%VD$3N%X zyYFPlZUP|zLwJM`P~;&IEf-zZ5GfkImrBrQnWVB2T`LJDx02$u^C1CS`;Y*oU1)sntHdFO51`9B2^`Q&>u zT~_ZO4Ha1q8Xb+JI=YaZ9T4wDs@#cHLZH2V2o}NkAj0QCAh7XnOFII0c;~)Q_g@Fl zxqHnr5ACl@ZF#k2VIMw<>lP` zOwVZSPOk2-Gr?ynK=oXcfq#5jU)-KdPaZ3BA(Tu{zU28`r6U5+x$Wkv`{kBO!Pv4VUGL5e zy|H@EumB>j)jU+t`|)RUj@c8Z{Lo8~6wi8vM zXBN*4+`6N#4?yo~d&YQnGy_lbW>k_<%#EvHj?cGn}WDCy10Ko^)#`%rLuT&oYv$rLBvv>~-J?$vYi#P9 zYYuFt@`1IuCY7*eSrzMcHPUO@1Mbf=4Q{#oLQEk^oH)s$ho2xF3ir)>r7tE(>;y5&#=FQs%1P_o7^>FOj z7eOl~`ds{=YSJJd->;g)M6VMR9DDY~0l@>r4j-ds^Hvm^s*(WngZV=)X@N#s&5Ob> zt(Y!}bj}vo!`AKM(s4MYfz;6b;f|cwgby4-N8(5!sVpA+@Y%(FG+;-22_HC=(?`#T zyOCPsl(4Y0;0#JOjfl0(mI|3V6Kf=G&8d%cI&&{DArOWH4OR|rOevL-Y*HdbZe1l@ zPEIM_l&#K4t+tQ}=@@sx0bBTny?T377cpC*g&=rkbpS3O`U`0_a5)c zh;OftVC~|ZJ_@g>MhJ*0&4*20!|xw5U+KK^1JZZn#%9}5vQ>+rxxXP2XYb)QKu~<; z6$EM)4P2nJrct`;1`wLY&L|rj1~0%3^{q5^MG*ofueyo6*)s+vpk^`SuU-zop2IEF zN8&^4R3Wh(6SoFdZfZ3FlKzT2M0fmcljM-%?cpu^c$;=TW|E^&qW%Ls=2OS$Qc_3p_|6 zvxg7RTG0_t^Z4t#S+~Dw*jIn&#bw;HVm=c*4umlJ#%--Q+7sh}O&{~OqaB0l{qVvK z6zhGYMZxZ$yY%1u3k&I75x&)aO;q3hekJj!>i%6V7_I84?qnL+jRV*Fk=(PWB! zP3`>Qg9hp&i4jSO3ZI)ht1GyuY&>}`2ccM!9bdHY*hdX?r>&d|_VB_DOwjvLMqsn! zJA1yJQwya;{!0RVtrCVDN@|DF77xC65Z*g@&H}DJn&7T&pPb#5=Y~alVeFv$EQp!<@QUPM|7Z>r5STJOW7kok-1Fw7;NIobL&cc*~;_11U=3^ zO0#XvDb{qF5|z!0(i+RwXux5JZ1XB}mt`xI&I~yWiAJM(dKrdi4<;^$+XhqENf^Y@gz+xU1*`1Qd~%$$K5yAc(R>DDovit z$&`}ueOwcYCE0$cg>~|SZb&hJE>@%n>DI7fjey5q; zb4RyPuZI|sX!4!r=mFS%vWp%ivc1fq`?RCe%F={w$@b>X(F4$)O7rs0tIJGCjI1^nCqscD97Lp{kUi&xsJ3 zC_<4WDNA7*g2E%+eetH!Hr>$_8VDQ><1~$If;C;eDblvWVG4p@boI5@yP|PkIoM2H zI5zq;3Q*S@<##*k$J7RwvxFQ{NXD4?Tn;%bcPxGKwXzeD)C+OjTAEUtgwkJu97o@r zQs9*arqplGE)LxO#*eRR7*IkdB8lf(lj)_5QA;UJN?8DxH?;Mv2WkhDP|Q{}oF^7n zr(^8{kW~77iJx%4zKuMq4PnekYcUvr-!ww!3xGn03=P@`Ci*-p3f<0YZLP<2ElGh$ eEBzY(FZLgCZ@LL@c(k|x0000"], "js": ["recaptcha.js", "recaptcha_voice.js"], "all_frames": true, "run_at": "document_end"}, {"matches": [""], "js": ["hcaptcha_language.js"], "all_frames": true, "run_at": "document_end"}, {"matches": ["*://*.google.com/recaptcha/*", "*://*.recaptcha.net/recaptcha/*", "*://recaptcha.net/recaptcha/*"], "js": ["recaptcha_fast.js"], "all_frames": true, "run_at": "document_start"}, {"matches": ["*://nopecha.com/setup"], "js": ["setup.js"], "all_frames": true, "run_at": "document_end"}], "host_permissions": [""], "icons": {"16": "icon/16.png", "32": "icon/32.png", "48": "icon/48.png", "128": "icon/128.png"}} \ No newline at end of file diff --git a/nopecha/popup.css b/nopecha/popup.css new file mode 100644 index 0000000..e3f2991 --- /dev/null +++ b/nopecha/popup.css @@ -0,0 +1,289 @@ +:root { + --input_scale_x: 1; + --input_scale_y: 0.8; +} + +html * { + font-family: monospace, monospaSFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 12px; +} + +html, body { + margin: 0; + padding: 0; + + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +.hidden { + display: none !important; + opacity: 0; +} + +.light { + font-size: 0.9em; + opacity: 0.5; +} +.green { + /* color: #8cd47e; */ + color: #1a73e8; +} +.red { + color: #ff6961; +} +.yellow { + color: #ca1; +} +.clickable { + cursor: pointer; + transition: 250ms all; +} +.clickable:hover { + opacity: 0.8; +} + +#main { + padding: 8px 16px; +} + +#footer { + margin-top: 8px; + width: 100%; + text-align: center; + color: #999; + height: 20px; +} + +#manage { + width: 100%; + text-align: center; + background-color: #1a73e8; + color: #fff; + height: 20px; + line-height: 20px; + border-radius: 4px; + border: 1px solid #1a73e8; + padding: 8px; + font-size: 1.2em; + transition: 200ms all; +} +#manage:hover { + color: #1a73e8; + background-color: transparent; +} + +.vspace { + min-height: 8px; +} + +.settings_group { + width: 280px; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + padding: 6px 4px; +} +.settings_group.vertical { + flex-direction: column; +} +.settings_group > .label { + flex-grow: 1; + font-size: 1.2em; + line-height: calc(34px * var(--input_scale_y)); + padding-right: 16px; +} +.settings_group > .value { + font-size: 1.2em; + line-height: calc(34px * var(--input_scale_y)); +} +.settings_group > input { + border-radius: 0; +} +.settings_group > input[type="text"], +.settings_group > input[type="button"], +.settings_group > select { + font-size: 0.9em; + outline: none; + border: 1px solid #999; + width: calc(60px * var(--input_scale_x)); + height: calc(34px * var(--input_scale_y)); +} +.settings_group > input[type="button"] { + background-color: #f0f0f0; + cursor: pointer; + transition: 200ms all; +} +.settings_group > input[type="button"]:hover { + background-color: #fff; +} + +.switch { + position: relative; + display: inline-block; + width: calc(60px * var(--input_scale_x)); + height: calc(34px * var(--input_scale_y)); +} +.switch input { + opacity: 0; + width: 0; + height: 0; +} +.slider { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} +.slider:before { + position: absolute; + content: ""; + height: calc(26px * var(--input_scale_y)); + width: calc(26px * var(--input_scale_x)); + left: calc(4px * var(--input_scale_x)); + bottom: calc(4px * var(--input_scale_y)); + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} +input:checked + .slider { + background-color: #2196F3; +} +input:focus + .slider { + box-shadow: 0 0 1px #2196F3; +} +input:checked + .slider:before { + -webkit-transform: translateX(calc(26px * var(--input_scale_x))); + -ms-transform: translateX(calc(26px * var(--input_scale_x))); + transform: translateX(calc(26px * var(--input_scale_x))); +} + +#key { + padding: 4px; + border-radius: 2px; +} + +.loading { + display: inline-block; + position: relative; + width: 40px; + height: 100%; +} +.loading div { + position: absolute; + top: 8px; + width: 6px; + height: 6px; + border-radius: 50%; + background: #777; + animation-timing-function: cubic-bezier(0, 1, 1, 0); +} +.loading div:nth-child(1) { + left: 4px; + animation: loading1 0.6s infinite; +} +.loading div:nth-child(2) { + left: 4px; + animation: loading2 0.6s infinite; +} +.loading div:nth-child(3) { + left: 16px; + animation: loading2 0.6s infinite; +} +.loading div:nth-child(4) { + left: 28px; + animation: loading3 0.6s infinite; +} +@keyframes loading1 { + 0% { + transform: scale(0); + } + 100% { + transform: scale(1); + } +} +@keyframes loading3 { + 0% { + transform: scale(1); + } + 100% { + transform: scale(0); + } +} +@keyframes loading2 { + 0% { + transform: translate(0, 0); + } + 100% { + transform: translate(12px, 0); + } +} + +.tab_btn_row { + background-color: #efefef; + display: flex; + flex-direction: row; + flex-wrap: nowrap; +} +.tab_btn_row .tab_btn { + flex-grow: 1; + padding: 4px 8px; + border-top: 1px solid transparent; + border-left: 1px solid transparent; + border-right: 1px solid transparent; + border-bottom: 1px solid #ccc; + border-radius: 4px 4px 0 0; + text-align: center; + font-size: 1.2em; + transition: 200ms all; +} +.tab_btn:not(.active):hover { + background-color: #fafafa; +} +.tab_btn.active { + background-color: #fff; + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid transparent; +} + +.content { + margin: 6px 0; + padding: 4px 8px 0 8px; + border-top: 1px solid #ccc; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; +} +.tab_content.bordered { + padding: 16px 8px 8px 8px; + border-top: 1px solid transparent; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; + border-bottom: 1px solid #ccc; + border-radius: 0 0 4px 4px; +} + +.footer_group { + width: 296px; +} + +.warning_box { + border-color: #FCD62E; + border-radius: 0.25rem; + border-width: 0.125rem; + padding: 0 0.5rem; + margin: 0 4px; + background-color: #FEF9C3; + border-style: solid; +} diff --git a/nopecha/popup.html b/nopecha/popup.html new file mode 100644 index 0000000..5957b1c --- /dev/null +++ b/nopecha/popup.html @@ -0,0 +1,149 @@ + + + + + + + + +
+ + +
+
+
Manage Subscription
+
+ +
+ + +
+ + + +
+
Subscription
+
+
+
+
+ +
+
Credits
+
+
+
+
+ +
+
Refills
+
+
+
+
+
+ +
+ +
+
hCaptcha
+
reCAPTCHA
+
+ +
+ +
+
+
Auto Solve
+ +
+ +
+
Solve Delay (ms)
+ +
+ +
+
Auto Open
+ +
+ +
+
Open Delay (ms)
+ +
+
+ + + +
+ + + + +
+ + + \ No newline at end of file diff --git a/nopecha/popup.js b/nopecha/popup.js new file mode 100644 index 0000000..5688306 --- /dev/null +++ b/nopecha/popup.js @@ -0,0 +1 @@ +Date.now||(Date.now=function(){return(new Date).getTime()});class BG{static exec(t,n){return new Promise(e=>{try{chrome.runtime.sendMessage({method:t,data:n},e)}catch{e()}})}}class Util{static sleep(t){return new Promise(e=>setTimeout(e,t))}static pad_left(e,t,n){for(;(""+e).lengtht(s.id,s.checked));for(const c of document.querySelectorAll('.settings_group input[type="text"]'))c.addEventListener("input",()=>n(c.id,c.value));for(const l of document.querySelectorAll(".settings_group select"))l.addEventListener("change",()=>a(l.id,l.value));document.querySelector("#manage").addEventListener("click",async()=>{await BG.exec("open_tab",{url:"https://nopecha.com/manage"})}),document.querySelector("#footer").addEventListener("click",async()=>{await BG.exec("open_tab",{url:"https://nopecha.com/discord"})});let i=null;document.querySelector("#key").addEventListener("input",()=>{clearTimeout(i),i=setTimeout(check_plan,500)});for(const d of document.querySelectorAll(".tab_btn")){d.dataset.target;d.addEventListener("click",()=>{for(const e of document.querySelectorAll(".tab"))e.classList.add("hidden");for(const t of document.querySelectorAll(".tab_btn"))t.classList.remove("active");d.classList.add("active"),document.querySelector(d.dataset.target).classList.remove("hidden")})}}async function render_plan(){var t=await BG.exec("get_settings");if(t&&plan&&!rendering_server_plan){rendering_server_plan=!0;const a=document.querySelector("#plan"),i=document.querySelector("#credit"),r=document.querySelector("#refills"),s=document.querySelector("#incorrect_key"),c=document.querySelector("#ipbanned_warning");var n=Date.now()/1e3;let e=null;plan.lastreset&&plan.duration&&(e=Math.floor(Math.max(0,plan.duration-(n-plan.lastreset)))),a.innerHTML=plan.plan,"free"===plan.plan?(""!==t.key?s.classList.remove("hidden"):s.classList.add("hidden"),a.classList.remove("green"),a.classList.add("red")):(s.classList.add("hidden"),a.classList.remove("red"),a.classList.add("green")),plan.plan.includes("Banned")?c.classList.remove("hidden"):c.classList.add("hidden"),0===e?(i.classList.remove("green"),i.classList.remove("red"),i.innerHTML='
'):(i.innerHTML=plan.credit+" / "+plan.quota,0===plan.credit?(i.classList.remove("green"),i.classList.add("red")):(i.classList.remove("red"),i.classList.add("green"))),e?(n=Util.time_to_hms(e),r.innerHTML=""+n):r.innerHTML='
',0!==plan.duration&&0===e&&await check_plan(),rendering_server_plan=!1}}async function main(){await initialize_ui(),await check_plan(),await render_plan(),setInterval(render_plan,250)}document.addEventListener("DOMContentLoaded",main); \ No newline at end of file diff --git a/nopecha/recaptcha.js b/nopecha/recaptcha.js new file mode 100644 index 0000000..26c7ce1 --- /dev/null +++ b/nopecha/recaptcha.js @@ -0,0 +1 @@ +(async()=>{class _{static time(){return Date.now||(Date.now=()=>(new Date).getTime()),Date.now()}static sleep(t=1e3){return new Promise(e=>setTimeout(e,t))}static async random_sleep(e,t){t=Math.floor(Math.random()*(t-e)+e);return _.sleep(t)}static pad(e){var t=2-String(e).length+1;return 0{try{chrome.runtime.sendMessage({method:e,data:a},t)}catch(e){t()}})}}class d{static async fetch(e,t){return g.exec("fetch",{url:e,options:t})}}class m{static INFERENCE_URL="https://api.nopecha.com";static MAX_WAIT_POST=60;static MAX_WAIT_GET=60;static ERRORS={UNKNOWN:9,INVALID_REQUEST:10,RATE_LIIMTED:11,BANNED_USER:12,NO_JOB:13,INCOMPLETE_JOB:14,INVALID_KEY:15,NO_CREDIT:16,UPDATE_REQUIRED:17};static async post({captcha_type:e,task:t,image_urls:a,grid:r,key:c}){for(var i=Date.now(),n=await g.exec("info_tab");!(Date.now()-i>1e3*m.MAX_WAIT_POST);){const u={type:e,task:t,image_urls:a,v:chrome.runtime.getManifest().version,key:c,url:n.url};r&&(u.grid=r);var l=await d.fetch(m.INFERENCE_URL,{method:"POST",body:JSON.stringify(u),headers:{"Content-Type":"application/json"}});try{var s=JSON.parse(l);if("error"in s){if(s.error===m.ERRORS.RATE_LIMITED){await _.sleep(2e3);continue}if(s.error===m.ERRORS.INVALID_KEY)break;if(s.error===m.ERRORS.NO_CREDIT)break;break}var o="id"in s?s.id:s.data;return await m.get({job_id:o,key:c})}catch(e){break}}return{job_id:null,clicks:null}}static async get({key:e,job_id:t}){for(var a=Date.now();!(Date.now()-a>1e3*m.MAX_WAIT_GET);){await _.sleep(500);var r=await d.fetch(m.INFERENCE_URL+`?id=${t}&key=`+e);try{var c=JSON.parse(r);if("error"in c){if(c.error!==m.ERRORS.INCOMPLETE_JOB)return{job_id:t,clicks:null};continue}return{job_id:t,clicks:c.data}}catch(e){break}}return{job_id:t,clicks:null}}}function a(){var e="true"===document.querySelector(".recaptcha-checkbox")?.getAttribute("aria-checked"),t=document.querySelector("#recaptcha-verify-button")?.disabled;return e||t}function y(c=15e3){return new Promise(async e=>{for(var t=_.time();;){var a=document.querySelectorAll(".rc-imageselect-tile"),r=document.querySelectorAll(".rc-imageselect-dynamic-selected");if(0c)return e(!1);await _.sleep(100)}})}async function w(e){let t=null;if(!(t=1{let f=!1;const h=setInterval(async()=>{if(!f){f=!0;var r=document.querySelector(".rc-imageselect-instructions")?.innerText?.split("\n"),c=await w(r);if(c){var r=3===r.length,i=document.querySelectorAll("table tr td");if(9!==i.length&&16!==i.length)f=!1;else{const s=[],o=Array(i.length).fill(null);let e=null,t=!1,a=0;for(const u of i){var n=u?.querySelector("img");if(!n)return void(f=!1);var l=n?.src?.trim();if(!l||""===l)return void(f=!1);300<=n.naturalWidth?e=l:100==n.naturalWidth&&(o[a]=l,t=!0),s.push(u),a++}t&&(e=null);i=JSON.stringify([e,o]);if(v!==i)return v=i,clearInterval(h),f=!1,d({task:c,is_hard:r,cells:s,background_url:e,urls:o});f=!1}}else f=!1}},t)}),o=9==n.length?3:4;const h=[];let e,a=[];if(null===l){e="1x1";for(let e=0;e{let i=null,n=!1,s=!1;function a(e){let t=e;for(;t&&!t.classList?.contains("rc-imageselect-tile");)t=t.parentNode;return t}function t(e,t,n=!1){!e||!n&&i===e||(!0===t&&e.classList.contains("rc-imageselect-tileselected")||!1===t&&!e.classList.contains("rc-imageselect-tileselected"))&&e.click()}document.addEventListener("mousedown",e=>{const t=a(e?.target);t&&(s=t.classList.contains("rc-imageselect-tileselected")?n=!0:!(n=!0),i=t)}),document.addEventListener("mouseup",e=>{n=!1,i=null}),document.addEventListener("mousemove",e=>{e=a(e?.target);n&&(i!==e&&null!==i&&t(i,s,!0),t(e,s))});window.addEventListener("load",function(e){const t=document.body.appendChild(document.createElement("style")).sheet;t.insertRule(".rc-imageselect-table-33, .rc-imageselect-table-42, .rc-imageselect-table-44 {transition-duration: 0.5s !important}",0),t.insertRule(".rc-imageselect-tile {transition-duration: 2s !important}",1),t.insertRule(".rc-imageselect-dynamic-selected {transition-duration: 1s !important}",2),t.insertRule(".rc-imageselect-progress {transition-duration: 0.5s !important}",3),t.insertRule(".rc-image-tile-overlay {transition-duration: 0.5s !important}",4),t.insertRule("#rc-imageselect img {pointer-events: none !important}",5)})})(); \ No newline at end of file diff --git a/nopecha/recaptcha_voice.js b/nopecha/recaptcha_voice.js new file mode 100644 index 0000000..8f3f9b8 --- /dev/null +++ b/nopecha/recaptcha_voice.js @@ -0,0 +1 @@ +(async()=>{class r{static time(){return Date.now||(Date.now=()=>(new Date).getTime()),Date.now()}static sleep(t=1e3){return new Promise(e=>setTimeout(e,t))}static async random_sleep(e,t){t=Math.floor(Math.random()*(t-e)+e);return r.sleep(t)}static pad(e){var t=2-String(e).length+1;return 0{try{chrome.runtime.sendMessage({method:e,data:a},t)}catch(e){t()}})}}class o{static async fetch(e,t){return n.exec("fetch",{url:e,options:t})}}function i(){var e,t;if(!l())return e="true"===document.querySelector(".recaptcha-checkbox")?.getAttribute("aria-checked"),t=document.querySelector("#recaptcha-verify-button")?.disabled,e||t}function l(){return"Try again later"===document.querySelector(".rc-doscaptcha-header")?.innerText}async function e(e){i()||(await r.sleep(e.recaptcha_open_delay),document.querySelector("#recaptcha-anchor")?.click())}async function t(t){var a=await n.exec("get_cache",{name:"recaptcha_visible",tab_specific:!0});if(!0===a&&!i())if(l())await n.exec("reset_recaptcha");else{a=document.querySelector(".rc-audiochallenge-tdownload-link")?.href,a=(fetch(a),document.querySelector("#audio-source")?.src?.replace("recaptcha.net","google.com"));let e=document.querySelector("html")?.getAttribute("lang")?.trim();e&&0!==e.length||(e="en");var c=r.time(),a=await o.fetch("https://engageub.pythonanywhere.com",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"input="+encodeURIComponent(a)+"&lang="+e}),a=(document.querySelector("#audio-response").value=a,t.recaptcha_solve_delay-(r.time()-c));0{try{chrome.runtime.sendMessage({method:e,data:n},t)}catch(e){t()}})}}document.location.hash?(document.body.innerText="Loading...",BG.exec("set_settings",{id:"key",value:document.location.hash.substring(1)}).then(()=>document.body.innerText="Key set!")):document.body.innerText="Missing key. Please set the hash and reload the page.\nExample: https://nopecha.com/setup#sub_testkey1234"; \ No newline at end of file diff --git a/nopecha/utils.js b/nopecha/utils.js new file mode 100644 index 0000000..1590e0e --- /dev/null +++ b/nopecha/utils.js @@ -0,0 +1 @@ +"use strict";class Type{static _string_constructor="string".constructor;static _array_constructor=[].constructor;static _object_constructor={}.constructor;static of(e){return null===e?"null":void 0===e?"undefined":e.constructor===Type._string_constructor?"string":e.constructor===Type._array_constructor?"array":e.constructor===Type._object_constructor?"object":""}}class Logger{static debug=!0;static log(e=0){const t=new Array(...arguments).map(e=>["array","object"].includes(Type.of(e))?JSON.stringify(e,null,4):""+e);t.join(" ")}}class Time{static time(){return Date.now||(Date.now=()=>(new Date).getTime()),Date.now()}static sleep(t=1e3){return new Promise(e=>setTimeout(e,t))}static async random_sleep(e,t){t=Math.floor(Math.random()*(t-e)+e);return Time.sleep(t)}static pad(e){var t=2-String(e).length+1;return 0{try{chrome.runtime.sendMessage({method:e,data:r},t)}catch(e){t()}})}}class Net{static async fetch(e,t){return BG.exec("fetch",{url:e,options:t})}}class Image{static encode(t){return new Promise(r=>{if(null===t)return r(null);const e=new XMLHttpRequest;e.onload=()=>{const t=new FileReader;t.onloadend=()=>{let e=t.result;if(e.startsWith("data:text/html;base64,"))return r(null);e=e.replace("data:image/jpeg;base64,",""),r(e)},t.readAsDataURL(e.response)},e.onerror=()=>{r(null)},e.onreadystatechange=()=>{4==this.readyState&&200!=this.status&&r(null)},e.open("GET",t),e.responseType="blob",e.send()})}}class NopeCHA{static INFERENCE_URL="https://api.nopecha.com";static MAX_WAIT_POST=60;static MAX_WAIT_GET=60;static ERRORS={UNKNOWN:9,INVALID_REQUEST:10,RATE_LIIMTED:11,BANNED_USER:12,NO_JOB:13,INCOMPLETE_JOB:14,INVALID_KEY:15,NO_CREDIT:16,UPDATE_REQUIRED:17};static async post({captcha_type:e,task:t,image_urls:r,grid:a,key:n}){for(var o=Date.now(),s=await BG.exec("info_tab");!(Date.now()-o>1e3*NopeCHA.MAX_WAIT_POST);){const u={type:e,task:t,image_urls:r,v:chrome.runtime.getManifest().version,key:n,url:s.url};a&&(u.grid=a);var i=await Net.fetch(NopeCHA.INFERENCE_URL,{method:"POST",body:JSON.stringify(u),headers:{"Content-Type":"application/json"}});try{var c=JSON.parse(i);if("error"in c){if(c.error===NopeCHA.ERRORS.RATE_LIMITED){await Time.sleep(2e3);continue}if(c.error===NopeCHA.ERRORS.INVALID_KEY)break;if(c.error===NopeCHA.ERRORS.NO_CREDIT)break;break}var l="id"in c?c.id:c.data;return await NopeCHA.get({job_id:l,key:n})}catch(e){break}}return{job_id:null,clicks:null}}static async get({key:e,job_id:t}){for(var r=Date.now();!(Date.now()-r>1e3*NopeCHA.MAX_WAIT_GET);){await Time.sleep(500);var a=await Net.fetch(NopeCHA.INFERENCE_URL+`?id=${t}&key=`+e);try{var n=JSON.parse(a);if("error"in n){if(n.error!==NopeCHA.ERRORS.INCOMPLETE_JOB)return{job_id:t,clicks:null};continue}return{job_id:t,clicks:n.data}}catch(e){break}}return{job_id:t,clicks:null}}}function oep(a,n=1,e=100){return new Promise(t=>{const r=setInterval(()=>{var e=document.querySelectorAll(a);if(e.length===n)return clearInterval(r),t(1===n?e[0]:e)},e)})}export{Type,Logger,Time,BG,Net,Image,NopeCHA,oep}; \ No newline at end of file From c2cb2841212b794bb2eb8a4fa76cf03ae76173cb Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 6 Oct 2022 21:04:10 +0200 Subject: [PATCH 080/520] add store icons to readme --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8b0c8cc..bcc10cd 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # free-games-claimer Claims free games on -- [Epic Games Store](https://www.epicgames.com/store/free-games) -- [Amazon Prime Gaming](https://gaming.amazon.com) +- [Epic Games Store](https://www.epicgames.com/store/free-games) +- [Amazon Prime Gaming](https://gaming.amazon.com) +- [GOG](https://www.gog.com) - WIP - PRs welcome :) ## Setup From ed5f2d8486adf9e53bf09f704be383b1abed34cc Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 6 Oct 2022 21:07:39 +0200 Subject: [PATCH 081/520] Update README.md --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bcc10cd..c712e00 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@ # free-games-claimer -Claims free games on +Claims free games periodically on - [Epic Games Store](https://www.epicgames.com/store/free-games) - [Amazon Prime Gaming](https://gaming.amazon.com) - [GOG](https://www.gog.com) - WIP -- PRs welcome :) + +Pull requests welcome :) ## Setup ... should be the same on Windows/macOS/Linux: From 094662aefc5bd63e56f53b3565b0429846fed7b6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 6 Oct 2022 23:27:27 +0200 Subject: [PATCH 082/520] eg: write data/browser/cookies.json for easy access --- epic-games.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 18f729c..d7ae275 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,7 +1,7 @@ import { chromium } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; -import { existsSync } from 'fs'; +import { existsSync, writeFileSync } from 'fs'; const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector @@ -155,4 +155,5 @@ try { } finally { await db.write(); // write out json db } +await writeFileSync(path.resolve(dirs.browser, 'cookies.json'), JSON.stringify(await context.cookies())); await context.close(); From 2012c7e2f2ea5b2b3f7b25ff58cdaf2b27d1d8fe Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 6 Oct 2022 23:33:39 +0200 Subject: [PATCH 083/520] eg: set cookie instead of click 'Accept All Cookies' --- epic-games.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index d7ae275..ee2a2d9 100644 --- a/epic-games.js +++ b/epic-games.js @@ -54,10 +54,12 @@ const page = context.pages().length ? context.pages()[0] : await context.newPage // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); try { + await context.addCookies([{name: 'OptanonAlertBoxClosed', value: '2022-10-06T21:15:28.081Z', domain: '.epicgames.com', path: '/'}]); + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto - // Accept cookies to get rid of banner to save space on screen. Will only appear for a fresh context, so we don't await, but let it time out if it does not exist and catch the exception. clickIfExists by checking selector's count > 0 did not work. - page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // _ => console.info('Cookies already accepted') + // Accept cookies to get rid of banner to save space on screen. Clicking this did not always work since the message was animated in too slowly. + // page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // not needed anymore since we set the cookie above while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { // TODO also check alternative for signed-in state console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page. If using docker, open http://localhost:6080"); From d42b0b38addc7ebaa3da25f6c2da11509a8f04ca Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 16 Oct 2022 17:30:23 +0200 Subject: [PATCH 084/520] add docker-compose.yml, #31 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit works for first run of a container but then fails with: free-games-claimer | 16/10/2022 15:29:57 passing arg to libvncserver: -rfbport free-games-claimer | 16/10/2022 15:29:57 passing arg to libvncserver: 5900 free-games-claimer | 16/10/2022 15:29:57 passing arg to libvncserver: -passwd free-games-claimer | 16/10/2022 15:29:57 x11vnc version: 0.9.16 lastmod: 2019-01-05 pid: 8 free-games-claimer | 16/10/2022 15:29:57 XOpenDisplay(":1.0") failed. free-games-claimer | 16/10/2022 15:29:57 Trying again with XAUTHLOCALHOSTNAME=localhost ... free-games-claimer | free-games-claimer | 16/10/2022 15:29:57 *************************************** free-games-claimer | 16/10/2022 15:29:57 *** XOpenDisplay failed (:1.0) free-games-claimer | free-games-claimer | *** x11vnc was unable to open the X DISPLAY: ":1.0", it cannot continue. free-games-claimer | *** There may be "Xlib:" error messages above with details about the failure. ... free-games-claimer | browserType.launchPersistentContext: free-games-claimer | ╔════════════════════════════════════════════════════════════════════════════════════════════════╗ free-games-claimer | ║ Looks like you launched a headed browser without having a XServer running. ║ free-games-claimer | ║ Set either 'headless: true' or use 'xvfb-run ' before running Playwright. ║ free-games-claimer | ║ ║ free-games-claimer | ║ <3 Playwright Team ║ free-games-claimer | ╚════════════════════════════════════════════════════════════════════════════════════════════════╝ --- docker-compose.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 docker-compose.yml diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..f5b81e8 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,10 @@ +services: + free-games-claimer: + container_name: free-games-claimer + # image: free-games-claimer:latest + build: . + ports: + - '5900:5900' + - '6080:6080' + volumes: + - ./data:/fgc/data From fb23408522ffba7ad3443293d02eeff83a553f9a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 16:28:10 +0200 Subject: [PATCH 085/520] remove unused scripts: login, codegen "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", --- package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/package.json b/package.json index eab13f0..68029bb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,5 @@ { "scripts": { - "login": "npx playwright open --save-storage=auth.json https://www.epicgames.com/login", - "codegen": "npx playwright codegen --load-storage=auth.json https://www.epicgames.com/store/en-US/free-games", "docker:build": "docker build --tag free-games-claimer .", "docker:epic-games": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name free-games-claimer free-games-claimer" }, From 2ab4b8b8411d6b2c64a0f05b03e276e697e2117b Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 16:29:01 +0200 Subject: [PATCH 086/520] remove unused @playwright/test --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 68029bb..715fd0c 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,6 @@ "docker:epic-games": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name free-games-claimer free-games-claimer" }, "devDependencies": { - "@playwright/test": "^1.25.1", "cross-env": "^7.0.3", "lowdb": "^3.0.0", "playwright": "^1.25.1", From 1532726b2a843060b5913f8698ee058700f877d9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 16:33:53 +0200 Subject: [PATCH 087/520] update deps via ncu -u --- package-lock.json | 100 +++++++++++++++------------------------------- package.json | 4 +- 2 files changed, 34 insertions(+), 70 deletions(-) diff --git a/package-lock.json b/package-lock.json index 24842aa..b9647eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,29 +5,12 @@ "packages": { "": { "devDependencies": { - "@playwright/test": "^1.25.1", "cross-env": "^7.0.3", - "lowdb": "^3.0.0", - "playwright": "^1.25.1", + "lowdb": "^4.0.0", + "playwright": "^1.27.1", "puppeteer-extra-plugin-stealth": "^2.11.1" } }, - "node_modules/@playwright/test": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.25.1.tgz", - "integrity": "sha512-IJ4X0yOakXtwkhbnNzKkaIgXe6df7u3H3FnuhI9Jqh+CdO0e/lYQlDLYiyI9cnXK8E7UAppAWP+VqAv6VX7HQg==", - "dev": true, - "dependencies": { - "@types/node": "*", - "playwright-core": "1.25.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=14" - } - }, "node_modules/@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -43,11 +26,6 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", "dev": true }, - "node_modules/@types/node": { - "version": "17.0.5", - "dev": true, - "license": "MIT" - }, "node_modules/arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -312,15 +290,15 @@ } }, "node_modules/lowdb": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-3.0.0.tgz", - "integrity": "sha512-9KZRulmIcU8fZuWiaM0d5e2/nPnrFyXkeXVpqT+MJS+vgbgOf1EbtvgQmba8HwUFgDl1oeZR6XqEJnkJmQdKmg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-4.0.0.tgz", + "integrity": "sha512-wH4WPH2A+doyzd9mluhMQQsdrHNfOXJE5+C5N03QvH+8EoEMB1WWnjkfn1MkPtVdDrONRTojuNAWi6Es3rVtmA==", "dev": true, "dependencies": { - "steno": "^2.1.0" + "steno": "^3.0.0" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -408,13 +386,13 @@ } }, "node_modules/playwright": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.25.1.tgz", - "integrity": "sha512-kOlW7mllnQ70ALTwAor73q/FhdH9EEXLUqjdzqioYLcSVC4n4NBfDqeCikGuayFZrLECLkU6Hcbziy/szqTXSA==", + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.27.1.tgz", + "integrity": "sha512-xXYZ7m36yTtC+oFgqH0eTgullGztKSRMb4yuwLPl8IYSmgBM88QiB+3IWb1mRIC9/NNwcgbG0RwtFlg+EAFQHQ==", "dev": true, "hasInstallScript": true, "dependencies": { - "playwright-core": "1.25.1" + "playwright-core": "1.27.1" }, "bin": { "playwright": "cli.js" @@ -424,9 +402,9 @@ } }, "node_modules/playwright-core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.25.1.tgz", - "integrity": "sha512-lSvPCmA2n7LawD2Hw7gSCLScZ+vYRkhU8xH0AapMyzwN+ojoDqhkH/KIEUxwNu2PjPoE/fcE0wLAksdOhJ2O5g==", + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.27.1.tgz", + "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==", "dev": true, "bin": { "playwright": "cli.js" @@ -614,12 +592,12 @@ } }, "node_modules/steno": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/steno/-/steno-2.1.0.tgz", - "integrity": "sha512-mauOsiaqTNGFkWqIfwcm3y/fq+qKKaIWf1vf3ocOuTdco9XoHCO2AGF1gFYXuZFSWuP38Q8LBHBGJv2KnJSXyA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", + "integrity": "sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw==", "dev": true, "engines": { - "node": "^14.13.1 || >=16.0.0" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -657,16 +635,6 @@ } }, "dependencies": { - "@playwright/test": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.25.1.tgz", - "integrity": "sha512-IJ4X0yOakXtwkhbnNzKkaIgXe6df7u3H3FnuhI9Jqh+CdO0e/lYQlDLYiyI9cnXK8E7UAppAWP+VqAv6VX7HQg==", - "dev": true, - "requires": { - "@types/node": "*", - "playwright-core": "1.25.1" - } - }, "@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -682,10 +650,6 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", "dev": true }, - "@types/node": { - "version": "17.0.5", - "dev": true - }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -889,12 +853,12 @@ "dev": true }, "lowdb": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-3.0.0.tgz", - "integrity": "sha512-9KZRulmIcU8fZuWiaM0d5e2/nPnrFyXkeXVpqT+MJS+vgbgOf1EbtvgQmba8HwUFgDl1oeZR6XqEJnkJmQdKmg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-4.0.0.tgz", + "integrity": "sha512-wH4WPH2A+doyzd9mluhMQQsdrHNfOXJE5+C5N03QvH+8EoEMB1WWnjkfn1MkPtVdDrONRTojuNAWi6Es3rVtmA==", "dev": true, "requires": { - "steno": "^2.1.0" + "steno": "^3.0.0" } }, "merge-deep": { @@ -963,18 +927,18 @@ "dev": true }, "playwright": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.25.1.tgz", - "integrity": "sha512-kOlW7mllnQ70ALTwAor73q/FhdH9EEXLUqjdzqioYLcSVC4n4NBfDqeCikGuayFZrLECLkU6Hcbziy/szqTXSA==", + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.27.1.tgz", + "integrity": "sha512-xXYZ7m36yTtC+oFgqH0eTgullGztKSRMb4yuwLPl8IYSmgBM88QiB+3IWb1mRIC9/NNwcgbG0RwtFlg+EAFQHQ==", "dev": true, "requires": { - "playwright-core": "1.25.1" + "playwright-core": "1.27.1" } }, "playwright-core": { - "version": "1.25.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.25.1.tgz", - "integrity": "sha512-lSvPCmA2n7LawD2Hw7gSCLScZ+vYRkhU8xH0AapMyzwN+ojoDqhkH/KIEUxwNu2PjPoE/fcE0wLAksdOhJ2O5g==", + "version": "1.27.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.27.1.tgz", + "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==", "dev": true }, "puppeteer-extra-plugin": { @@ -1077,9 +1041,9 @@ "dev": true }, "steno": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/steno/-/steno-2.1.0.tgz", - "integrity": "sha512-mauOsiaqTNGFkWqIfwcm3y/fq+qKKaIWf1vf3ocOuTdco9XoHCO2AGF1gFYXuZFSWuP38Q8LBHBGJv2KnJSXyA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", + "integrity": "sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw==", "dev": true }, "universalify": { diff --git a/package.json b/package.json index 715fd0c..29965e2 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ }, "devDependencies": { "cross-env": "^7.0.3", - "lowdb": "^3.0.0", - "playwright": "^1.25.1", + "lowdb": "^4.0.0", + "playwright": "^1.27.1", "puppeteer-extra-plugin-stealth": "^2.11.1" }, "type": "module" From ac0ecc0f7acc77c155b9d7f0695f22791d8fa1ca Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 16:43:41 +0200 Subject: [PATCH 088/520] eg: ignoring --enable-automation now shows info bar about unsupported --no-sandbox --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index ee2a2d9..c27f310 100644 --- a/epic-games.js +++ b/epic-games.js @@ -42,7 +42,7 @@ const context = await chromium.launchPersistentContext(dirs.browser, { `--disable-extensions-except=${ext}`, `--load-extension=${ext}`, ], - ignoreDefaultArgs: ['--enable-automation'], // remove default arg that shows the info bar with 'Chrome is being controlled by automated test software.' + // ignoreDefaultArgs: ['--enable-automation'], // remove default arg that shows the info bar with 'Chrome is being controlled by automated test software.'. Since Chromeium 106 this leads to show another info bar with 'You are using an unsupported command-line flag: --no-sandbox. Stability and security will suffer.'. }); // 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. From f450d29bc358945567fcaa0ccf50ef3108d215a6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 18:07:20 +0200 Subject: [PATCH 089/520] eg: login from CLI, prompts for email, password, OTP --- epic-games.js | 25 ++++++++++++++++++++--- package-lock.json | 51 +++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 3 files changed, 74 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index c27f310..6de53be 100644 --- a/epic-games.js +++ b/epic-games.js @@ -3,6 +3,11 @@ import path from 'path'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; import { existsSync, writeFileSync } from 'fs'; +import prompts from 'prompts'; // alternatives: enquirer, inquirer +// import enquirer from 'enquirer'; const { prompt } = enquirer; +// single prompt that just returns the non-empty value instead of an object - why name things if there's just one? +const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; + const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; @@ -61,14 +66,28 @@ try { // Accept cookies to get rid of banner to save space on screen. Clicking this did not always work since the message was animated in too slowly. // page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // not needed anymore since we set the cookie above - while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { // TODO also check alternative for signed-in state + while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page. If using docker, open http://localhost:6080"); context.setDefaultTimeout(0); // give user time to log in without timeout await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); - // after login it just reloads the login page... + + const email = process.env.EMAIL || await prompt({message: 'Enter email'}); + const password = process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); + if (email && password) { + await page.click('text=Sign in with Epic Games'); + await page.fill('#email', email); + await page.fill('#password', password); + await page.click('button[type="submit"]'); + // TODO alternatively wait for redirectUrl + await page.waitForNavigation({ url: '**/id/login/mfa**'}).then(async () => { + console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); + const otp = await prompt({type: 'number', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); + await page.type('input[name="code-input-0"]', otp); + await page.click('button[type="submit"]'); + }); + } await page.waitForNavigation({ url: URL_CLAIM }); context.setDefaultTimeout(TIMEOUT); - // process.exit(1); } const user = await page.locator('#user span').first().innerHTML(); console.log(`Signed in as ${user}`); diff --git a/package-lock.json b/package-lock.json index b9647eb..93508d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "cross-env": "^7.0.3", "lowdb": "^4.0.0", "playwright": "^1.27.1", + "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" } }, @@ -280,6 +281,15 @@ "node": ">=0.10.0" } }, + "node_modules/kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -413,6 +423,19 @@ "node": ">=14" } }, + "node_modules/prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "dependencies": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/puppeteer-extra-plugin": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", @@ -591,6 +614,12 @@ "node": ">=8" } }, + "node_modules/sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, "node_modules/steno": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", @@ -846,6 +875,12 @@ "is-buffer": "^1.1.5" } }, + "kleur": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", + "dev": true + }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -941,6 +976,16 @@ "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==", "dev": true }, + "prompts": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", + "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", + "dev": true, + "requires": { + "kleur": "^3.0.3", + "sisteransi": "^1.0.5" + } + }, "puppeteer-extra-plugin": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", @@ -1040,6 +1085,12 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "dev": true }, + "sisteransi": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", + "dev": true + }, "steno": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", diff --git a/package.json b/package.json index 29965e2..4abd28a 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "cross-env": "^7.0.3", "lowdb": "^4.0.0", "playwright": "^1.27.1", + "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" }, "type": "module" From e3eb26d52726d3e05f750f4e39bf5764c46ccf9d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 19:55:10 +0200 Subject: [PATCH 090/520] docekr compose: shorter names for image and container --- docker-compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index f5b81e8..154cb57 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ services: free-games-claimer: - container_name: free-games-claimer - # image: free-games-claimer:latest + container_name: fgc # is printed in front of every output line + image: free-games-claimer # otherwise image name will be free-games-claimer-free-games-claimer build: . ports: - '5900:5900' From ef725064312a1850892cca912d7be2bb7bf6eba4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 19:55:42 +0200 Subject: [PATCH 091/520] docker: respect package-lock.json, not just package.json --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index b3b686f..9871e07 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,7 +57,7 @@ RUN apt-get update \ && ln -s $NOVNC_HOME/vnc_auto.html $NOVNC_HOME/index.html WORKDIR /fgc -COPY package.json . +COPY package*.json . # Install chromium & dependencies only RUN npm install \ && npx playwright install --with-deps chromium \ @@ -69,7 +69,7 @@ COPY . . # Shell scripts RUN dos2unix ./docker/*.sh RUN mv ./docker/entrypoint.sh /usr/local/bin/entrypoint \ - && chmod +x /usr/local/bin/entrypoint + && chmod +x /usr/local/bin/entrypoint ENTRYPOINT ["entrypoint"] CMD ["node", "epic-games.js"] From 1dbe239e48df7adc481ce3d1035e93051b66c04a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 19:56:24 +0200 Subject: [PATCH 092/520] rm -f /tmp/.X1-lock, fixes #31 --- docker/entrypoint.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 6de4434..2e40733 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -6,6 +6,11 @@ # https://bugs.chromium.org/p/chromium/issues/detail?id=367048 rm -f /fgc/data/browser/SingletonLock +# Remove X server display lock, fix for `docker compose up` which reuses container which made it fail after initial run, https://github.com/vogler/free-games-claimer/issues/31 +# echo $DISPLAY +# ls -l /tmp/.X11-unix/ +rm -f /tmp/.X1-lock + # 6000+SERVERNUM is the TCP port Xvfb is listening on: # SERVERNUM=$(echo "$DISPLAY" | sed 's/:\([0-9][0-9]*\).*/\1/') From 9f0e50afb66dbe8226fee82487756c6c17f73acc Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 20:09:12 +0200 Subject: [PATCH 093/520] remove VNC_PASSWORD Usually behind firewall and process is short-lived, except for when no longer logged in. --- Dockerfile | 1 - README.md | 2 +- docker/entrypoint.sh | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9871e07..2ebba26 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,6 @@ ENV SCREEN_HEIGHT 900 ENV SCREEN_DEPTH 24 # Configure VNC via environment variables: -ENV VNC_PASSWORD secret ENV VNC_PORT 5900 ENV NOVNC_PORT 6080 ENV NOVNC_HOME /usr/share/novnc diff --git a/README.md b/README.md index c712e00..437f516 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Alternatives: - [Install Docker](https://docs.docker.com/get-docker/) - `npm run docker:build` - `npm run docker:epic-games` - - When you need to login, go to http://localhost:6080 with password `secret` (you can also connect with another VNC client) + - When you need to login, go to http://localhost:6080 (you can also connect with any other VNC client on port 5900) ### Amazon Prime Gaming Run `node prime-gaming` diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 2e40733..13d8837 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -19,7 +19,7 @@ rm -f /tmp/.X1-lock # −screen NUM WxHxD creates the screen and sets its width, height, and depth Xvfb :1 -ac -screen 0 "${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}" >/dev/null 2>&1 & -x11vnc -display :1.0 -forever -shared -rfbport "${VNC_PORT:-5900}" -passwd "${VNC_PASSWORD:-secret}" -bg +x11vnc -display :1.0 -forever -shared -rfbport "${VNC_PORT:-5900}" -bg -nopw # -passwd "${VNC_PASSWORD}" websockify -D --web "$NOVNC_HOME" "$NOVNC_PORT" "localhost:$VNC_PORT" & DISPLAY=:1.0 export DISPLAY From a789034c513801d012d686fa1ca49eef492b0ce6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 20:45:44 +0200 Subject: [PATCH 094/520] clean up Dockerfile and move down config such that it does not trigger rebuild --- Dockerfile | 32 ++++++++++++++++---------------- docker/entrypoint.sh | 2 +- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2ebba26..5fd0b7e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,24 +3,11 @@ FROM ubuntu:focal ARG DEBIAN_FRONTEND=noninteractive -# Configure Xvfb via environment variables: -ENV SCREEN_WIDTH 1440 -ENV SCREEN_HEIGHT 900 -ENV SCREEN_DEPTH 24 - -# Configure VNC via environment variables: -ENV VNC_PORT 5900 -ENV NOVNC_PORT 6080 -ENV NOVNC_HOME /usr/share/novnc -EXPOSE 5900 -EXPOSE 6080 - # Playwright ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD true # === INSTALL Node.js === - # Taken from https://github.com/microsoft/playwright/blob/main/utils/docker/Dockerfile.focal RUN apt-get update && \ # Install node16 @@ -35,7 +22,6 @@ RUN apt-get update && \ # Create the pwuser adduser pwuser - # === Install the base requirements to run and debug webdriver implementations === RUN apt-get update \ && apt-get install --no-install-recommends --no-install-suggests -y \ @@ -52,11 +38,13 @@ RUN apt-get update \ /usr/share/doc/* \ /var/cache/* \ /var/lib/apt/lists/* \ - /var/tmp/* \ - && ln -s $NOVNC_HOME/vnc_auto.html $NOVNC_HOME/index.html + /var/tmp/* + +RUN ln -s /usr/share/novnc/vnc_auto.html /usr/share/novnc/index.html WORKDIR /fgc COPY package*.json . + # Install chromium & dependencies only RUN npm install \ && npx playwright install --with-deps chromium \ @@ -66,9 +54,21 @@ RUN npm install \ COPY . . # Shell scripts +# On windows, git might be configured to check out dos/CRLF line endings, so we convert. RUN dos2unix ./docker/*.sh RUN mv ./docker/entrypoint.sh /usr/local/bin/entrypoint \ && chmod +x /usr/local/bin/entrypoint +# Configure VNC via environment variables: +ENV VNC_PORT 5900 +ENV NOVNC_PORT 6080 +EXPOSE 5900 +EXPOSE 6080 + +# Configure Xvfb via environment variables: +ENV SCREEN_WIDTH 1440 +ENV SCREEN_HEIGHT 900 +ENV SCREEN_DEPTH 24 + ENTRYPOINT ["entrypoint"] CMD ["node", "epic-games.js"] diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 13d8837..5508a96 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -20,7 +20,7 @@ rm -f /tmp/.X1-lock Xvfb :1 -ac -screen 0 "${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}" >/dev/null 2>&1 & x11vnc -display :1.0 -forever -shared -rfbport "${VNC_PORT:-5900}" -bg -nopw # -passwd "${VNC_PASSWORD}" -websockify -D --web "$NOVNC_HOME" "$NOVNC_PORT" "localhost:$VNC_PORT" & +websockify -D --web "/usr/share/novnc/" "$NOVNC_PORT" "localhost:$VNC_PORT" & DISPLAY=:1.0 export DISPLAY exec tini -g -- "$@" From fbc33ffcaf64ffc09175eafd0e998798432fcc99 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 21:20:15 +0200 Subject: [PATCH 095/520] same resolution for docker as for browser --- Dockerfile | 4 ++-- epic-games.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5fd0b7e..cd652f8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -66,8 +66,8 @@ EXPOSE 5900 EXPOSE 6080 # Configure Xvfb via environment variables: -ENV SCREEN_WIDTH 1440 -ENV SCREEN_HEIGHT 900 +ENV SCREEN_WIDTH 1280 +ENV SCREEN_HEIGHT 1280 ENV SCREEN_DEPTH 24 ENTRYPOINT ["entrypoint"] diff --git a/epic-games.js b/epic-games.js index 6de53be..30cec9d 100644 --- a/epic-games.js +++ b/epic-games.js @@ -13,7 +13,7 @@ const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https:/ const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; const TIMEOUT = 20 * 1000; // 20s, default is 30s -const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) - 80 || 1280; +const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) || 1280; const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; const db = await jsonDb('epic-games.json'); From 4e4410f5d6085b9b413b728566b1001399b1244a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 21:21:14 +0200 Subject: [PATCH 096/520] clean up entrypoint.sh, quiet noisy x11vnc, echo status --- docker/entrypoint.sh | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 5508a96..de596e4 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -18,9 +18,12 @@ rm -f /tmp/.X1-lock # -ac disables host-based access control mechanisms # −screen NUM WxHxD creates the screen and sets its width, height, and depth -Xvfb :1 -ac -screen 0 "${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}" >/dev/null 2>&1 & -x11vnc -display :1.0 -forever -shared -rfbport "${VNC_PORT:-5900}" -bg -nopw # -passwd "${VNC_PASSWORD}" -websockify -D --web "/usr/share/novnc/" "$NOVNC_PORT" "localhost:$VNC_PORT" & -DISPLAY=:1.0 -export DISPLAY +export DISPLAY=:1 # need to export this, otherwise playwright complains with 'Looks like you launched a headed browser without having a XServer running.' +Xvfb $DISPLAY -ac -screen 0 "${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}" & +echo "Xvfb display server created screen with resolution ${SCREEN_WIDTH}x${SCREEN_HEIGHT}." +x11vnc -display $DISPLAY -forever -shared -rfbport $VNC_PORT -bg -nopw 2>/dev/null 1>&2 # -passwd "${VNC_PASSWORD}" +echo "VNC is running on port $VNC_PORT (no password!)." +websockify -D --web "/usr/share/novnc/" $NOVNC_PORT "localhost:$VNC_PORT" 2>/dev/null 1>&2 & +echo "noVNC is running on http://localhost:$NOVNC_PORT" +echo exec tini -g -- "$@" From 2de5b8e4abbefd958bcf45011d1721ebe10764b4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 21:21:48 +0200 Subject: [PATCH 097/520] eg: info depending on NOVNC_PORT --- epic-games.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 30cec9d..eba402b 100644 --- a/epic-games.js +++ b/epic-games.js @@ -67,7 +67,8 @@ try { // page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // not needed anymore since we set the cookie above while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { - console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page. If using docker, open http://localhost:6080"); + console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page."); + if (process.env.NOVNC_PORT) console.info(`Open http://localhost:${process.env.NOVNC_PORT} to login inside the docker container.`); context.setDefaultTimeout(0); // give user time to log in without timeout await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); From ab8434694579ee759876fb6c628decca599a416f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Oct 2022 22:01:27 +0200 Subject: [PATCH 098/520] GitHub Action to build & push Docker image --- .github/workflows/docker.yml | 48 ++++++++++++++++++++++++++++++++++++ docker-compose.yml | 2 +- 2 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docker.yml diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 0000000..5d44b87 --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,48 @@ +name: Build and push Docker image (amd64, arm64 to hub.docker.com and ghcr.io) + +on: + workflow_dispatch: + push: + branches: + - 'main' + paths-ignore: + - ".github/**" + - ".gitignore" + - "README.md" + +jobs: + docker: + runs-on: ubuntu-latest + steps: + # - + # name: Checkout + # uses: actions/checkout@v3 + - + name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + - + name: Login to Docker Hub + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + - + name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - + name: Build and push + uses: docker/build-push-action@v3 + with: + context: . + platforms: linux/amd64,linux/arm64,linux/arm/v7 + push: true + tags: | + voglerr/free-games-claimer:latest + ghcr.io/vogler/free-games-claimer:latest diff --git a/docker-compose.yml b/docker-compose.yml index 154cb57..e62360e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,7 @@ services: free-games-claimer: container_name: fgc # is printed in front of every output line - image: free-games-claimer # otherwise image name will be free-games-claimer-free-games-claimer + image: ghcr.io/vogler/free-games-claimer # otherwise image name will be free-games-claimer-free-games-claimer build: . ports: - '5900:5900' From 43f390ddb5e98e3810cf5caf0e024dbda4581647 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 21 Oct 2022 00:03:39 +0200 Subject: [PATCH 099/520] Buildx failed to read dockerfile https://github.com/vogler/free-games-claimer/actions/runs/3292559451/jobs/5429519946 --- .dockerignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index ffd3c43..6cca3e4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,5 +2,4 @@ node_modules data .gitignore -**Dockerfile** .dockerignore From b82507e0af3935d1837e867b48f833c8955ec44e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 21 Oct 2022 00:05:34 +0200 Subject: [PATCH 100/520] Revert "Buildx failed to read dockerfile" This reverts commit 43f390ddb5e98e3810cf5caf0e024dbda4581647. --- .dockerignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.dockerignore b/.dockerignore index 6cca3e4..ffd3c43 100644 --- a/.dockerignore +++ b/.dockerignore @@ -2,4 +2,5 @@ node_modules data .gitignore +**Dockerfile** .dockerignore From e931892b766ed6584f08d6de1458f85747526b9d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 21 Oct 2022 00:05:58 +0200 Subject: [PATCH 101/520] need Checkout Action after all? --- .github/workflows/docker.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5d44b87..3abfeaf 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -14,9 +14,9 @@ jobs: docker: runs-on: ubuntu-latest steps: - # - - # name: Checkout - # uses: actions/checkout@v3 + - + name: Checkout + uses: actions/checkout@v3 - name: Set up QEMU uses: docker/setup-qemu-action@v2 From 746d226cf674e7712388de04b6291383232bc8bc Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 21 Oct 2022 00:47:36 +0200 Subject: [PATCH 102/520] eg: fix page.type: text: expected string, got number --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index eba402b..9b56c46 100644 --- a/epic-games.js +++ b/epic-games.js @@ -83,7 +83,7 @@ try { await page.waitForNavigation({ url: '**/id/login/mfa**'}).then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); const otp = await prompt({type: 'number', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); - await page.type('input[name="code-input-0"]', otp); + await page.type('input[name="code-input-0"]', otp.toString()); await page.click('button[type="submit"]'); }); } From 11b1a96f3bdbd45134c1c737c23c6de8d0a7bc8c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 21 Oct 2022 01:19:19 +0200 Subject: [PATCH 103/520] no longer build for arm/v7 --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 3abfeaf..671ef85 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -41,7 +41,7 @@ jobs: uses: docker/build-push-action@v3 with: context: . - platforms: linux/amd64,linux/arm64,linux/arm/v7 + platforms: linux/amd64,linux/arm64 # ,linux/arm/v7 push: true tags: | voglerr/free-games-claimer:latest From d58ca2037e18615c99fdeb88cdd21a424d2a3d67 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 21 Oct 2022 01:48:34 +0200 Subject: [PATCH 104/520] metadata in package.json --- package-lock.json | 190 ++++++++++------------------------------------ package.json | 19 ++++- 2 files changed, 57 insertions(+), 152 deletions(-) diff --git a/package-lock.json b/package-lock.json index 93508d0..30a45c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,10 +1,14 @@ { "name": "free-games-claimer", + "version": "1.0.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "devDependencies": { + "name": "free-games-claimer", + "version": "1.0.0", + "license": "MIT", + "dependencies": { "cross-env": "^7.0.3", "lowdb": "^4.0.0", "playwright": "^1.27.1", @@ -16,7 +20,6 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "dev": true, "dependencies": { "@types/ms": "*" } @@ -24,14 +27,12 @@ "node_modules/@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "node_modules/arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -39,14 +40,12 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -56,7 +55,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", - "dev": true, "dependencies": { "for-own": "^0.1.3", "is-plain-object": "^2.0.1", @@ -71,14 +69,12 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dev": true, "dependencies": { "cross-spawn": "^7.0.1" }, @@ -96,7 +92,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -110,7 +105,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "dependencies": { "ms": "2.1.2" }, @@ -127,7 +121,6 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -136,7 +129,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -145,7 +137,6 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", - "dev": true, "dependencies": { "for-in": "^1.0.1" }, @@ -157,7 +148,6 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -170,14 +160,12 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -196,14 +184,12 @@ "node_modules/graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -212,20 +198,17 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -234,7 +217,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "dependencies": { "isobject": "^3.0.1" }, @@ -245,14 +227,12 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "node_modules/isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -261,7 +241,6 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "dependencies": { "universalify": "^2.0.0" }, @@ -273,7 +252,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, "dependencies": { "is-buffer": "^1.1.5" }, @@ -285,7 +263,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, "engines": { "node": ">=6" } @@ -294,7 +271,6 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -303,7 +279,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-4.0.0.tgz", "integrity": "sha512-wH4WPH2A+doyzd9mluhMQQsdrHNfOXJE5+C5N03QvH+8EoEMB1WWnjkfn1MkPtVdDrONRTojuNAWi6Es3rVtmA==", - "dev": true, "dependencies": { "steno": "^3.0.0" }, @@ -318,7 +293,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", - "dev": true, "dependencies": { "arr-union": "^3.1.0", "clone-deep": "^0.2.4", @@ -332,7 +306,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -344,7 +317,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", - "dev": true, "dependencies": { "for-in": "^0.1.3", "is-extendable": "^0.1.1" @@ -357,7 +329,6 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -365,14 +336,12 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -381,7 +350,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -390,7 +358,6 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, "engines": { "node": ">=8" } @@ -399,7 +366,6 @@ "version": "1.27.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.27.1.tgz", "integrity": "sha512-xXYZ7m36yTtC+oFgqH0eTgullGztKSRMb4yuwLPl8IYSmgBM88QiB+3IWb1mRIC9/NNwcgbG0RwtFlg+EAFQHQ==", - "dev": true, "hasInstallScript": true, "dependencies": { "playwright-core": "1.27.1" @@ -415,7 +381,6 @@ "version": "1.27.1", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.27.1.tgz", "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==", - "dev": true, "bin": { "playwright": "cli.js" }, @@ -427,7 +392,6 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -440,7 +404,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", "integrity": "sha512-0uatQxzuVn8yegbrEwSk03wvwpMB5jNs7uTTnermylLZzoT+1rmAQaJXwlS3+vADUbw6ELNgNEHC7Skm0RqHbQ==", - "dev": true, "dependencies": { "@types/debug": "^4.1.0", "debug": "^4.1.1", @@ -466,7 +429,6 @@ "version": "2.11.1", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.1.tgz", "integrity": "sha512-n0wdC0Ilc9tk5L6FWLyd0P2gT8b2fp+2NuB+KB0oTSw3wXaZ0D6WNakjJsayJ4waGzIJFCUHkmK9zgx5NKMoFw==", - "dev": true, "dependencies": { "debug": "^4.1.1", "puppeteer-extra-plugin": "^3.2.2", @@ -492,7 +454,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.0.tgz", "integrity": "sha512-qrhYPTGIqzL2hpeJ5DXjf8xMy5rt1UvcqSgpGTTOUOjIMz1ROWnKHjBoE9fNBJ4+ToRZbP8MzIDXWlEk/e1zJA==", - "dev": true, "dependencies": { "debug": "^4.1.1", "fs-extra": "^10.0.0", @@ -519,7 +480,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.0.tgz", "integrity": "sha512-4XxMhMkJ+qqLsPY9ULF90qS9Bj1Qrwwgp1TY9zTdp1dJuy7QSgYE7xlyamq3cKrRuzg3QUOqygJo52sVeXSg5A==", - "dev": true, "dependencies": { "debug": "^4.1.1", "deepmerge": "^4.2.2", @@ -546,7 +506,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -561,7 +520,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", - "dev": true, "dependencies": { "is-extendable": "^0.1.1", "kind-of": "^2.0.1", @@ -576,7 +534,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", - "dev": true, "dependencies": { "is-buffer": "^1.0.2" }, @@ -588,7 +545,6 @@ "version": "0.2.7", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -597,7 +553,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "dependencies": { "shebang-regex": "^3.0.0" }, @@ -609,7 +564,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, "engines": { "node": ">=8" } @@ -617,14 +571,12 @@ "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, "node_modules/steno": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", "integrity": "sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw==", - "dev": true, "engines": { "node": ">=14.16" }, @@ -636,7 +588,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true, "engines": { "node": ">= 10.0.0" } @@ -645,7 +596,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "dependencies": { "isexe": "^2.0.0" }, @@ -659,8 +609,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" } }, "dependencies": { @@ -668,7 +617,6 @@ "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", - "dev": true, "requires": { "@types/ms": "*" } @@ -676,26 +624,22 @@ "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", - "dev": true + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==" }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -705,7 +649,6 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", - "dev": true, "requires": { "for-own": "^0.1.3", "is-plain-object": "^2.0.1", @@ -717,14 +660,12 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", "integrity": "sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw==", - "dev": true, "requires": { "cross-spawn": "^7.0.1" } @@ -733,7 +674,6 @@ "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -744,7 +684,6 @@ "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, "requires": { "ms": "2.1.2" } @@ -752,20 +691,17 @@ "deepmerge": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true + "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", - "dev": true + "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==" }, "for-own": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", - "dev": true, "requires": { "for-in": "^1.0.1" } @@ -774,7 +710,6 @@ "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", - "dev": true, "requires": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -784,14 +719,12 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -804,14 +737,12 @@ "graceful-fs": { "version": "4.2.10", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "dev": true + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -820,26 +751,22 @@ "inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", - "dev": true + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==" }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" } @@ -847,20 +774,17 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", - "dev": true + "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, "requires": { "graceful-fs": "^4.1.6", "universalify": "^2.0.0" @@ -870,7 +794,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -878,20 +801,17 @@ "kleur": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true + "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", - "dev": true + "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" }, "lowdb": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-4.0.0.tgz", "integrity": "sha512-wH4WPH2A+doyzd9mluhMQQsdrHNfOXJE5+C5N03QvH+8EoEMB1WWnjkfn1MkPtVdDrONRTojuNAWi6Es3rVtmA==", - "dev": true, "requires": { "steno": "^3.0.0" } @@ -900,7 +820,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", - "dev": true, "requires": { "arr-union": "^3.1.0", "clone-deep": "^0.2.4", @@ -911,7 +830,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "requires": { "brace-expansion": "^1.1.7" } @@ -920,7 +838,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", - "dev": true, "requires": { "for-in": "^0.1.3", "is-extendable": "^0.1.1" @@ -929,22 +846,19 @@ "for-in": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", - "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", - "dev": true + "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==" } } }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "requires": { "wrappy": "1" } @@ -952,20 +866,17 @@ "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, "path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright": { "version": "1.27.1", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.27.1.tgz", "integrity": "sha512-xXYZ7m36yTtC+oFgqH0eTgullGztKSRMb4yuwLPl8IYSmgBM88QiB+3IWb1mRIC9/NNwcgbG0RwtFlg+EAFQHQ==", - "dev": true, "requires": { "playwright-core": "1.27.1" } @@ -973,14 +884,12 @@ "playwright-core": { "version": "1.27.1", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.27.1.tgz", - "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==", - "dev": true + "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==" }, "prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, "requires": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" @@ -990,7 +899,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", "integrity": "sha512-0uatQxzuVn8yegbrEwSk03wvwpMB5jNs7uTTnermylLZzoT+1rmAQaJXwlS3+vADUbw6ELNgNEHC7Skm0RqHbQ==", - "dev": true, "requires": { "@types/debug": "^4.1.0", "debug": "^4.1.1", @@ -1001,7 +909,6 @@ "version": "2.11.1", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.1.tgz", "integrity": "sha512-n0wdC0Ilc9tk5L6FWLyd0P2gT8b2fp+2NuB+KB0oTSw3wXaZ0D6WNakjJsayJ4waGzIJFCUHkmK9zgx5NKMoFw==", - "dev": true, "requires": { "debug": "^4.1.1", "puppeteer-extra-plugin": "^3.2.2", @@ -1012,7 +919,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.0.tgz", "integrity": "sha512-qrhYPTGIqzL2hpeJ5DXjf8xMy5rt1UvcqSgpGTTOUOjIMz1ROWnKHjBoE9fNBJ4+ToRZbP8MzIDXWlEk/e1zJA==", - "dev": true, "requires": { "debug": "^4.1.1", "fs-extra": "^10.0.0", @@ -1024,7 +930,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.0.tgz", "integrity": "sha512-4XxMhMkJ+qqLsPY9ULF90qS9Bj1Qrwwgp1TY9zTdp1dJuy7QSgYE7xlyamq3cKrRuzg3QUOqygJo52sVeXSg5A==", - "dev": true, "requires": { "debug": "^4.1.1", "deepmerge": "^4.2.2", @@ -1036,7 +941,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "requires": { "glob": "^7.1.3" } @@ -1045,7 +949,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", - "dev": true, "requires": { "is-extendable": "^0.1.1", "kind-of": "^2.0.1", @@ -1057,7 +960,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", - "dev": true, "requires": { "is-buffer": "^1.0.2" } @@ -1065,8 +967,7 @@ "lazy-cache": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", - "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", - "dev": true + "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==" } } }, @@ -1074,7 +975,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, "requires": { "shebang-regex": "^3.0.0" } @@ -1082,32 +982,27 @@ "shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true + "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" }, "steno": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", - "integrity": "sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw==", - "dev": true + "integrity": "sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw==" }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -1115,8 +1010,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" } } } diff --git a/package.json b/package.json index 4abd28a..c7c9e95 100644 --- a/package.json +++ b/package.json @@ -1,14 +1,25 @@ { + "name": "free-games-claimer", + "version": "1.0.0", + "description": "Claims free games on the Epic Games Store and Amazon Prime Gaming.", + "homepage": "https://github.com/vogler/free-games-claimer", + "main": "index.js", "scripts": { - "docker:build": "docker build --tag free-games-claimer .", - "docker:epic-games": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name free-games-claimer free-games-claimer" + "docker:build": "docker build . -t free-games-claimer", + "docker:run": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name free-games-claimer free-games-claimer" }, - "devDependencies": { + "type": "module", + "dependencies": { "cross-env": "^7.0.3", "lowdb": "^4.0.0", "playwright": "^1.27.1", "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" }, - "type": "module" + "repository": { + "type": "git", + "url": "https://github.com/vogler/free-games-claimer.git" + }, + "author": "Ralf Vogler", + "license": "MIT" } From 4d6d2f47d9786fd338e1b9c444ebbdf195f8beb6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 21 Oct 2022 12:38:32 +0200 Subject: [PATCH 105/520] update readme and docker scripts --- README.md | 32 ++++++++++++++++++++++---------- package.json | 4 ++-- 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 437f516..e303100 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,25 @@ Claims free games periodically on Pull requests welcome :) +_Works on Windows/macOS/Linux._ + ## Setup -... should be the same on Windows/macOS/Linux: +[Install Docker](https://docs.docker.com/get-docker/) and use +``` +docker run --rm -it -p 6080:6080 -v fgc:/fgc/data ghcr.io/vogler/free-games-claimer +``` +Data is stored in the volume `fgc`. + +
+ I want to run without Docker or develop locally. 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 && npx playwright install chromium` -This downloads Chromium (343 MB) to a cache in home ([doc](https://playwright.dev/docs/browsers#managing-browser-binaries)). +This downloads 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 playwright install chromium --with-deps`. +
## Usage Both scripts start an automated Chromium instance, either with the browser GUI shown or hidden (*headless mode*). @@ -25,16 +35,18 @@ After login, the script will just continue, but you can also restart it. If something goes wrong, use `PWDEBUG=1 node ...` to [inspect](https://playwright.dev/docs/inspector). ### Epic Games Store -Alternatives: +Options: - Run `node epic-games` (browser window will open, [headless leads to captcha](https://github.com/vogler/free-games-claimer/issues/2)) -- Run with Docker (browser is hidden inside -> headless for host): +- Run inside Docker (browser is hidden, headless for host): - [Install Docker](https://docs.docker.com/get-docker/) - - `npm run docker:build` - - `npm run docker:epic-games` - - When you need to login, go to http://localhost:6080 (you can also connect with any other VNC client on port 5900) + - Options: + - `docker run` command from above + - `npm run docker` which does the same but stores files in `./data` instead of a Docker volume. + - `docker compose up` + - When you need to login, go to http://localhost:6080 (you can also connect with a VNC client on port 5900) ### Amazon Prime Gaming -Run `node prime-gaming` +Run `node prime-gaming` (locally or in Docker). Runs headless. Run `node prime-gaming show` to show the GUI (to login). @@ -43,8 +55,8 @@ Keys for {Origin, GOG.com, Legacy Games} should be printed to the console and ne A screenshot of the page with the code is saved to `data/screenshots` as well. ### Run periodically -Epic Games releases one (sometimes more) free game *every week*, but around christmas every day. -Prime Gaming has new games *every month*. +Epic Games usually has two free games *every week*, before Christmas every day. +Prime Gaming has new games *every month* or more often during Prime days. It is save to run both scripts every day. If you can't use Docker for quasi-headless mode, you could run in a virtual machine, on a server, or you wake your PC at night to avoid being interrupted. diff --git a/package.json b/package.json index c7c9e95..bac3e17 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "homepage": "https://github.com/vogler/free-games-claimer", "main": "index.js", "scripts": { - "docker:build": "docker build . -t free-games-claimer", - "docker:run": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name free-games-claimer free-games-claimer" + "docker:build": "docker build . -t ghcr.io/vogler/free-games-claimer", + "docker": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name fgc ghcr.io/vogler/free-games-claimer" }, "type": "module", "dependencies": { From 4e88964fcd01e97316e527e285a01ee56c2d0a55 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 21 Oct 2022 16:24:52 +0200 Subject: [PATCH 106/520] lint with trunk --- .github/workflows/docker.yml | 2 +- docker-compose.yml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 671ef85..c09e9c7 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,7 +4,7 @@ on: workflow_dispatch: push: branches: - - 'main' + - "main" paths-ignore: - ".github/**" - ".gitignore" diff --git a/docker-compose.yml b/docker-compose.yml index e62360e..dc85bcd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,10 +1,11 @@ +# docker compose up services: free-games-claimer: container_name: fgc # is printed in front of every output line image: ghcr.io/vogler/free-games-claimer # otherwise image name will be free-games-claimer-free-games-claimer build: . ports: - - '5900:5900' - - '6080:6080' + - "5900:5900" + - "6080:6080" volumes: - ./data:/fgc/data From aae396fa807ec1f9562bc26b6389d64ffa372edd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 21 Oct 2022 17:21:23 +0200 Subject: [PATCH 107/520] eg: more info around login --- epic-games.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 9b56c46..916e895 100644 --- a/epic-games.js +++ b/epic-games.js @@ -67,11 +67,12 @@ try { // page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // not needed anymore since we set the cookie above while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { - console.error("Not signed in anymore. Please login and then navigate to the 'Free Games' page."); + console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); if (process.env.NOVNC_PORT) console.info(`Open http://localhost:${process.env.NOVNC_PORT} to login inside the docker container.`); context.setDefaultTimeout(0); // give user time to log in without timeout await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); + console.info('Press ESC to skip if you want to login in the browser.'); const email = process.env.EMAIL || await prompt({message: 'Enter email'}); const password = process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); if (email && password) { @@ -79,6 +80,9 @@ try { await page.fill('#email', email); await page.fill('#password', password); await page.click('button[type="submit"]'); + page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(() => { + console.log('Got a captcha! You may have to solve it in the browser if the NopeCHA extension fails to do so.'); + }); // TODO alternatively wait for redirectUrl await page.waitForNavigation({ url: '**/id/login/mfa**'}).then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); @@ -86,6 +90,8 @@ try { await page.type('input[name="code-input-0"]', otp.toString()); await page.click('button[type="submit"]'); }); + } else { + console.log('Waiting for you to login in the browser.'); } await page.waitForNavigation({ url: URL_CLAIM }); context.setDefaultTimeout(TIMEOUT); From 569e690d9e4a64e444cddcafc1242c8c7e1a9e25 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Oct 2022 14:09:43 +0200 Subject: [PATCH 108/520] eg: log indent 'This game contains mature content' --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 916e895..c622582 100644 --- a/epic-games.js +++ b/epic-games.js @@ -118,7 +118,7 @@ try { // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { - console.log('This game contains mature content recommended only for ages 18+'); + console.log(' This game contains mature content recommended only for ages 18+'); await page.click('button:has-text("Continue")'); } From 7ffdd61c44fbef8d0b7651624838247fb2423bb6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 1 Nov 2022 22:18:33 +0100 Subject: [PATCH 109/520] pg: indent logging per game --- prime-gaming.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 5abf540..a79e4b7 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -102,9 +102,9 @@ try { // Full game for PC [and MAC] on: gog.com, Origin, Legacy Games, EPIC GAMES, Battle.net // 3 Full PC Games on Legacy Games const store = store_text.toLowerCase().replace(/.* on /, ''); - console.log('External store:', store); + console.log(' External store:', store); if (await page.locator('div:has-text("Link game account")').count()) { - console.error('Account linking is required to claim this offer!'); + console.error(' Account linking is required to claim this offer!'); } else { // print code if there is one const redeem = { @@ -115,17 +115,17 @@ try { let code; if (store in redeem) { // did not work for linked origin: && !await page.locator('div:has-text("Successfully Claimed")').count() code = await page.inputValue('input[type="text"]'); - console.log('Code to redeem game:', code); + console.log(' Code to redeem game:', code); if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); } - console.log('URL to redeem game:', redeem[store]); + console.log(' URL to redeem game:', redeem[store]); } db.data.claimed.push({ title, time: datetime(), store, code, url: page.url() }); // save screenshot of potential code just in case const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${filenamify(title)}.png`); await page.screenshot({ path: p, fullPage: true }); - console.info('Saved a screenshot of page to', p); + // console.info(' Saved a screenshot of page to', p); run.c_external++; } // await page.pause(); From 0df7bf35ba9bf1260b7ddab06f2d75e35d42a186 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 1 Nov 2022 22:21:47 +0100 Subject: [PATCH 110/520] pg: redeem code for microsoft games --- prime-gaming.js | 1 + 1 file changed, 1 insertion(+) diff --git a/prime-gaming.js b/prime-gaming.js index a79e4b7..9f23d2d 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -111,6 +111,7 @@ try { // 'origin': 'https://www.origin.com/redeem', // TODO still needed or now only via account linking? 'gog.com': 'https://www.gog.com/redeem', 'legacy games': 'https://www.legacygames.com/primedeal', + 'microsoft games': 'https://redeem.microsoft.com', }; let code; if (store in redeem) { // did not work for linked origin: && !await page.locator('div:has-text("Successfully Claimed")').count() From e1cd3117b6db9e745137992c85e73f78ad75d7ae Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 12 Nov 2022 13:43:27 +0100 Subject: [PATCH 111/520] fix #33 --- epic-games.js | 1 + util.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index c622582..5c22d2c 100644 --- a/epic-games.js +++ b/epic-games.js @@ -17,6 +17,7 @@ const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) || 1280; const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; const db = await jsonDb('epic-games.json'); +db.data ||= {}; const migrateDb = (user) => { if (user in db.data || !('claimed' in db.data)) return; db.data[user] = {}; diff --git a/util.js b/util.js index a8c5fc7..644382f 100644 --- a/util.js +++ b/util.js @@ -16,7 +16,6 @@ import { Low, JSONFile } from 'lowdb'; export const jsonDb = async file => { const db = new Low(new JSONFile(dataDir(file))); await db.read(); - db.data ||= {}; return db; }; From b49862060c3773a3ba29630ee5c52935dfab66d9 Mon Sep 17 00:00:00 2001 From: gladiopeace <5968813+gladiopeace@users.noreply.github.com> Date: Wed, 30 Nov 2022 03:16:51 +0200 Subject: [PATCH 112/520] fix copy multiple files when copying multiple files you need to point to a directory rather a single file "." --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cd652f8..e32481f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,7 +43,7 @@ RUN apt-get update \ RUN ln -s /usr/share/novnc/vnc_auto.html /usr/share/novnc/index.html WORKDIR /fgc -COPY package*.json . +COPY package*.json ./ # Install chromium & dependencies only RUN npm install \ From 89ba21d2cb6a6461410f68d792b4ac1bb4e2a630 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 17 Dec 2022 00:12:59 +0100 Subject: [PATCH 113/520] update lowdb --- package-lock.json | 46 +++++++++++++++++++++++----------------------- package.json | 2 +- util.js | 3 ++- 3 files changed, 26 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 30a45c9..0d65482 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "cross-env": "^7.0.3", - "lowdb": "^4.0.0", + "lowdb": "^5.0.5", "playwright": "^1.27.1", "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" @@ -227,7 +227,7 @@ "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "node_modules/isobject": { "version": "3.0.1", @@ -276,9 +276,9 @@ } }, "node_modules/lowdb": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-4.0.0.tgz", - "integrity": "sha512-wH4WPH2A+doyzd9mluhMQQsdrHNfOXJE5+C5N03QvH+8EoEMB1WWnjkfn1MkPtVdDrONRTojuNAWi6Es3rVtmA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-5.0.5.tgz", + "integrity": "sha512-7EWKmIMhNKA8TXFhL8t0p6N2LC53l3ZqsWQGSksGhhjrcms9rbKlyrAh2PzSGK5v0KPJ2W5VItBnC3NDRzOnzQ==", "dependencies": { "steno": "^3.0.0" }, @@ -363,12 +363,12 @@ } }, "node_modules/playwright": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.27.1.tgz", - "integrity": "sha512-xXYZ7m36yTtC+oFgqH0eTgullGztKSRMb4yuwLPl8IYSmgBM88QiB+3IWb1mRIC9/NNwcgbG0RwtFlg+EAFQHQ==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.29.0.tgz", + "integrity": "sha512-vtXgX3FPNcAJq1QoIVCvmiHHKOLVTZkSYEo60n+EnX5MrNznAJzGquxT8c2sv+BG3CDyLeKm351e491HnF7yjw==", "hasInstallScript": true, "dependencies": { - "playwright-core": "1.27.1" + "playwright-core": "1.29.0" }, "bin": { "playwright": "cli.js" @@ -378,9 +378,9 @@ } }, "node_modules/playwright-core": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.27.1.tgz", - "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.29.0.tgz", + "integrity": "sha512-pboOm1m0RD6z1GtwAbEH60PYRfF87vKdzOSRw2RyO0Y0a7utrMyWN2Au1ojGvQr4umuBMODkKTv607YIRypDSQ==", "bin": { "playwright": "cli.js" }, @@ -774,7 +774,7 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, "isobject": { "version": "3.0.1", @@ -809,9 +809,9 @@ "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" }, "lowdb": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-4.0.0.tgz", - "integrity": "sha512-wH4WPH2A+doyzd9mluhMQQsdrHNfOXJE5+C5N03QvH+8EoEMB1WWnjkfn1MkPtVdDrONRTojuNAWi6Es3rVtmA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-5.0.5.tgz", + "integrity": "sha512-7EWKmIMhNKA8TXFhL8t0p6N2LC53l3ZqsWQGSksGhhjrcms9rbKlyrAh2PzSGK5v0KPJ2W5VItBnC3NDRzOnzQ==", "requires": { "steno": "^3.0.0" } @@ -874,17 +874,17 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.27.1.tgz", - "integrity": "sha512-xXYZ7m36yTtC+oFgqH0eTgullGztKSRMb4yuwLPl8IYSmgBM88QiB+3IWb1mRIC9/NNwcgbG0RwtFlg+EAFQHQ==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.29.0.tgz", + "integrity": "sha512-vtXgX3FPNcAJq1QoIVCvmiHHKOLVTZkSYEo60n+EnX5MrNznAJzGquxT8c2sv+BG3CDyLeKm351e491HnF7yjw==", "requires": { - "playwright-core": "1.27.1" + "playwright-core": "1.29.0" } }, "playwright-core": { - "version": "1.27.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.27.1.tgz", - "integrity": "sha512-9EmeXDncC2Pmp/z+teoVYlvmPWUC6ejSSYZUln7YaP89Z6lpAaiaAnqroUt/BoLo8tn7WYShcfaCh+xofZa44Q==" + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.29.0.tgz", + "integrity": "sha512-pboOm1m0RD6z1GtwAbEH60PYRfF87vKdzOSRw2RyO0Y0a7utrMyWN2Au1ojGvQr4umuBMODkKTv607YIRypDSQ==" }, "prompts": { "version": "2.4.2", diff --git a/package.json b/package.json index bac3e17..a3c08c6 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "type": "module", "dependencies": { "cross-env": "^7.0.3", - "lowdb": "^4.0.0", + "lowdb": "^5.0.5", "playwright": "^1.27.1", "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" diff --git a/util.js b/util.js index 644382f..a14bb1d 100644 --- a/util.js +++ b/util.js @@ -12,7 +12,8 @@ export const dirs = { screenshots: dataDir('screenshots'), }; -import { Low, JSONFile } from 'lowdb'; +import { Low } from 'lowdb'; +import { JSONFile } from 'lowdb/node'; export const jsonDb = async file => { const db = new Low(new JSONFile(dataDir(file))); await db.read(); From f5c5bc48523e295c675803e9ab2ce50b49c817fa Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 17 Dec 2022 00:14:14 +0100 Subject: [PATCH 114/520] update playwright --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0d65482..cd6b762 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "cross-env": "^7.0.3", "lowdb": "^5.0.5", - "playwright": "^1.27.1", + "playwright": "^1.29.0", "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" } diff --git a/package.json b/package.json index a3c08c6..e2d288b 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,7 @@ "dependencies": { "cross-env": "^7.0.3", "lowdb": "^5.0.5", - "playwright": "^1.27.1", + "playwright": "^1.29.0", "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" }, From d616de2096efff9044fecd358209a6ccfc778ddb Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 17 Dec 2022 00:23:22 +0100 Subject: [PATCH 115/520] eg: use firefox instead of chromium, fixes #34 --- epic-games.js | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/epic-games.js b/epic-games.js index 5c22d2c..9fd3097 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,4 +1,4 @@ -import { chromium } from 'playwright'; // stealth plugin needs no outdated playwright-extra +import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; import { existsSync, writeFileSync } from 'fs'; @@ -30,23 +30,24 @@ const migrateDb = (user) => { } // https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 -const ext = path.resolve('nopecha'); +const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox // https://playwright.dev/docs/auth#multi-factor-authentication -const context = await chromium.launchPersistentContext(dirs.browser, { +const context = await firefox.launchPersistentContext(dirs.browser, { // chrome will not work in linux arm64, only chromium // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge headless: false, viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? + // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 locale: "en-US", // ignore OS locale to be sure to have english text for locators // recordVideo: { dir: 'data/videos/' }, // will record a .webm video for each page navigated args: [ // https://peter.sh/experiments/chromium-command-line-switches // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' // '--restore-last-session', // does not apply for crash/killed '--hide-crash-restore-bubble', - `--disable-extensions-except=${ext}`, - `--load-extension=${ext}`, + // `--disable-extensions-except=${ext}`, + // `--load-extension=${ext}`, ], // ignoreDefaultArgs: ['--enable-automation'], // remove default arg that shows the info bar with 'Chrome is being controlled by automated test software.'. Since Chromeium 106 this leads to show another info bar with 'You are using an unsupported command-line flag: --no-sandbox. Stability and security will suffer.'. }); @@ -114,7 +115,7 @@ try { console.log('Free games:', urls); for (const url of urls) { - await page.goto(url, { waitUntil: 'domcontentloaded' }); + await page.goto(url); // , { waitUntil: 'domcontentloaded' }); const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); // barrier to block until page is loaded // click Continue if 'This game contains mature content recommended only for ages 18+' @@ -137,12 +138,13 @@ try { await page.click('[data-testid="purchase-cta-button"]'); // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoided by Windows userAgent? - // page.click('button:has-text("Continue")').catch(_ => { }); + page.click('button:has-text("Continue")').catch(_ => { }); // needed since change from Chromium to Firefox? if (process.env.DRYRUN) continue; if (debug) await page.pause(); // it then creates an iframe for the purchase + await page.waitForSelector('#webPurchaseContainer iframe'); // TODO needed? const iframe = page.frameLocator('#webPurchaseContainer iframe'); await iframe.locator('button:has-text("Place Order")').click(); @@ -153,15 +155,14 @@ try { await Promise.any([btnAgree.click(), page.waitForSelector('text=Thank you for buying').then(_ => { })]); // EU: wait for agree button, non-EU: potentially done const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); - await captcha.waitFor().then(async () => { + captcha.waitFor().then(async () => { // don't await, since element may not be shown console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') // await page.waitForTimeout(2000); // const p = path.resolve(dirs.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); // await captcha.screenshot({ path: p }); // console.info(' Saved a screenshot of hcaptcha challenge to', p); // console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? - }); - + }).catch(_ => { }); // may time out if not shown await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again = no-op db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed time From ba545632f5004754b096d4d930bc24834c22dec6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 17 Dec 2022 00:27:15 +0100 Subject: [PATCH 116/520] docker: also install firefox --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index e32481f..43269bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -45,9 +45,9 @@ RUN ln -s /usr/share/novnc/vnc_auto.html /usr/share/novnc/index.html WORKDIR /fgc COPY package*.json ./ -# Install chromium & dependencies only +# Install browser & dependencies only RUN npm install \ - && npx playwright install --with-deps chromium \ + && npx playwright install --with-deps firefox chromium \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* From a6f7c6d7e4937bb5170dbe1d2f4de4d73edf7a7c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 17 Dec 2022 09:17:31 +0100 Subject: [PATCH 117/520] Readme: Xbox Live Games with Gold - planned --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e303100..53156e3 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ Claims free games periodically on - [Epic Games Store](https://www.epicgames.com/store/free-games) - [Amazon Prime Gaming](https://gaming.amazon.com) -- [GOG](https://www.gog.com) - WIP +- [GOG](https://www.gog.com) - planned +- [Xbox Live Games with Gold](https://www.xbox.com/en-US/live/gold#gameswithgold) - planned Pull requests welcome :) From 31fb97345e8067b7ebd521daf3e15867eb91e0c4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 17 Dec 2022 09:06:07 +0100 Subject: [PATCH 118/520] docker: nicer entrypoint log --- docker/entrypoint.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index de596e4..625bba3 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -20,10 +20,10 @@ rm -f /tmp/.X1-lock export DISPLAY=:1 # need to export this, otherwise playwright complains with 'Looks like you launched a headed browser without having a XServer running.' Xvfb $DISPLAY -ac -screen 0 "${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}" & -echo "Xvfb display server created screen with resolution ${SCREEN_WIDTH}x${SCREEN_HEIGHT}." +echo "Xvfb display server created screen with resolution ${SCREEN_WIDTH}x${SCREEN_HEIGHT}" x11vnc -display $DISPLAY -forever -shared -rfbport $VNC_PORT -bg -nopw 2>/dev/null 1>&2 # -passwd "${VNC_PASSWORD}" -echo "VNC is running on port $VNC_PORT (no password!)." +echo "VNC is running on port $VNC_PORT (no password!)" websockify -D --web "/usr/share/novnc/" $NOVNC_PORT "localhost:$VNC_PORT" 2>/dev/null 1>&2 & -echo "noVNC is running on http://localhost:$NOVNC_PORT" +echo "noVNC (VNC via browser) is running on http://localhost:$NOVNC_PORT" echo exec tini -g -- "$@" From 34393eec76419fe1a8f3f4cb56f3f47ac6d2712e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 21 Dec 2022 01:12:16 +0100 Subject: [PATCH 119/520] pg: also use firefox Dropping chromium reduced image size by ~500MB from 1.55GB to 1.04GB. --- Dockerfile | 4 ++-- prime-gaming.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 43269bf..b8d8092 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM ubuntu:focal ARG DEBIAN_FRONTEND=noninteractive # Playwright -ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true +# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD true # === INSTALL Node.js === @@ -47,7 +47,7 @@ COPY package*.json ./ # Install browser & dependencies only RUN npm install \ - && npx playwright install --with-deps firefox chromium \ + && npx playwright install --with-deps firefox \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* diff --git a/prime-gaming.js b/prime-gaming.js index 9f23d2d..09f036f 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,4 +1,4 @@ -import { chromium } from 'playwright'; // stealth plugin needs no outdated playwright-extra +import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; @@ -22,7 +22,7 @@ const run = { }; // https://playwright.dev/docs/auth#multi-factor-authentication -const context = await chromium.launchPersistentContext(dirs.browser, { +const context = await firefox.launchPersistentContext(dirs.browser, { // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge, chrome will not work on arm64 linux, only chromium which is the default headless, viewport: { width: 1280, height: 1280 }, From c44d1641ea1c3e16cab11aa538828094722d61f9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 21 Dec 2022 09:03:58 +0100 Subject: [PATCH 120/520] cleanup Dockerfile, merged apt runs -> saved ~90MB Image size from 1.04GB to 952MB. --- Dockerfile | 48 +++++++++++++++++------------------------------- 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/Dockerfile b/Dockerfile index b8d8092..88eff95 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,44 +1,30 @@ # FROM mcr.microsoft.com/playwright:v1.20.0 +# Partially from https://github.com/microsoft/playwright/blob/main/utils/docker/Dockerfile.focal FROM ubuntu:focal ARG DEBIAN_FRONTEND=noninteractive -# Playwright -# ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD true -# === INSTALL Node.js === -# Taken from https://github.com/microsoft/playwright/blob/main/utils/docker/Dockerfile.focal -RUN apt-get update && \ - # Install node16 - apt-get install -y curl wget && \ - curl -sL https://deb.nodesource.com/setup_16.x | bash - && \ - apt-get install -y nodejs && \ - # Feature-parity with node.js base images. - apt-get install -y --no-install-recommends git openssh-client && \ - npm install -g yarn && \ - # clean apt cache - rm -rf /var/lib/apt/lists/* && \ - # Create the pwuser - adduser pwuser - -# === Install the base requirements to run and debug webdriver implementations === +# Install up-to-date node & npm, then deps for virtual screen & noVNC RUN apt-get update \ + && apt-get install -y curl \ + && curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \ + && apt-get install -y nodejs \ && apt-get install --no-install-recommends --no-install-suggests -y \ - xvfb \ - ca-certificates \ - x11vnc \ - curl \ - tini \ - novnc websockify \ - dos2unix \ + xvfb \ + ca-certificates \ + x11vnc \ + tini \ + novnc websockify \ + dos2unix \ && apt-get clean \ && rm -rf \ - /tmp/* \ - /usr/share/doc/* \ - /var/cache/* \ - /var/lib/apt/lists/* \ - /var/tmp/* + /tmp/* \ + /usr/share/doc/* \ + /var/cache/* \ + /var/lib/apt/lists/* \ + /var/tmp/* RUN ln -s /usr/share/novnc/vnc_auto.html /usr/share/novnc/index.html @@ -54,7 +40,7 @@ RUN npm install \ COPY . . # Shell scripts -# On windows, git might be configured to check out dos/CRLF line endings, so we convert. +# On windows, git might be configured to check out dos/CRLF line endings, so we convert them for those people in case they want to build the image. RUN dos2unix ./docker/*.sh RUN mv ./docker/entrypoint.sh /usr/local/bin/entrypoint \ && chmod +x /usr/local/bin/entrypoint From 0ad324b756a5722330fac1a47eead471979d3dff Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 21 Dec 2022 09:15:38 +0100 Subject: [PATCH 121/520] docker: upgrade node 16 -> 19, +60MB --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 88eff95..cf03184 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD true # Install up-to-date node & npm, then deps for virtual screen & noVNC RUN apt-get update \ && apt-get install -y curl \ - && curl -fsSL https://deb.nodesource.com/setup_16.x | bash - \ + && curl -fsSL https://deb.nodesource.com/setup_19.x | bash - \ && apt-get install -y nodejs \ && apt-get install --no-install-recommends --no-install-suggests -y \ xvfb \ From 3c4f79a1ed8e246e7da8325c32a4e84f15b1df3c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 24 Dec 2022 14:37:02 +0100 Subject: [PATCH 122/520] docker: upgrade Ubuntu 20.04 (focal) -> 22.04 (jammy), +60MB --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index cf03184..a1a4b3c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # FROM mcr.microsoft.com/playwright:v1.20.0 # Partially from https://github.com/microsoft/playwright/blob/main/utils/docker/Dockerfile.focal -FROM ubuntu:focal +FROM ubuntu:jammy ARG DEBIAN_FRONTEND=noninteractive From 8ce6c2fdc90c21e9e9cd5020d13f2e0ffd9c45db Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 24 Dec 2022 14:46:01 +0100 Subject: [PATCH 123/520] docker: hadolint: pipefail Can't do the recommended --no-install-recommends for curl because otherwise it has problems with certificates: curl: (77) error setting certificate file: /etc/ssl/certs/ca-certificates.crt --- Dockerfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index a1a4b3c..b2a17f1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,8 +2,9 @@ # Partially from https://github.com/microsoft/playwright/blob/main/utils/docker/Dockerfile.focal FROM ubuntu:jammy +# https://github.com/hadolint/hadolint/wiki/DL4006 +SHELL ["/bin/bash", "-o", "pipefail", "-c"] ARG DEBIAN_FRONTEND=noninteractive - ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD true # Install up-to-date node & npm, then deps for virtual screen & noVNC From 67e622e6ec2cbc091f0497eafc5048a08123f9b1 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 24 Dec 2022 15:02:28 +0100 Subject: [PATCH 124/520] docker: move firefox install up to other apt deps; --with-deps needed! MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Without --with-deps we get and error starting the browser: node:internal/process/esm_loader:108 internalBinding('errors').triggerUncaughtException( ^ browserType.launchPersistentContext: ╔══════════════════════════════════════════════════════╗ ║ Host system is missing dependencies to run browsers. ║ ║ Please install them with the following command: ║ ║ ║ ║ npx playwright install-deps ║ ║ ║ ║ Alternatively, use apt: ║ ║ apt-get install libgtk-3-0\ ║ ║ libasound2\ ║ ║ libxcomposite1\ ║ ║ libpangocairo-1.0-0\ ║ ║ libpango-1.0-0\ ║ ║ libatk1.0-0\ ║ ║ libcairo-gobject2\ ║ ║ libcairo2\ ║ ║ libgdk-pixbuf-2.0-0\ ║ ║ libdbus-glib-1-2\ ║ ║ libxcursor1 ║ ║ ║ ║ <3 Playwright Team ║ ╚══════════════════════════════════════════════════════╝ at async file:///fgc/prime-gaming.js:25:17 { name: 'Error' } --- Dockerfile | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index b2a17f1..e5937b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,12 +2,15 @@ # Partially from https://github.com/microsoft/playwright/blob/main/utils/docker/Dockerfile.focal FROM ubuntu:jammy +# Configuration variables are at the end! + # https://github.com/hadolint/hadolint/wiki/DL4006 SHELL ["/bin/bash", "-o", "pipefail", "-c"] ARG DEBIAN_FRONTEND=noninteractive ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD true -# Install up-to-date node & npm, then deps for virtual screen & noVNC +# Install up-to-date node & npm, deps for virtual screen & noVNC, browser. +# Playwright needs --with-deps for firefox. RUN apt-get update \ && apt-get install -y curl \ && curl -fsSL https://deb.nodesource.com/setup_19.x | bash - \ @@ -19,6 +22,7 @@ RUN apt-get update \ tini \ novnc websockify \ dos2unix \ + && npx playwright install --with-deps firefox \ && apt-get clean \ && rm -rf \ /tmp/* \ @@ -32,16 +36,11 @@ RUN ln -s /usr/share/novnc/vnc_auto.html /usr/share/novnc/index.html WORKDIR /fgc COPY package*.json ./ -# Install browser & dependencies only -RUN npm install \ - && npx playwright install --with-deps firefox \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* +RUN npm install COPY . . -# Shell scripts -# On windows, git might be configured to check out dos/CRLF line endings, so we convert them for those people in case they want to build the image. +# Shell scripts need Linux line endings. On Windows, git might be configured to check out dos/CRLF line endings, so we convert them for those people in case they want to build the image. RUN dos2unix ./docker/*.sh RUN mv ./docker/entrypoint.sh /usr/local/bin/entrypoint \ && chmod +x /usr/local/bin/entrypoint From 1c38f730ab6d0a4e6db8e1a2229ab8d7b6c76fd2 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 24 Dec 2022 15:20:45 +0100 Subject: [PATCH 125/520] docker: run both epic-games and prime-gaming by default --- Dockerfile | 4 +++- epic-games.js | 2 ++ prime-gaming.js | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index e5937b3..ceeabf5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,5 +56,7 @@ ENV SCREEN_WIDTH 1280 ENV SCREEN_HEIGHT 1280 ENV SCREEN_DEPTH 24 +# Script to setup display server & VNC is always executed. ENTRYPOINT ["entrypoint"] -CMD ["node", "epic-games.js"] +# Default command to run. This is replaced by appending own command, e.g. `docker run ... node prime-gaming` to only run this script. +CMD node epic-games; node prime-gaming diff --git a/epic-games.js b/epic-games.js index 9fd3097..6c526b8 100644 --- a/epic-games.js +++ b/epic-games.js @@ -16,6 +16,8 @@ const TIMEOUT = 20 * 1000; // 20s, default is 30s const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) || 1280; const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; +console.log(datetime(), 'started checking epic-games'); + const db = await jsonDb('epic-games.json'); db.data ||= {}; const migrateDb = (user) => { diff --git a/prime-gaming.js b/prime-gaming.js index 09f036f..cb7a0cc 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -10,6 +10,8 @@ const headless = !debug && !show; const URL_CLAIM = 'https://gaming.amazon.com/home'; const TIMEOUT = 20 * 1000; // 20s, default is 30s +console.log(datetime(), 'started checking prime-gaming'); + const db = await jsonDb('prime-gaming.json'); db.data ||= { claimed: [], runs: [] }; const run = { From 09b867158c4ff04557236ab8396e4fcc94aa291f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Dec 2022 01:23:17 +0100 Subject: [PATCH 126/520] viewport dimensions via env --- prime-gaming.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index cb7a0cc..69338d2 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -9,6 +9,8 @@ const headless = !debug && !show; // const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? const URL_CLAIM = 'https://gaming.amazon.com/home'; const TIMEOUT = 20 * 1000; // 20s, default is 30s +const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) || 1280; +const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; console.log(datetime(), 'started checking prime-gaming'); @@ -27,7 +29,7 @@ const run = { const context = await firefox.launchPersistentContext(dirs.browser, { // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge, chrome will not work on arm64 linux, only chromium which is the default headless, - viewport: { width: 1280, height: 1280 }, + viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, locale: "en-US", // ignore OS locale to be sure to have english text for locators }); From bea048cc727d404e940698b12bdee37d2a4c859d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Dec 2022 12:05:43 +0100 Subject: [PATCH 127/520] update README chromium -> firefox --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 53156e3..a80375d 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ _Works on Windows/macOS/Linux._ ``` docker run --rm -it -p 6080:6080 -v fgc:/fgc/data ghcr.io/vogler/free-games-claimer ``` +which will run `epic-games` and then `prime-gaming`. Data is stored in the volume `fgc`.
@@ -21,14 +22,14 @@ Data is stored in the volume `fgc`. 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 && npx playwright install chromium` +3. Run `npm install && npx playwright install firefox` -This downloads 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 playwright install chromium --with-deps`. +This downloads 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`.
## Usage -Both scripts start an automated Chromium instance, either with the browser GUI shown or hidden (*headless mode*). +Both scripts start an automated Firefox instance, either with the browser GUI shown or hidden (*headless mode*). Login has to be done in the browser. It's hard to automate since you usually need to enter some OTP (but you can select 'remember this device'). After login, the script will just continue, but you can also restart it. From 8edc4727b334e4105964b541a04626916c0a5586 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Dec 2022 12:54:45 +0100 Subject: [PATCH 128/520] pg: changes from eg --- prime-gaming.js | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 69338d2..4e7b9d5 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -2,7 +2,7 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwr import path from 'path'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; -const debug = process.env.PWDEBUG == '1'; // runs headful and opens https://playwright.dev/docs/inspector +const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector const show = process.argv.includes('show', 2); const headless = !debug && !show; @@ -27,7 +27,6 @@ const run = { // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(dirs.browser, { - // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge, chrome will not work on arm64 linux, only chromium which is the default headless, viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, locale: "en-US", // ignore OS locale to be sure to have english text for locators @@ -38,20 +37,14 @@ await stealth(context); if (!debug) context.setDefaultTimeout(TIMEOUT); -// const page = /* context.pages().length ? context.pages[0] : */ await context.newPage(); -const page = context.pages()[0]; -console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); - -const clickIfExists = async selector => { - if (await page.locator(selector).count() > 0 && await page.locator(selector).isVisible()) - await page.click(selector); -}; +const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist +// console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever // need to wait for some elements to exist before checking if signed in or accepting cookies: await Promise.any(['button:has-text("Sign in")', '[data-a-target="user-dropdown-first-name-text"]'].map(s => page.waitForSelector(s))); - await clickIfExists('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")'); // to not waste screen space in --debug + await page.click('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")').catch(_ => { }); // to not waste screen space in --debug while (await page.locator('button:has-text("Sign in")').count() > 0) { console.error('Not signed in anymore.'); if (headless) { @@ -79,6 +72,7 @@ try { // const title = await card.locator('h3').first().innerText(); const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); + if (process.env.DRYRUN) continue; // const img = await (await card.$('img.tw-image')).getAttribute('src'); // console.log('Image:', img); const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); @@ -99,6 +93,7 @@ try { if (!card) break; const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); + if (process.env.DRYRUN) continue; await (await card.$('text=Claim')).click(); // await page.waitForNavigation(); await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")')]); // waits for navigation @@ -141,13 +136,11 @@ try { // await page.screenshot({ path: p, fullPage: true }); await page.locator(games_sel).screenshot({ path: p }); } catch (error) { - console.error(error); + console.error(error); // .toString()? run.error = error.toString(); } finally { - // write out json db run.endTime = datetime(); db.data.runs.push(run); - await db.write(); - - await context.close(); + await db.write(); // write out json db } +await context.close(); From 4ed5aa77742339e01684dae83e85c2fd74a74eec Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Dec 2022 12:58:18 +0100 Subject: [PATCH 129/520] pg: no longer track runs in json --- prime-gaming.js | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 4e7b9d5..f31d3f4 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -15,15 +15,7 @@ const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; console.log(datetime(), 'started checking prime-gaming'); const db = await jsonDb('prime-gaming.json'); -db.data ||= { claimed: [], runs: [] }; -const run = { - startTime: datetime(), - endTime: null, - n_internal: null, // unclaimed games at beginning - c_internal: 0, // claimed games at end - n_external: null, - c_external: 0, -}; +db.data ||= { claimed: [] }; // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(dirs.browser, { @@ -63,8 +55,7 @@ try { await page.waitForSelector(games_sel); console.log('Number of already claimed games (total):', await page.locator(`${games_sel} p:has-text("Collected")`).count()); const game_sel = `${games_sel} [data-a-target="item-card"]:has-text("Claim game")`; - run.n_internal = await page.locator(game_sel).count(); - console.log('Number of free unclaimed games (Prime Gaming):', run.n_internal); + console.log('Number of free unclaimed games (Prime Gaming):', await page.locator(game_sel).count()); const games = await page.$$(game_sel); // for (let i=1; i<=n; i++) { for (const card of games) { @@ -79,7 +70,6 @@ try { await card.screenshot({ path: p }); await (await card.$('button:has-text("Claim game")')).click(); db.data.claimed.push({ title, time: datetime(), store: 'internal' }); - run.c_internal++; // await page.pause(); } // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com @@ -87,7 +77,6 @@ try { const game_sel_ext = `${games_sel} [data-a-target="item-card"]:has(p:text-is("Claim"))`; do { n = await page.locator(game_sel_ext).count(); - run.n_external ||= n; console.log('Number of free unclaimed games (external stores):', n); const card = await page.$(game_sel_ext); if (!card) break; @@ -126,7 +115,6 @@ try { const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${filenamify(title)}.png`); await page.screenshot({ path: p, fullPage: true }); // console.info(' Saved a screenshot of page to', p); - run.c_external++; } // await page.pause(); await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); @@ -137,10 +125,7 @@ try { await page.locator(games_sel).screenshot({ path: p }); } catch (error) { console.error(error); // .toString()? - run.error = error.toString(); } finally { - run.endTime = datetime(); - db.data.runs.push(run); await db.write(); // write out json db } await context.close(); From a10c61379ba38c080c9705dc510e17a53f02d900 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Dec 2022 14:54:13 +0100 Subject: [PATCH 130/520] pg: prompts for login & MFA --- prime-gaming.js | 36 ++++++++++++++++++++++++++++++------ 1 file changed, 30 insertions(+), 6 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index f31d3f4..34a8236 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -2,6 +2,11 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwr import path from 'path'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; +import prompts from 'prompts'; // alternatives: enquirer, inquirer +// import enquirer from 'enquirer'; const { prompt } = enquirer; +// single prompt that just returns the non-empty value instead of an object - why name things if there's just one? +const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; + const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector const show = process.argv.includes('show', 2); const headless = !debug && !show; @@ -36,16 +41,35 @@ try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever // need to wait for some elements to exist before checking if signed in or accepting cookies: await Promise.any(['button:has-text("Sign in")', '[data-a-target="user-dropdown-first-name-text"]'].map(s => page.waitForSelector(s))); - await page.click('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")').catch(_ => { }); // to not waste screen space in --debug + page.click('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")').catch(_ => { }); // to not waste screen space when non-headless, TODO does not work reliably, need to wait for something else first? while (await page.locator('button:has-text("Sign in")').count() > 0) { console.error('Not signed in anymore.'); - if (headless) { - console.log('Please run `node prime-gaming show` to login in the opened browser.'); - await context.close(); // not needed? - process.exit(1); - } await page.click('button:has-text("Sign in")'); if (!debug) context.setDefaultTimeout(0); // give user time to log in without timeout + console.info('Press ESC to skip if you want to login in the browser (not possible in default headless mode).'); + const email = process.env.EMAIL || await prompt({message: 'Enter email'}); + const password = process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); + if (email && password) { + await page.fill('[name=email]', email); + await page.fill('[name=password]', password); + await page.check('[name=rememberMe]'); + await page.click('input[type="submit"]'); + // handle MFA, but don't await it + page.waitForNavigation({ url: '**/ap/mfa**'}).then(async () => { + console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App'); + await page.check('[name=rememberDevice]'); + const otp = await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + await page.type('input[name=otpCode]', otp.toString()); + await page.click('input[type="submit"]'); + }); + } else { + if (headless) { + console.log('Please run `node prime-gaming show` to login in the opened browser.'); + await context.close(); // not needed? + process.exit(1); + } + console.log('Waiting for you to login in the browser.'); + } await page.waitForNavigation({ url: 'https://gaming.amazon.com/home?signedIn=true' }); if (!debug) context.setDefaultTimeout(TIMEOUT); } From db7cf88c1b1c4303224d3d17ff0f556a54b7dbc3 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Dec 2022 14:55:04 +0100 Subject: [PATCH 131/520] eg: don't await MFA --- epic-games.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/epic-games.js b/epic-games.js index 6c526b8..edab185 100644 --- a/epic-games.js +++ b/epic-games.js @@ -87,13 +87,13 @@ try { page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(() => { console.log('Got a captcha! You may have to solve it in the browser if the NopeCHA extension fails to do so.'); }); - // TODO alternatively wait for redirectUrl - await page.waitForNavigation({ url: '**/id/login/mfa**'}).then(async () => { + // handle MFA, but don't await it + page.waitForNavigation({ url: '**/id/login/mfa**'}).then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); - const otp = await prompt({type: 'number', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); + const otp = await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them await page.type('input[name="code-input-0"]', otp.toString()); await page.click('button[type="submit"]'); - }); + }).catch(_ => { }); } else { console.log('Waiting for you to login in the browser.'); } From 3c6d7f430000a799fd747df1e796d305a0a0c335 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Dec 2022 15:17:08 +0100 Subject: [PATCH 132/520] pg: index by user and title, migrateDB --- prime-gaming.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 34a8236..29d178c 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -20,7 +20,16 @@ const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; console.log(datetime(), 'started checking prime-gaming'); const db = await jsonDb('prime-gaming.json'); -db.data ||= { claimed: [] }; +db.data ||= {}; +const migrateDb = (user) => { + if (user in db.data || !('claimed' in db.data)) return; + db.data[user] = {}; + for (const e of db.data.claimed) { + db.data[user][e.title] = e; + } + delete db.data.claimed; + delete db.data.runs; +} // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(dirs.browser, { @@ -73,7 +82,14 @@ try { await page.waitForNavigation({ url: 'https://gaming.amazon.com/home?signedIn=true' }); if (!debug) context.setDefaultTimeout(TIMEOUT); } - console.log('Signed in.'); + const user = await page.locator('[data-a-target="user-dropdown-first-name-text"]').first().innerText(); + console.log(`Signed in as ${user}`); + // await page.click('button[aria-label="User dropdown and more options"]'); + // const twitch = await page.locator('[data-a-target="TwitchDisplayName"]').first().innerText(); + // console.log(`Twitch user name is ${twitch}`); + migrateDb(user); // TODO remove this after some time since it will run fine without and people can still use this commit to adjust their data/prime-gaming.json + db.data[user] ||= {}; + await page.click('button[data-type="Game"]'); const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; await page.waitForSelector(games_sel); @@ -93,7 +109,7 @@ try { const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); await card.screenshot({ path: p }); await (await card.$('button:has-text("Claim game")')).click(); - db.data.claimed.push({ title, time: datetime(), store: 'internal' }); + db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; // await page.pause(); } // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com @@ -134,7 +150,7 @@ try { } console.log(' URL to redeem game:', redeem[store]); } - db.data.claimed.push({ title, time: datetime(), store, code, url: page.url() }); + db.data[user][title] ||= { title, time: datetime(), store, code, url: page.url() }; // save screenshot of potential code just in case const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${filenamify(title)}.png`); await page.screenshot({ path: p, fullPage: true }); From 446c1c93462a1b8e7d44d4167161ffd152cdbfd0 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Dec 2022 15:22:55 +0100 Subject: [PATCH 133/520] pg: comment external microsoft --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 29d178c..c7f6618 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -112,7 +112,7 @@ try { db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; // await page.pause(); } - // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com + // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft let n; const game_sel_ext = `${games_sel} [data-a-target="item-card"]:has(p:text-is("Claim"))`; do { From a4d39b6a6e35138426ddd332464104b907d39b3b Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Dec 2022 15:23:22 +0100 Subject: [PATCH 134/520] pg: fix for #41 --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index c7f6618..f56cafd 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -125,7 +125,7 @@ try { if (process.env.DRYRUN) continue; await (await card.$('text=Claim')).click(); // await page.waitForNavigation(); - await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")')]); // waits for navigation + await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); // Full game for PC [and MAC] on: gog.com, Origin, Legacy Games, EPIC GAMES, Battle.net // 3 Full PC Games on Legacy Games From 67ccf032e54cd2cd18fa92878f7a0bedf7416279 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Dec 2022 15:38:17 +0100 Subject: [PATCH 135/520] eg: catch pending optional promise to avoid time out --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index edab185..b943ae7 100644 --- a/epic-games.js +++ b/epic-games.js @@ -86,7 +86,7 @@ try { await page.click('button[type="submit"]'); page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(() => { console.log('Got a captcha! You may have to solve it in the browser if the NopeCHA extension fails to do so.'); - }); + }).catch(_ => { }); // handle MFA, but don't await it page.waitForNavigation({ url: '**/id/login/mfa**'}).then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); From 21a2bc01a4712276c8ac1861ef493d05a127eb3f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 30 Dec 2022 15:37:49 +0100 Subject: [PATCH 136/520] mention how to change default command with `docker run`, #42 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a80375d..a92c018 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ _Works on Windows/macOS/Linux._ ``` docker run --rm -it -p 6080:6080 -v fgc:/fgc/data ghcr.io/vogler/free-games-claimer ``` -which will run `epic-games` and then `prime-gaming`. +which will run `node epic-games; node prime-gaming`. If you only want to claim games for one store, you can override the default by appending e.g. `node epic-games` at the end of the `docker run` command. Data is stored in the volume `fgc`.
From 593677ca19e3d63846f8450c6e895b2584a38c29 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Dec 2022 17:13:38 +0100 Subject: [PATCH 137/520] cp prime-gaming.js gog.js --- gog.js | 171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 gog.js diff --git a/gog.js b/gog.js new file mode 100644 index 0000000..f56cafd --- /dev/null +++ b/gog.js @@ -0,0 +1,171 @@ +import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra +import path from 'path'; +import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; + +import prompts from 'prompts'; // alternatives: enquirer, inquirer +// import enquirer from 'enquirer'; const { prompt } = enquirer; +// single prompt that just returns the non-empty value instead of an object - why name things if there's just one? +const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; + +const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector +const show = process.argv.includes('show', 2); +const headless = !debug && !show; + +// const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? +const URL_CLAIM = 'https://gaming.amazon.com/home'; +const TIMEOUT = 20 * 1000; // 20s, default is 30s +const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) || 1280; +const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; + +console.log(datetime(), 'started checking prime-gaming'); + +const db = await jsonDb('prime-gaming.json'); +db.data ||= {}; +const migrateDb = (user) => { + if (user in db.data || !('claimed' in db.data)) return; + db.data[user] = {}; + for (const e of db.data.claimed) { + db.data[user][e.title] = e; + } + delete db.data.claimed; + delete db.data.runs; +} + +// https://playwright.dev/docs/auth#multi-factor-authentication +const context = await firefox.launchPersistentContext(dirs.browser, { + headless, + viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, + locale: "en-US", // ignore OS locale to be sure to have english text for locators +}); + +// TODO test if needed +await stealth(context); + +if (!debug) context.setDefaultTimeout(TIMEOUT); + +const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist +// console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); + +try { + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever + // need to wait for some elements to exist before checking if signed in or accepting cookies: + await Promise.any(['button:has-text("Sign in")', '[data-a-target="user-dropdown-first-name-text"]'].map(s => page.waitForSelector(s))); + page.click('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")').catch(_ => { }); // to not waste screen space when non-headless, TODO does not work reliably, need to wait for something else first? + while (await page.locator('button:has-text("Sign in")').count() > 0) { + console.error('Not signed in anymore.'); + await page.click('button:has-text("Sign in")'); + if (!debug) context.setDefaultTimeout(0); // give user time to log in without timeout + console.info('Press ESC to skip if you want to login in the browser (not possible in default headless mode).'); + const email = process.env.EMAIL || await prompt({message: 'Enter email'}); + const password = process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); + if (email && password) { + await page.fill('[name=email]', email); + await page.fill('[name=password]', password); + await page.check('[name=rememberMe]'); + await page.click('input[type="submit"]'); + // handle MFA, but don't await it + page.waitForNavigation({ url: '**/ap/mfa**'}).then(async () => { + console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App'); + await page.check('[name=rememberDevice]'); + const otp = await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + await page.type('input[name=otpCode]', otp.toString()); + await page.click('input[type="submit"]'); + }); + } else { + if (headless) { + console.log('Please run `node prime-gaming show` to login in the opened browser.'); + await context.close(); // not needed? + process.exit(1); + } + console.log('Waiting for you to login in the browser.'); + } + await page.waitForNavigation({ url: 'https://gaming.amazon.com/home?signedIn=true' }); + if (!debug) context.setDefaultTimeout(TIMEOUT); + } + const user = await page.locator('[data-a-target="user-dropdown-first-name-text"]').first().innerText(); + console.log(`Signed in as ${user}`); + // await page.click('button[aria-label="User dropdown and more options"]'); + // const twitch = await page.locator('[data-a-target="TwitchDisplayName"]').first().innerText(); + // console.log(`Twitch user name is ${twitch}`); + migrateDb(user); // TODO remove this after some time since it will run fine without and people can still use this commit to adjust their data/prime-gaming.json + db.data[user] ||= {}; + + await page.click('button[data-type="Game"]'); + const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; + await page.waitForSelector(games_sel); + console.log('Number of already claimed games (total):', await page.locator(`${games_sel} p:has-text("Collected")`).count()); + const game_sel = `${games_sel} [data-a-target="item-card"]:has-text("Claim game")`; + console.log('Number of free unclaimed games (Prime Gaming):', await page.locator(game_sel).count()); + const games = await page.$$(game_sel); + // for (let i=1; i<=n; i++) { + for (const card of games) { + // const card = page.locator(`:nth-match(${game_sel}, ${i})`); // this will reevaluate after games are claimed and index will be wrong + // const title = await card.locator('h3').first().innerText(); + const title = await (await card.$('.item-card-details__body__primary')).innerText(); + console.log('Current free game:', title); + if (process.env.DRYRUN) continue; + // const img = await (await card.$('img.tw-image')).getAttribute('src'); + // console.log('Image:', img); + const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); + await card.screenshot({ path: p }); + await (await card.$('button:has-text("Claim game")')).click(); + db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; + // await page.pause(); + } + // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft + let n; + const game_sel_ext = `${games_sel} [data-a-target="item-card"]:has(p:text-is("Claim"))`; + do { + n = await page.locator(game_sel_ext).count(); + console.log('Number of free unclaimed games (external stores):', n); + const card = await page.$(game_sel_ext); + if (!card) break; + const title = await (await card.$('.item-card-details__body__primary')).innerText(); + console.log('Current free game:', title); + if (process.env.DRYRUN) continue; + await (await card.$('text=Claim')).click(); + // await page.waitForNavigation(); + await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation + const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); + // Full game for PC [and MAC] on: gog.com, Origin, Legacy Games, EPIC GAMES, Battle.net + // 3 Full PC Games on Legacy Games + const store = store_text.toLowerCase().replace(/.* on /, ''); + console.log(' External store:', store); + if (await page.locator('div:has-text("Link game account")').count()) { + console.error(' Account linking is required to claim this offer!'); + } else { + // print code if there is one + const redeem = { + // 'origin': 'https://www.origin.com/redeem', // TODO still needed or now only via account linking? + 'gog.com': 'https://www.gog.com/redeem', + 'legacy games': 'https://www.legacygames.com/primedeal', + 'microsoft games': 'https://redeem.microsoft.com', + }; + let code; + if (store in redeem) { // did not work for linked origin: && !await page.locator('div:has-text("Successfully Claimed")').count() + code = await page.inputValue('input[type="text"]'); + console.log(' Code to redeem game:', code); + if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ + redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); + } + console.log(' URL to redeem game:', redeem[store]); + } + db.data[user][title] ||= { title, time: datetime(), store, code, url: page.url() }; + // save screenshot of potential code just in case + const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${filenamify(title)}.png`); + await page.screenshot({ path: p, fullPage: true }); + // console.info(' Saved a screenshot of page to', p); + } + // await page.pause(); + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); + await page.click('button[data-type="Game"]'); + } while (n); + const p = path.resolve(dirs.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); + // await page.screenshot({ path: p, fullPage: true }); + await page.locator(games_sel).screenshot({ path: p }); +} catch (error) { + console.error(error); // .toString()? +} finally { + await db.write(); // write out json db +} +await context.close(); From 6305ffd15d4e850bca6b52e5c43575f2ec60c54e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 7 Jan 2023 12:31:23 +0100 Subject: [PATCH 138/520] gog: login, claim (waiting), unsubscribe newsletter --- gog.js | 151 ++++++++++++++------------------------------------------- 1 file changed, 37 insertions(+), 114 deletions(-) diff --git a/gog.js b/gog.js index f56cafd..4e05af7 100644 --- a/gog.js +++ b/gog.js @@ -11,158 +11,81 @@ const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https:/ const show = process.argv.includes('show', 2); const headless = !debug && !show; -// const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? -const URL_CLAIM = 'https://gaming.amazon.com/home'; -const TIMEOUT = 20 * 1000; // 20s, default is 30s +const URL_CLAIM = 'https://www.gog.com/en'; +const TIMEOUT = 0 * 1000; // 20s, default is 30s const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) || 1280; const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; -console.log(datetime(), 'started checking prime-gaming'); +console.log(datetime(), 'started checking gog'); -const db = await jsonDb('prime-gaming.json'); +const db = await jsonDb('gog.json'); db.data ||= {}; -const migrateDb = (user) => { - if (user in db.data || !('claimed' in db.data)) return; - db.data[user] = {}; - for (const e of db.data.claimed) { - db.data[user][e.title] = e; - } - delete db.data.claimed; - delete db.data.runs; -} // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(dirs.browser, { headless, viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, - 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 -> done via /en in URL }); -// TODO test if needed -await stealth(context); - if (!debug) context.setDefaultTimeout(TIMEOUT); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); try { + await context.addCookies([{name: 'CookieConsent', value: '{stamp:%274oR8MJL+bxVlG6g+kl2we5+suMJ+Tv7I4C5d4k+YY4vrnhCD+P23RQ==%27%2Cnecessary:true%2Cpreferences:true%2Cstatistics:true%2Cmarketing:true%2Cmethod:%27explicit%27%2Cver:1%2Cutc:1672331618201%2Cregion:%27de%27}', domain: 'www.gog.com', path: '/'}]); // to not waste screen space when non-headless + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever - // need to wait for some elements to exist before checking if signed in or accepting cookies: - await Promise.any(['button:has-text("Sign in")', '[data-a-target="user-dropdown-first-name-text"]'].map(s => page.waitForSelector(s))); - page.click('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")').catch(_ => { }); // to not waste screen space when non-headless, TODO does not work reliably, need to wait for something else first? - while (await page.locator('button:has-text("Sign in")').count() > 0) { + + // page.click('#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll').catch(_ => { }); // does not work reliably, solved by setting CookieConsent above + // await Promise.any([page.waitForSelector('a:has-text("Sign in")', {}), page.waitForSelector('#menuUsername')]); + while (await page.locator('a:has-text("Sign in")').first().isVisible()) { console.error('Not signed in anymore.'); - await page.click('button:has-text("Sign in")'); + await page.click('a:has-text("Sign in")'); + // it then creates an iframe for the login + await page.waitForSelector('#GalaxyAccountsFrameContainer iframe'); // TODO needed? + const iframe = page.frameLocator('#GalaxyAccountsFrameContainer iframe'); if (!debug) context.setDefaultTimeout(0); // give user time to log in without timeout - console.info('Press ESC to skip if you want to login in the browser (not possible in default headless mode).'); + console.info('Press ESC to skip if you want to login in the browser (not possible in headless mode).'); const email = process.env.EMAIL || await prompt({message: 'Enter email'}); const password = process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); if (email && password) { - await page.fill('[name=email]', email); - await page.fill('[name=password]', password); - await page.check('[name=rememberMe]'); - await page.click('input[type="submit"]'); + iframe.locator('a[href="/logout"]').click().catch(_ => { }); // Click 'Change account' (email from previous login is set in some cookie) + await iframe.locator('#login_username').fill(email); + await iframe.locator('#login_password').fill(password); + await iframe.locator('#login_login').click(); // handle MFA, but don't await it - page.waitForNavigation({ url: '**/ap/mfa**'}).then(async () => { - console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App'); - await page.check('[name=rememberDevice]'); - const otp = await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them - await page.type('input[name=otpCode]', otp.toString()); - await page.click('input[type="submit"]'); + iframe.locator('form[name=second_step_authentication]').waitFor().then(async () => { + console.log('Two-Step Verification - Enter security code'); + console.log(await iframe.locator('.form__description').innerText()) + const otp = await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 4 || 'The code must be 4 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + await iframe.locator('#second_step_authentication_token_letter_1').type(otp.toString(), {delay: 10}); + await iframe.locator('#second_step_authentication_send').click(); + await page.waitForTimeout(1000); // TODO wait for something else below? }); } else { if (headless) { - console.log('Please run `node prime-gaming show` to login in the opened browser.'); + console.log('Please run `node gog show` to login in the opened browser.'); await context.close(); // not needed? process.exit(1); } console.log('Waiting for you to login in the browser.'); } - await page.waitForNavigation({ url: 'https://gaming.amazon.com/home?signedIn=true' }); + // await page.waitForNavigation(); // TODO was blocking if (!debug) context.setDefaultTimeout(TIMEOUT); } - const user = await page.locator('[data-a-target="user-dropdown-first-name-text"]').first().innerText(); + const user = await page.locator('#menuUsername').first().innerHTML(); console.log(`Signed in as ${user}`); - // await page.click('button[aria-label="User dropdown and more options"]'); - // const twitch = await page.locator('[data-a-target="TwitchDisplayName"]').first().innerText(); - // console.log(`Twitch user name is ${twitch}`); - migrateDb(user); // TODO remove this after some time since it will run fine without and people can still use this commit to adjust their data/prime-gaming.json db.data[user] ||= {}; - await page.click('button[data-type="Game"]'); - const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; - await page.waitForSelector(games_sel); - console.log('Number of already claimed games (total):', await page.locator(`${games_sel} p:has-text("Collected")`).count()); - const game_sel = `${games_sel} [data-a-target="item-card"]:has-text("Claim game")`; - console.log('Number of free unclaimed games (Prime Gaming):', await page.locator(game_sel).count()); - const games = await page.$$(game_sel); - // for (let i=1; i<=n; i++) { - for (const card of games) { - // const card = page.locator(`:nth-match(${game_sel}, ${i})`); // this will reevaluate after games are claimed and index will be wrong - // const title = await card.locator('h3').first().innerText(); - const title = await (await card.$('.item-card-details__body__primary')).innerText(); - console.log('Current free game:', title); - if (process.env.DRYRUN) continue; - // const img = await (await card.$('img.tw-image')).getAttribute('src'); - // console.log('Image:', img); - const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); - await card.screenshot({ path: p }); - await (await card.$('button:has-text("Claim game")')).click(); - db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; - // await page.pause(); - } - // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft - let n; - const game_sel_ext = `${games_sel} [data-a-target="item-card"]:has(p:text-is("Claim"))`; - do { - n = await page.locator(game_sel_ext).count(); - console.log('Number of free unclaimed games (external stores):', n); - const card = await page.$(game_sel_ext); - if (!card) break; - const title = await (await card.$('.item-card-details__body__primary')).innerText(); - console.log('Current free game:', title); - if (process.env.DRYRUN) continue; - await (await card.$('text=Claim')).click(); - // await page.waitForNavigation(); - await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation - const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); - // Full game for PC [and MAC] on: gog.com, Origin, Legacy Games, EPIC GAMES, Battle.net - // 3 Full PC Games on Legacy Games - const store = store_text.toLowerCase().replace(/.* on /, ''); - console.log(' External store:', store); - if (await page.locator('div:has-text("Link game account")').count()) { - console.error(' Account linking is required to claim this offer!'); - } else { - // print code if there is one - const redeem = { - // 'origin': 'https://www.origin.com/redeem', // TODO still needed or now only via account linking? - 'gog.com': 'https://www.gog.com/redeem', - 'legacy games': 'https://www.legacygames.com/primedeal', - 'microsoft games': 'https://redeem.microsoft.com', - }; - let code; - if (store in redeem) { // did not work for linked origin: && !await page.locator('div:has-text("Successfully Claimed")').count() - code = await page.inputValue('input[type="text"]'); - console.log(' Code to redeem game:', code); - if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ - redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); - } - console.log(' URL to redeem game:', redeem[store]); - } - db.data[user][title] ||= { title, time: datetime(), store, code, url: page.url() }; - // save screenshot of potential code just in case - const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${filenamify(title)}.png`); - await page.screenshot({ path: p, fullPage: true }); - // console.info(' Saved a screenshot of page to', p); - } - // await page.pause(); - await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); - await page.click('button[data-type="Game"]'); - } while (n); - const p = path.resolve(dirs.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); - // await page.screenshot({ path: p, fullPage: true }); - await page.locator(games_sel).screenshot({ path: p }); + console.log('TODO get title of current game (waiting for next offer)'); + await page.goto('https://www.gog.com/giveaway/claim'); + console.log(await page.innerText('body')); + + console.log("Unsubscribe from 'Promotions and hot deals' newsletter"); + await page.goto('https://www.gog.com/en/account/settings/subscriptions'); + await page.locator('li:has-text("Promotions and hot deals") input').uncheck(); } catch (error) { console.error(error); // .toString()? } finally { From cf9c31e544dd21a2a31094974543dea52de46b88 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 7 Jan 2023 12:32:55 +0100 Subject: [PATCH 139/520] eg: cookie comment --- epic-games.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index b943ae7..658490d 100644 --- a/epic-games.js +++ b/epic-games.js @@ -63,12 +63,11 @@ const page = context.pages().length ? context.pages()[0] : await context.newPage // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); try { - await context.addCookies([{name: 'OptanonAlertBoxClosed', value: '2022-10-06T21:15:28.081Z', domain: '.epicgames.com', path: '/'}]); + await context.addCookies([{name: 'OptanonAlertBoxClosed', value: '2022-10-06T21:15:28.081Z', domain: '.epicgames.com', path: '/'}]); // Accept cookies to get rid of banner to save space on screen. await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto - // Accept cookies to get rid of banner to save space on screen. Clicking this did not always work since the message was animated in too slowly. - // page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // not needed anymore since we set the cookie above + // page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // Not needed anymore since we set the cookie above. Clicking this did not always work since the message was animated in too slowly. while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); From cc183a6303e14e3ae60ac7530eb0cbf5d1612a17 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 7 Jan 2023 12:40:25 +0100 Subject: [PATCH 140/520] eg: set cookie accept time to 5 days ago instead of a static value --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 658490d..b1b97c9 100644 --- a/epic-games.js +++ b/epic-games.js @@ -63,7 +63,7 @@ const page = context.pages().length ? context.pages()[0] : await context.newPage // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); try { - await context.addCookies([{name: 'OptanonAlertBoxClosed', value: '2022-10-06T21:15:28.081Z', domain: '.epicgames.com', path: '/'}]); // Accept cookies to get rid of banner to save space on screen. + await context.addCookies([{name: 'OptanonAlertBoxClosed', value: new Date(Date.now() - 5*24*60*60*1000).toISOString(), domain: '.epicgames.com', path: '/'}]); // Accept cookies to get rid of banner to save space on screen. Set accept time to 5 days ago. await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto From 09c3e57a8adc74a66216f322578239cb447f8bcf Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 7 Jan 2023 13:47:08 +0100 Subject: [PATCH 141/520] use SHOW for pg and eg, headless by default for both, but show inside Docker --- Dockerfile | 3 +++ epic-games.js | 4 +++- prime-gaming.js | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index ceeabf5..4aa43c8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -56,6 +56,9 @@ ENV SCREEN_WIDTH 1280 ENV SCREEN_HEIGHT 1280 ENV SCREEN_DEPTH 24 +# Show browser instead of running headless +ENV SHOW 1 + # Script to setup display server & VNC is always executed. ENTRYPOINT ["entrypoint"] # Default command to run. This is replaced by appending own command, e.g. `docker run ... node prime-gaming` to only run this script. diff --git a/epic-games.js b/epic-games.js index b1b97c9..e2562fb 100644 --- a/epic-games.js +++ b/epic-games.js @@ -9,6 +9,8 @@ import prompts from 'prompts'; // alternatives: enquirer, inquirer const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector +const show = process.env.SHOW == '1'; +const headless = !debug && !show; const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; @@ -38,7 +40,7 @@ const ext = path.resolve('nopecha'); // used in Chromium, currently not needed i const context = await firefox.launchPersistentContext(dirs.browser, { // chrome will not work in linux arm64, only chromium // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge - headless: false, + headless, viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 diff --git a/prime-gaming.js b/prime-gaming.js index f56cafd..e77d7d1 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -8,7 +8,7 @@ import prompts from 'prompts'; // alternatives: enquirer, inquirer const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector -const show = process.argv.includes('show', 2); +const show = process.env.SHOW == '1'; const headless = !debug && !show; // const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? From 577fd84a7c4c9aef94636b043c10ba4fdfa75f9f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 7 Jan 2023 13:47:53 +0100 Subject: [PATCH 142/520] update README, explain options via env vars, TODOs --- README.md | 70 +++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a92c018..e47f71b 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Claims free games periodically on - [Epic Games Store](https://www.epicgames.com/store/free-games) - [Amazon Prime Gaming](https://gaming.amazon.com) -- [GOG](https://www.gog.com) - planned +- [GOG](https://www.gog.com) - WIP - [Xbox Live Games with Gold](https://www.xbox.com/en-US/live/gold#gameswithgold) - planned Pull requests welcome :) @@ -26,47 +26,81 @@ Data is stored in the volume `fgc`. This downloads 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`. + +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.
## Usage -Both scripts start an automated Firefox instance, either with the browser GUI shown or hidden (*headless mode*). +Both scripts start an automated Firefox instance, either with the browser GUI shown or hidden (*headless mode*). By default, you won't see any browser open on your host system. -Login has to be done in the browser. It's hard to automate since you usually need to enter some OTP (but you can select 'remember this device'). -After login, the script will just continue, but you can also restart it. +- When running inside Docker, the browser will be shown only inside the Container. You can open http://localhost:6080 to interact with the browser running inside the container via noVNC (or use other VNC clients on port 5900). +- When running the scripts outside of Docker, the browser will be hidden by default; you can use `SHOW=1 ...` to show the UI (see options below). -If something goes wrong, use `PWDEBUG=1 node ...` to [inspect](https://playwright.dev/docs/inspector). +When running the first time, you have to login for each store you want to claim games on. +You can login indirectly via the terminal or directly in the browser. The scripts will wait until you are successfully logged in. + +There will be prompts in the terminal asking you to enter email, password, and afterwards some OTP (one time password / security code) if you have 2FA/MFA (two-/multi-factor authentication) enabled. If you want to login yourself via the browser, you can just hit Escape to skip the prompts. + +After login, the script will just continue claiming the current games. If it still waits after you are already logged in, you can restart it (and open an issue). + +### Options +Options are set via [environment variables](https://kinsta.com/knowledgebase/what-is-an-environment-variable/) which can be set in many ways and allow for flexible configuration. + +TODO: On the first run, the script will guide you through configuration and save all settings to a `.env` file. You can edit this file directly or run `node fgc config` to run the configuration assistant again. + +The available options/variables and their default values are: + +| Option | Default | Description | +|--------------- |--------- |------------------------------------------------------------------------ | +| SHOW | 1 | Show browser if 1. Default for Docker, not shown when running outside. | +| SCREEN_WIDTH | 1280 | Width of the opened browser (and screen vor VNC in Docker). | +| SCREEN_HEIGHT | 1280 | Height of the opened browser (and screen vor VNC in Docker). | +| VNC_PASSWORD | | VNC password for Docker. No password used by default! | +| EMAIL | | Default email for any login. | +| PASSWORD | | Default password for any login. | +| EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | +| EG_PASSWORD | | Epic Games password for login. Overrides PASSWORD. | +| PG_EMAIL | | Prime Gaming email for login. Overrides EMAIL. | +| PG_PASSWORD | | Prime Gaming password for login. Overrides PASSWORD. | + +#### Other ways to set options +On Linux/macOS you can prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. +For Docker you can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@bar.baz ...` or using `--env-file` (see [docs](https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file)). If you are using [docker compose](https://docs.docker.com/compose/environment-variables/), you can put them in the `environment:` section. ### Epic Games Store -Options: -- Run `node epic-games` (browser window will open, [headless leads to captcha](https://github.com/vogler/free-games-claimer/issues/2)) -- Run inside Docker (browser is hidden, headless for host): - - [Install Docker](https://docs.docker.com/get-docker/) - - Options: - - `docker run` command from above - - `npm run docker` which does the same but stores files in `./data` instead of a Docker volume. - - `docker compose up` - - When you need to login, go to http://localhost:6080 (you can also connect with a VNC client on port 5900) +Run `node epic-games` (locally or in Docker). ### Amazon Prime Gaming Run `node prime-gaming` (locally or in Docker). -Runs headless. Run `node prime-gaming show` to show the GUI (to login). - Claiming the Amazon Games works, external Epic Games also work if the account is linked. -Keys for {Origin, GOG.com, Legacy Games} should be printed to the console and need to be redeemed manually at the URL printed to the terminal ([issue](https://github.com/vogler/free-games-claimer/issues/5)). +Keys for {Origin, GOG.com, Legacy Games} are printed to the console and need to be redeemed manually at the URL printed to the terminal ([issue](https://github.com/vogler/free-games-claimer/issues/5)). A screenshot of the page with the code is saved to `data/screenshots` as well. ### Run periodically +#### How often? Epic Games usually has two free games *every week*, before Christmas every day. Prime Gaming has new games *every month* or more often during Prime days. It is save to run both scripts every day. -If you can't use Docker for quasi-headless mode, you could run in a virtual machine, on a server, or you wake your PC at night to avoid being interrupted. + +#### How to schedule? +The container/scripts will claim currently available games and then exit. +If you want it to run regularly, you have to schedule the runs yourself. + +TODO: add some server-mode where the script just keeps running and claims games e.g. every day. - Linux/macOS: `crontab -e` - macOS: [launchd](https://stackoverflow.com/questions/132955/how-do-i-set-a-task-to-run-every-so-often) - Windows: [task scheduler](https://active-directory-wp.com/docs/Usage/How_to_add_a_cron_job_on_Windows/Scheduled_tasks_and_cron_jobs_on_Windows/index.html), [other options](https://stackoverflow.com/questions/132971/what-is-the-windows-version-of-cron) +### Problems? + +Check the open [issues](https://github.com/vogler/free-games-claimer/issues) and comment there or open a new issue. + +If you're a developer, you can use `PWDEBUG=1 ...` to [inspect](https://playwright.dev/docs/inspector) which opens a debugger where you can step through the script. + + ## History/DevLog
Click to expand From 792b4b3915477f7e5ba01ae953566340f776f069 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 7 Jan 2023 13:55:31 +0100 Subject: [PATCH 143/520] options: store-specific logins, drop SCREEN_ --- Dockerfile | 6 +++--- README.md | 6 ++++-- docker/entrypoint.sh | 4 ++-- epic-games.js | 10 +++++----- gog.js | 12 ++++++------ prime-gaming.js | 10 +++++----- 6 files changed, 25 insertions(+), 23 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4aa43c8..1ef665d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -52,9 +52,9 @@ EXPOSE 5900 EXPOSE 6080 # Configure Xvfb via environment variables: -ENV SCREEN_WIDTH 1280 -ENV SCREEN_HEIGHT 1280 -ENV SCREEN_DEPTH 24 +ENV WIDTH 1280 +ENV HEIGHT 1280 +ENV DEPTH 24 # Show browser instead of running headless ENV SHOW 1 diff --git a/README.md b/README.md index e47f71b..f6e78f1 100644 --- a/README.md +++ b/README.md @@ -53,8 +53,8 @@ The available options/variables and their default values are: | Option | Default | Description | |--------------- |--------- |------------------------------------------------------------------------ | | SHOW | 1 | Show browser if 1. Default for Docker, not shown when running outside. | -| SCREEN_WIDTH | 1280 | Width of the opened browser (and screen vor VNC in Docker). | -| SCREEN_HEIGHT | 1280 | Height of the opened browser (and screen vor VNC in Docker). | +| WIDTH | 1280 | Width of the opened browser (and screen vor VNC in Docker). | +| HEIGHT | 1280 | Height of the opened browser (and screen vor VNC in Docker). | | VNC_PASSWORD | | VNC password for Docker. No password used by default! | | EMAIL | | Default email for any login. | | PASSWORD | | Default password for any login. | @@ -62,6 +62,8 @@ The available options/variables and their default values are: | EG_PASSWORD | | Epic Games password for login. Overrides PASSWORD. | | PG_EMAIL | | Prime Gaming email for login. Overrides EMAIL. | | PG_PASSWORD | | Prime Gaming password for login. Overrides PASSWORD. | +| GOG_EMAIL | | GOG email for login. Overrides EMAIL. | +| GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | #### Other ways to set options On Linux/macOS you can prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index 625bba3..0fa7de8 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -19,8 +19,8 @@ rm -f /tmp/.X1-lock # −screen NUM WxHxD creates the screen and sets its width, height, and depth export DISPLAY=:1 # need to export this, otherwise playwright complains with 'Looks like you launched a headed browser without having a XServer running.' -Xvfb $DISPLAY -ac -screen 0 "${SCREEN_WIDTH}x${SCREEN_HEIGHT}x${SCREEN_DEPTH}" & -echo "Xvfb display server created screen with resolution ${SCREEN_WIDTH}x${SCREEN_HEIGHT}" +Xvfb $DISPLAY -ac -screen 0 "${WIDTH}x${HEIGHT}x${DEPTH}" & +echo "Xvfb display server created screen with resolution ${WIDTH}x${HEIGHT}" x11vnc -display $DISPLAY -forever -shared -rfbport $VNC_PORT -bg -nopw 2>/dev/null 1>&2 # -passwd "${VNC_PASSWORD}" echo "VNC is running on port $VNC_PORT (no password!)" websockify -D --web "/usr/share/novnc/" $NOVNC_PORT "localhost:$VNC_PORT" 2>/dev/null 1>&2 & diff --git a/epic-games.js b/epic-games.js index e2562fb..675bb89 100644 --- a/epic-games.js +++ b/epic-games.js @@ -15,8 +15,8 @@ const headless = !debug && !show; const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; const TIMEOUT = 20 * 1000; // 20s, default is 30s -const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) || 1280; -const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; +const WIDTH = Number(process.env.WIDTH) || 1280; +const HEIGHT = Number(process.env.HEIGHT) || 1280; console.log(datetime(), 'started checking epic-games'); @@ -41,7 +41,7 @@ const context = await firefox.launchPersistentContext(dirs.browser, { // chrome will not work in linux arm64, only chromium // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge headless, - viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, + viewport: { width: WIDTH, height: HEIGHT }, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 locale: "en-US", // ignore OS locale to be sure to have english text for locators @@ -78,8 +78,8 @@ try { await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); console.info('Press ESC to skip if you want to login in the browser.'); - const email = process.env.EMAIL || await prompt({message: 'Enter email'}); - const password = process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); + const email = process.env.EG_EMAIL || process.env.EMAIL || await prompt({message: 'Enter email'}); + const password = process.env.EG_PASSWORD || process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); if (email && password) { await page.click('text=Sign in with Epic Games'); await page.fill('#email', email); diff --git a/gog.js b/gog.js index 4e05af7..75afc9c 100644 --- a/gog.js +++ b/gog.js @@ -12,9 +12,9 @@ const show = process.argv.includes('show', 2); const headless = !debug && !show; const URL_CLAIM = 'https://www.gog.com/en'; -const TIMEOUT = 0 * 1000; // 20s, default is 30s -const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) || 1280; -const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; +const TIMEOUT = 20 * 1000; // 20s, default is 30s +const WIDTH = Number(process.env.WIDTH) || 1280; +const HEIGHT = Number(process.env.HEIGHT) || 1280; console.log(datetime(), 'started checking gog'); @@ -24,7 +24,7 @@ db.data ||= {}; // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(dirs.browser, { headless, - viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, + viewport: { width: WIDTH, height: HEIGHT }, locale: "en-US", // ignore OS locale to be sure to have english text for locators -> done via /en in URL }); @@ -48,8 +48,8 @@ try { const iframe = page.frameLocator('#GalaxyAccountsFrameContainer iframe'); if (!debug) context.setDefaultTimeout(0); // give user time to log in without timeout console.info('Press ESC to skip if you want to login in the browser (not possible in headless mode).'); - const email = process.env.EMAIL || await prompt({message: 'Enter email'}); - const password = process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); + const email = process.env.GOG_EMAIL || process.env.EMAIL || await prompt({message: 'Enter email'}); + const password = process.env.GOG_PASSWORD || process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); if (email && password) { iframe.locator('a[href="/logout"]').click().catch(_ => { }); // Click 'Change account' (email from previous login is set in some cookie) await iframe.locator('#login_username').fill(email); diff --git a/prime-gaming.js b/prime-gaming.js index e77d7d1..26c2eaa 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -14,8 +14,8 @@ const headless = !debug && !show; // const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? const URL_CLAIM = 'https://gaming.amazon.com/home'; const TIMEOUT = 20 * 1000; // 20s, default is 30s -const SCREEN_WIDTH = Number(process.env.SCREEN_WIDTH) || 1280; -const SCREEN_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280; +const WIDTH = Number(process.env.WIDTH) || 1280; +const HEIGHT = Number(process.env.HEIGHT) || 1280; console.log(datetime(), 'started checking prime-gaming'); @@ -34,7 +34,7 @@ const migrateDb = (user) => { // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(dirs.browser, { headless, - viewport: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }, + viewport: { width: WIDTH, height: HEIGHT }, locale: "en-US", // ignore OS locale to be sure to have english text for locators }); @@ -56,8 +56,8 @@ try { await page.click('button:has-text("Sign in")'); if (!debug) context.setDefaultTimeout(0); // give user time to log in without timeout console.info('Press ESC to skip if you want to login in the browser (not possible in default headless mode).'); - const email = process.env.EMAIL || await prompt({message: 'Enter email'}); - const password = process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); + const email = process.env.PG_EMAIL || process.env.EMAIL || await prompt({message: 'Enter email'}); + const password = process.env.PG_PASSWORD || process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); if (email && password) { await page.fill('[name=email]', email); await page.fill('[name=password]', password); From af0c9a6f2d6f357f6ab00ba6eca29d103d1e5e48 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 9 Jan 2023 10:26:10 +0100 Subject: [PATCH 144/520] lowdb: fix for 'Can't import JSONFile' --- jsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsconfig.json b/jsconfig.json index 38451e9..1b438cb 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -3,7 +3,7 @@ "checkJs": true, "target": "es2021", "module": "esnext", - "moduleResolution": "node" + "moduleResolution": "NodeNext", // https://github.com/typicode/lowdb/issues/554 }, "exclude": ["node_modules", "**/node_modules"] } From 2168c40aa5bb739c7a1a25606fbd4e510b59506a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 9 Jan 2023 10:47:19 +0100 Subject: [PATCH 145/520] centralize env vars in config.js --- config.js | 19 +++++++++++++++++++ epic-games.js | 28 +++++++++++----------------- gog.js | 24 +++++++++--------------- prime-gaming.js | 28 +++++++++++----------------- 4 files changed, 50 insertions(+), 49 deletions(-) create mode 100644 config.js diff --git a/config.js b/config.js new file mode 100644 index 0000000..51e4dcb --- /dev/null +++ b/config.js @@ -0,0 +1,19 @@ +// import * as dotenv from 'dotenv'; +// dotenv.config({ path: 'data/config.env' }); + +export const cfg = { + debug: process.env.PWDEBUG == '1', // runs non-headless and opens https://playwright.dev/docs/inspector + dryrun: process.env.DRYRUN == '1', // don't claim anything + show: process.env.SHOW == '1', // run non-headless + get headless() { return !this.debug && !this.show }, + width: Number(process.env.WIDTH) || 1280, + height: Number(process.env.HEIGHT) || 1280, + timeout: (Number(process.env.TIMEOUT) || 20) * 1000, // 20s, default for playwright is 30s + novnc_port: process.env.NOVNC_PORT, + eg_email: process.env.EG_EMAIL || process.env.EMAIL, + eg_password: process.env.EG_PASSWORD || process.env.PASSWORD, + pg_email: process.env.PG_EMAIL || process.env.EMAIL, + pg_password: process.env.PG_PASSWORD || process.env.PASSWORD, + gog_email: process.env.GOG_EMAIL || process.env.EMAIL, + gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD, +}; diff --git a/epic-games.js b/epic-games.js index 675bb89..fb1a604 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,6 +1,7 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; +import { cfg } from './config.js'; import { existsSync, writeFileSync } from 'fs'; import prompts from 'prompts'; // alternatives: enquirer, inquirer @@ -8,15 +9,8 @@ import prompts from 'prompts'; // alternatives: enquirer, inquirer // single prompt that just returns the non-empty value instead of an object - why name things if there's just one? const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; -const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector -const show = process.env.SHOW == '1'; -const headless = !debug && !show; - const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; -const TIMEOUT = 20 * 1000; // 20s, default is 30s -const WIDTH = Number(process.env.WIDTH) || 1280; -const HEIGHT = Number(process.env.HEIGHT) || 1280; console.log(datetime(), 'started checking epic-games'); @@ -40,8 +34,8 @@ const ext = path.resolve('nopecha'); // used in Chromium, currently not needed i const context = await firefox.launchPersistentContext(dirs.browser, { // chrome will not work in linux arm64, only chromium // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge - headless, - viewport: { width: WIDTH, height: HEIGHT }, + headless: cfg.headless, + viewport: { width: cfg.width, height: cfg.height }, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 locale: "en-US", // ignore OS locale to be sure to have english text for locators @@ -59,7 +53,7 @@ const context = await firefox.launchPersistentContext(dirs.browser, { // 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); -if (!debug) context.setDefaultTimeout(TIMEOUT); +if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); @@ -73,13 +67,13 @@ try { while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); - if (process.env.NOVNC_PORT) console.info(`Open http://localhost:${process.env.NOVNC_PORT} to login inside the docker container.`); + if (cfg.novnc_port) console.info(`Open http://localhost:${cfg.novnc_port} to login inside the docker container.`); context.setDefaultTimeout(0); // give user time to log in without timeout await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); console.info('Press ESC to skip if you want to login in the browser.'); - const email = process.env.EG_EMAIL || process.env.EMAIL || await prompt({message: 'Enter email'}); - const password = process.env.EG_PASSWORD || process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); + const email = cfg.eg_email || await prompt({message: 'Enter email'}); + const password = cfg.eg_password || await prompt({type: 'password', message: 'Enter password'}); if (email && password) { await page.click('text=Sign in with Epic Games'); await page.fill('#email', email); @@ -99,7 +93,7 @@ try { console.log('Waiting for you to login in the browser.'); } await page.waitForNavigation({ url: URL_CLAIM }); - context.setDefaultTimeout(TIMEOUT); + context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('#user span').first().innerHTML(); console.log(`Signed in as ${user}`); @@ -143,8 +137,8 @@ try { // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoided by Windows userAgent? page.click('button:has-text("Continue")').catch(_ => { }); // needed since change from Chromium to Firefox? - if (process.env.DRYRUN) continue; - if (debug) await page.pause(); + if (cfg.dryrun) continue; + if (cfg.debug) await page.pause(); // it then creates an iframe for the purchase await page.waitForSelector('#webPurchaseContainer iframe'); // TODO needed? @@ -170,7 +164,7 @@ try { db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed time console.log(' Claimed successfully!'); - context.setDefaultTimeout(TIMEOUT); + context.setDefaultTimeout(cfg.timeout); } catch (e) { console.log(e); console.error(' Failed to claim! Try again if NopeCHA timed out. Click the extension to see if you ran out of credits (refill after 24h). To avoid captchas try to get a new IP or set a cookie from https://www.hcaptcha.com/accessibility'); diff --git a/gog.js b/gog.js index 75afc9c..4e2e0bf 100644 --- a/gog.js +++ b/gog.js @@ -1,20 +1,14 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; +import { cfg } from './config.js'; import prompts from 'prompts'; // alternatives: enquirer, inquirer // import enquirer from 'enquirer'; const { prompt } = enquirer; // single prompt that just returns the non-empty value instead of an object - why name things if there's just one? const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; -const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector -const show = process.argv.includes('show', 2); -const headless = !debug && !show; - const URL_CLAIM = 'https://www.gog.com/en'; -const TIMEOUT = 20 * 1000; // 20s, default is 30s -const WIDTH = Number(process.env.WIDTH) || 1280; -const HEIGHT = Number(process.env.HEIGHT) || 1280; console.log(datetime(), 'started checking gog'); @@ -23,12 +17,12 @@ db.data ||= {}; // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(dirs.browser, { - headless, - viewport: { width: WIDTH, height: HEIGHT }, + headless: cfg.headless, + viewport: { width: cfg.width, height: cfg.height }, locale: "en-US", // ignore OS locale to be sure to have english text for locators -> done via /en in URL }); -if (!debug) context.setDefaultTimeout(TIMEOUT); +if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); @@ -46,10 +40,10 @@ try { // it then creates an iframe for the login await page.waitForSelector('#GalaxyAccountsFrameContainer iframe'); // TODO needed? const iframe = page.frameLocator('#GalaxyAccountsFrameContainer iframe'); - if (!debug) context.setDefaultTimeout(0); // give user time to log in without timeout + if (!cfg.debug) context.setDefaultTimeout(0); // give user time to log in without timeout console.info('Press ESC to skip if you want to login in the browser (not possible in headless mode).'); - const email = process.env.GOG_EMAIL || process.env.EMAIL || await prompt({message: 'Enter email'}); - const password = process.env.GOG_PASSWORD || process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); + const email = cfg.gog_email || await prompt({message: 'Enter email'}); + const password = cfg.gog_password || await prompt({type: 'password', message: 'Enter password'}); if (email && password) { iframe.locator('a[href="/logout"]').click().catch(_ => { }); // Click 'Change account' (email from previous login is set in some cookie) await iframe.locator('#login_username').fill(email); @@ -65,7 +59,7 @@ try { await page.waitForTimeout(1000); // TODO wait for something else below? }); } else { - if (headless) { + if (cfg.headless) { console.log('Please run `node gog show` to login in the opened browser.'); await context.close(); // not needed? process.exit(1); @@ -73,7 +67,7 @@ try { console.log('Waiting for you to login in the browser.'); } // await page.waitForNavigation(); // TODO was blocking - if (!debug) context.setDefaultTimeout(TIMEOUT); + if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('#menuUsername').first().innerHTML(); console.log(`Signed in as ${user}`); diff --git a/prime-gaming.js b/prime-gaming.js index 26c2eaa..940cc9e 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,21 +1,15 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; +import { cfg } from './config.js'; import prompts from 'prompts'; // alternatives: enquirer, inquirer // import enquirer from 'enquirer'; const { prompt } = enquirer; // single prompt that just returns the non-empty value instead of an object - why name things if there's just one? const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; -const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector -const show = process.env.SHOW == '1'; -const headless = !debug && !show; - // const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? const URL_CLAIM = 'https://gaming.amazon.com/home'; -const TIMEOUT = 20 * 1000; // 20s, default is 30s -const WIDTH = Number(process.env.WIDTH) || 1280; -const HEIGHT = Number(process.env.HEIGHT) || 1280; console.log(datetime(), 'started checking prime-gaming'); @@ -33,15 +27,15 @@ const migrateDb = (user) => { // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(dirs.browser, { - headless, - viewport: { width: WIDTH, height: HEIGHT }, + headless: cfg.headless, + viewport: { width: cfg.width, height: cfg.height }, locale: "en-US", // ignore OS locale to be sure to have english text for locators }); // TODO test if needed await stealth(context); -if (!debug) context.setDefaultTimeout(TIMEOUT); +if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); @@ -54,10 +48,10 @@ try { while (await page.locator('button:has-text("Sign in")').count() > 0) { console.error('Not signed in anymore.'); await page.click('button:has-text("Sign in")'); - if (!debug) context.setDefaultTimeout(0); // give user time to log in without timeout + if (!cfg.debug) context.setDefaultTimeout(0); // give user time to log in without timeout console.info('Press ESC to skip if you want to login in the browser (not possible in default headless mode).'); - const email = process.env.PG_EMAIL || process.env.EMAIL || await prompt({message: 'Enter email'}); - const password = process.env.PG_PASSWORD || process.env.PASSWORD || await prompt({type: 'password', message: 'Enter password'}); + const email = cfg.pg_email || await prompt({message: 'Enter email'}); + const password = cfg.pg_password || await prompt({type: 'password', message: 'Enter password'}); if (email && password) { await page.fill('[name=email]', email); await page.fill('[name=password]', password); @@ -72,7 +66,7 @@ try { await page.click('input[type="submit"]'); }); } else { - if (headless) { + if (cfg.headless) { console.log('Please run `node prime-gaming show` to login in the opened browser.'); await context.close(); // not needed? process.exit(1); @@ -80,7 +74,7 @@ try { console.log('Waiting for you to login in the browser.'); } await page.waitForNavigation({ url: 'https://gaming.amazon.com/home?signedIn=true' }); - if (!debug) context.setDefaultTimeout(TIMEOUT); + if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('[data-a-target="user-dropdown-first-name-text"]').first().innerText(); console.log(`Signed in as ${user}`); @@ -103,7 +97,7 @@ try { // const title = await card.locator('h3').first().innerText(); const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); - if (process.env.DRYRUN) continue; + if (cfg.dryrun) continue; // const img = await (await card.$('img.tw-image')).getAttribute('src'); // console.log('Image:', img); const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); @@ -122,7 +116,7 @@ try { if (!card) break; const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); - if (process.env.DRYRUN) continue; + if (cfg.dryrun) continue; await (await card.$('text=Claim')).click(); // await page.waitForNavigation(); await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation From 6a7594fa320fc4ef3d97fda71c658eeefc4c842a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 9 Jan 2023 10:56:18 +0100 Subject: [PATCH 146/520] use dotenv for loading env vars from data/config.env --- README.md | 6 ++++-- config.js | 11 ++++++----- package-lock.json | 14 ++++++++++++++ package.json | 1 + 4 files changed, 25 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index f6e78f1..49d06ac 100644 --- a/README.md +++ b/README.md @@ -46,9 +46,9 @@ After login, the script will just continue claiming the current games. If it sti ### Options Options are set via [environment variables](https://kinsta.com/knowledgebase/what-is-an-environment-variable/) which can be set in many ways and allow for flexible configuration. -TODO: On the first run, the script will guide you through configuration and save all settings to a `.env` file. You can edit this file directly or run `node fgc config` to run the configuration assistant again. +TODO: On the first run, the script will guide you through configuration and save all settings to `data/config.env`. You can edit this file directly or run `node fgc config` to run the configuration assistant again. -The available options/variables and their default values are: +Available options/variables and their default values: | Option | Default | Description | |--------------- |--------- |------------------------------------------------------------------------ | @@ -65,6 +65,8 @@ The available options/variables and their default values are: | GOG_EMAIL | | GOG email for login. Overrides EMAIL. | | GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | +See `config.js` for all options. + #### Other ways to set options On Linux/macOS you can prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. For Docker you can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@bar.baz ...` or using `--env-file` (see [docs](https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file)). If you are using [docker compose](https://docs.docker.com/compose/environment-variables/), you can put them in the `environment:` section. diff --git a/config.js b/config.js index 51e4dcb..e0873b5 100644 --- a/config.js +++ b/config.js @@ -1,15 +1,16 @@ -// import * as dotenv from 'dotenv'; -// dotenv.config({ path: 'data/config.env' }); +import * as dotenv from 'dotenv'; +dotenv.config({ path: 'data/config.env' }); // loads env vars from file - will not set vars that are already set, i.e., can overwrite values from file by prefixing, e.g., VAR=VAL node ... +// Options - also see table in README.md export const cfg = { debug: process.env.PWDEBUG == '1', // runs non-headless and opens https://playwright.dev/docs/inspector dryrun: process.env.DRYRUN == '1', // don't claim anything show: process.env.SHOW == '1', // run non-headless get headless() { return !this.debug && !this.show }, - width: Number(process.env.WIDTH) || 1280, - height: Number(process.env.HEIGHT) || 1280, + width: Number(process.env.WIDTH) || 1280, // width of the opened browser + height: Number(process.env.HEIGHT) || 1280, // height of the opened browser timeout: (Number(process.env.TIMEOUT) || 20) * 1000, // 20s, default for playwright is 30s - novnc_port: process.env.NOVNC_PORT, + novnc_port: process.env.NOVNC_PORT, // running in docker if set eg_email: process.env.EG_EMAIL || process.env.EMAIL, eg_password: process.env.EG_PASSWORD || process.env.PASSWORD, pg_email: process.env.PG_EMAIL || process.env.EMAIL, diff --git a/package-lock.json b/package-lock.json index cd6b762..5f5954b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "cross-env": "^7.0.3", + "dotenv": "^16.0.3", "lowdb": "^5.0.5", "playwright": "^1.29.0", "prompts": "^2.4.2", @@ -125,6 +126,14 @@ "node": ">=0.10.0" } }, + "node_modules/dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "engines": { + "node": ">=12" + } + }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -693,6 +702,11 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", diff --git a/package.json b/package.json index e2d288b..7720bf5 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "type": "module", "dependencies": { "cross-env": "^7.0.3", + "dotenv": "^16.0.3", "lowdb": "^5.0.5", "playwright": "^1.29.0", "prompts": "^2.4.2", From 84d4b9b7bc6384796e658c9e100fd41a7770bdc6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 9 Jan 2023 20:21:29 +0100 Subject: [PATCH 147/520] docker compose comments, only noVNC by default, use fgc volume as in README --- docker-compose.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index dc85bcd..7c16500 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,11 +1,11 @@ -# docker compose up +# start with `docker compose up` services: free-games-claimer: container_name: fgc # is printed in front of every output line image: ghcr.io/vogler/free-games-claimer # otherwise image name will be free-games-claimer-free-games-claimer build: . ports: - - "5900:5900" - - "6080:6080" + # - "5900:5900" # VNC server + - "6080:6080" # noVNC (browser-based VNC client) volumes: - - ./data:/fgc/data + - fgc:/fgc/data From 72d8550c868267b0d66f0f88408c65350f46797a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 9 Jan 2023 20:37:13 +0100 Subject: [PATCH 148/520] docker/entrypoint.sh -> docker-entrypoint.sh --- Dockerfile | 9 ++++----- docker/entrypoint.sh => docker-entrypoint.sh | 0 2 files changed, 4 insertions(+), 5 deletions(-) rename docker/entrypoint.sh => docker-entrypoint.sh (100%) diff --git a/Dockerfile b/Dockerfile index 1ef665d..b201581 100644 --- a/Dockerfile +++ b/Dockerfile @@ -40,10 +40,9 @@ RUN npm install COPY . . -# Shell scripts need Linux line endings. On Windows, git might be configured to check out dos/CRLF line endings, so we convert them for those people in case they want to build the image. -RUN dos2unix ./docker/*.sh -RUN mv ./docker/entrypoint.sh /usr/local/bin/entrypoint \ - && chmod +x /usr/local/bin/entrypoint +# Shell scripts need Linux line endings. On Windows, git might be configured to check out dos/CRLF line endings, so we convert them for those people in case they want to build the image. They could also use --config core.autocrlf=input +RUN dos2unix *.sh && chmod +x *.sh +COPY docker-entrypoint.sh /usr/local/bin/ # Configure VNC via environment variables: ENV VNC_PORT 5900 @@ -60,6 +59,6 @@ ENV DEPTH 24 ENV SHOW 1 # Script to setup display server & VNC is always executed. -ENTRYPOINT ["entrypoint"] +ENTRYPOINT ["docker-entrypoint.sh"] # Default command to run. This is replaced by appending own command, e.g. `docker run ... node prime-gaming` to only run this script. CMD node epic-games; node prime-gaming diff --git a/docker/entrypoint.sh b/docker-entrypoint.sh similarity index 100% rename from docker/entrypoint.sh rename to docker-entrypoint.sh From 9e0d4434fe292b6ee0b6a88afbb1762c00c9af90 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 9 Jan 2023 21:03:05 +0100 Subject: [PATCH 149/520] docker-entrypoint: bash strict mode --- docker-entrypoint.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 0fa7de8..cb0470a 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,4 +1,6 @@ -#!/bin/sh +#!/bin/bash + +set -euo pipefail # exit on error, error on undef var, error on any fail in pipe (not just last cmd); add -x to print each cmd; see gist bash_strict_mode.md # Remove chromium profile lock. # When running in docker and then killing it, on the next run chromium displayed a dialog to unlock the profile which made the script time out. @@ -26,4 +28,4 @@ echo "VNC is running on port $VNC_PORT (no password!)" websockify -D --web "/usr/share/novnc/" $NOVNC_PORT "localhost:$VNC_PORT" 2>/dev/null 1>&2 & echo "noVNC (VNC via browser) is running on http://localhost:$NOVNC_PORT" echo -exec tini -g -- "$@" +exec tini -g -- "$@" # https://github.com/krallin/tini/issues/8 node/playwright respond to signals like ctrl-c, but unsure about zombie processes From 1a3d90f7953190a888865eafe32267fcba001cbd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 10 Jan 2023 00:06:25 +0100 Subject: [PATCH 150/520] add otplib to generate OTP from key for eg, pg; gog only has mail --- README.md | 12 +++++ config.js | 5 ++ epic-games.js | 5 +- package-lock.json | 118 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + prime-gaming.js | 3 +- 6 files changed, 141 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 49d06ac..b14c0ba 100644 --- a/README.md +++ b/README.md @@ -60,8 +60,10 @@ Available options/variables and their default values: | PASSWORD | | Default password for any login. | | EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | | EG_PASSWORD | | Epic Games password for login. Overrides PASSWORD. | +| EG_OTPKEY | | Epic Games MFA OTP key. | | PG_EMAIL | | Prime Gaming email for login. Overrides EMAIL. | | PG_PASSWORD | | Prime Gaming password for login. Overrides PASSWORD. | +| PG_OTPKEY | | Prime Gaming MFA OTP key. | | GOG_EMAIL | | GOG email for login. Overrides EMAIL. | | GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | @@ -71,6 +73,16 @@ See `config.js` for all options. On Linux/macOS you can prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. For Docker you can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@bar.baz ...` or using `--env-file` (see [docs](https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file)). If you are using [docker compose](https://docs.docker.com/compose/environment-variables/), you can put them in the `environment:` section. +### Automatic login, two-factor authentication +If you set the options for email, password and OTP key, there will be no prompts and logins automatic. This is optional since all stores should stay logged in since cookies are refreshed. +To get the OTP key, it is easiest to follow the store's guide for adding an authenticator app. You should also scan the shown QR code with your favorite app to have an alternative. + +- Epic Games: visit [password & security](https://www.epicgames.com/account/password), enable 'third-party authenticator app', copy the 'Manual Entry Key' and use it to set `EG_OTPKEY`. +- Prime Gaming: visit Amazon 'Your Account › Login & security', 2-step verification › Manage › Add new app › Can't scan the barcode, copy the bold key and use it to set `PG_OTPKEY` +- GOG: only offers OTP via email + +Beware that storing passwords and OTP keys as clear text may be a security risk. Use a unique/generated password! TODO: maybe at least offer to base64 encode for storage. + ### Epic Games Store Run `node epic-games` (locally or in Docker). diff --git a/config.js b/config.js index e0873b5..e6d2cd6 100644 --- a/config.js +++ b/config.js @@ -11,10 +11,15 @@ export const cfg = { height: Number(process.env.HEIGHT) || 1280, // height of the opened browser timeout: (Number(process.env.TIMEOUT) || 20) * 1000, // 20s, default for playwright is 30s novnc_port: process.env.NOVNC_PORT, // running in docker if set + // auth epic-games eg_email: process.env.EG_EMAIL || process.env.EMAIL, eg_password: process.env.EG_PASSWORD || process.env.PASSWORD, + eg_otpkey: process.env.EG_OTPKEY, + // auth prime-gaming pg_email: process.env.PG_EMAIL || process.env.EMAIL, pg_password: process.env.PG_PASSWORD || process.env.PASSWORD, + pg_otpkey: process.env.PG_OTPKEY, + // auth gog gog_email: process.env.GOG_EMAIL || process.env.EMAIL, gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD, }; diff --git a/epic-games.js b/epic-games.js index fb1a604..4f40525 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,8 +1,9 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra +import { authenticator } from 'otplib'; import path from 'path'; +import { existsSync, writeFileSync } from 'fs'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; import { cfg } from './config.js'; -import { existsSync, writeFileSync } from 'fs'; import prompts from 'prompts'; // alternatives: enquirer, inquirer // import enquirer from 'enquirer'; const { prompt } = enquirer; @@ -85,7 +86,7 @@ try { // handle MFA, but don't await it page.waitForNavigation({ url: '**/id/login/mfa**'}).then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); - const otp = await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + const otp = cfg.eg_otpkey && authenticator.generate(cfg.eg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them await page.type('input[name="code-input-0"]', otp.toString()); await page.click('button[type="submit"]'); }).catch(_ => { }); diff --git a/package-lock.json b/package-lock.json index 5f5954b..3280933 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,11 +12,54 @@ "cross-env": "^7.0.3", "dotenv": "^16.0.3", "lowdb": "^5.0.5", + "otplib": "^12.0.1", "playwright": "^1.29.0", "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" } }, + "node_modules/@otplib/core": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", + "integrity": "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==" + }, + "node_modules/@otplib/plugin-crypto": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz", + "integrity": "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==", + "dependencies": { + "@otplib/core": "^12.0.1" + } + }, + "node_modules/@otplib/plugin-thirty-two": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz", + "integrity": "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==", + "dependencies": { + "@otplib/core": "^12.0.1", + "thirty-two": "^1.0.2" + } + }, + "node_modules/@otplib/preset-default": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-default/-/preset-default-12.0.1.tgz", + "integrity": "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, + "node_modules/@otplib/preset-v11": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-v11/-/preset-v11-12.0.1.tgz", + "integrity": "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, "node_modules/@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -355,6 +398,16 @@ "wrappy": "1" } }, + "node_modules/otplib": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", + "integrity": "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==", + "dependencies": { + "@otplib/core": "^12.0.1", + "@otplib/preset-default": "^12.0.1", + "@otplib/preset-v11": "^12.0.1" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -593,6 +646,14 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==", + "engines": { + "node": ">=0.2.6" + } + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -622,6 +683,48 @@ } }, "dependencies": { + "@otplib/core": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", + "integrity": "sha512-4sGntwbA/AC+SbPhbsziRiD+jNDdIzsZ3JUyfZwjtKyc/wufl1pnSIaG4Uqx8ymPagujub0o92kgBnB89cuAMA==" + }, + "@otplib/plugin-crypto": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-crypto/-/plugin-crypto-12.0.1.tgz", + "integrity": "sha512-qPuhN3QrT7ZZLcLCyKOSNhuijUi9G5guMRVrxq63r9YNOxxQjPm59gVxLM+7xGnHnM6cimY57tuKsjK7y9LM1g==", + "requires": { + "@otplib/core": "^12.0.1" + } + }, + "@otplib/plugin-thirty-two": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/plugin-thirty-two/-/plugin-thirty-two-12.0.1.tgz", + "integrity": "sha512-MtT+uqRso909UkbrrYpJ6XFjj9D+x2Py7KjTO9JDPhL0bJUYVu5kFP4TFZW4NFAywrAtFRxOVY261u0qwb93gA==", + "requires": { + "@otplib/core": "^12.0.1", + "thirty-two": "^1.0.2" + } + }, + "@otplib/preset-default": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-default/-/preset-default-12.0.1.tgz", + "integrity": "sha512-xf1v9oOJRyXfluBhMdpOkr+bsE+Irt+0D5uHtvg6x1eosfmHCsCC6ej/m7FXiWqdo0+ZUI6xSKDhJwc8yfiOPQ==", + "requires": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, + "@otplib/preset-v11": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@otplib/preset-v11/-/preset-v11-12.0.1.tgz", + "integrity": "sha512-9hSetMI7ECqbFiKICrNa4w70deTUfArtwXykPUvSHWOdzOlfa9ajglu7mNCntlvxycTiOAXkQGwjQCzzDEMRMg==", + "requires": { + "@otplib/core": "^12.0.1", + "@otplib/plugin-crypto": "^12.0.1", + "@otplib/plugin-thirty-two": "^12.0.1" + } + }, "@types/debug": { "version": "4.1.7", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", @@ -877,6 +980,16 @@ "wrappy": "1" } }, + "otplib": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", + "integrity": "sha512-xDGvUOQjop7RDgxTQ+o4pOol0/3xSZzawTiPKRrHnQWAy0WjhNs/5HdIDJCrqC4MBynmjXgULc6YfioaxZeFgg==", + "requires": { + "@otplib/core": "^12.0.1", + "@otplib/preset-default": "^12.0.1", + "@otplib/preset-v11": "^12.0.1" + } + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1008,6 +1121,11 @@ "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", "integrity": "sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw==" }, + "thirty-two": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", + "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==" + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", diff --git a/package.json b/package.json index 7720bf5..6f90c03 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "cross-env": "^7.0.3", "dotenv": "^16.0.3", "lowdb": "^5.0.5", + "otplib": "^12.0.1", "playwright": "^1.29.0", "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" diff --git a/prime-gaming.js b/prime-gaming.js index 940cc9e..ee07acb 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,4 +1,5 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra +import { authenticator } from 'otplib'; import path from 'path'; import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; import { cfg } from './config.js'; @@ -61,7 +62,7 @@ try { page.waitForNavigation({ url: '**/ap/mfa**'}).then(async () => { console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App'); await page.check('[name=rememberDevice]'); - const otp = await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + const otp = cfg.pg_otpkey && authenticator.generate(cfg.pg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them await page.type('input[name=otpCode]', otp.toString()); await page.click('input[type="submit"]'); }); From ba1b6fba950342d02e09ce6ff90dfb591481daea Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 10 Jan 2023 00:37:38 +0100 Subject: [PATCH 151/520] use $VNC_PASSWORD if set --- docker-entrypoint.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index cb0470a..7796364 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -euo pipefail # exit on error, error on undef var, error on any fail in pipe (not just last cmd); add -x to print each cmd; see gist bash_strict_mode.md +set -eo pipefail # exit on error, error on any fail in pipe (not just last cmd); add -x to print each cmd; see gist bash_strict_mode.md # Remove chromium profile lock. # When running in docker and then killing it, on the next run chromium displayed a dialog to unlock the profile which made the script time out. @@ -23,7 +23,9 @@ rm -f /tmp/.X1-lock export DISPLAY=:1 # need to export this, otherwise playwright complains with 'Looks like you launched a headed browser without having a XServer running.' Xvfb $DISPLAY -ac -screen 0 "${WIDTH}x${HEIGHT}x${DEPTH}" & echo "Xvfb display server created screen with resolution ${WIDTH}x${HEIGHT}" -x11vnc -display $DISPLAY -forever -shared -rfbport $VNC_PORT -bg -nopw 2>/dev/null 1>&2 # -passwd "${VNC_PASSWORD}" +pw="-nopw" +[ -z "$VNC_PASSWORD" ] || pw="-passwd $VNC_PASSWORD" +x11vnc -display $DISPLAY -forever -shared -rfbport $VNC_PORT -bg $pw 2>/dev/null 1>&2 echo "VNC is running on port $VNC_PORT (no password!)" websockify -D --web "/usr/share/novnc/" $NOVNC_PORT "localhost:$VNC_PORT" 2>/dev/null 1>&2 & echo "noVNC (VNC via browser) is running on http://localhost:$NOVNC_PORT" From 1aeb35ac86a8a2c97eacaa5b2dd9bc425152d647 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 12 Jan 2023 16:27:38 +0100 Subject: [PATCH 152/520] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b14c0ba..9a2063b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ If you don't want to use Docker for quasi-headless mode, you could run inside a ## Usage Both scripts start an automated Firefox instance, either with the browser GUI shown or hidden (*headless mode*). By default, you won't see any browser open on your host system. -- When running inside Docker, the browser will be shown only inside the Container. You can open http://localhost:6080 to interact with the browser running inside the container via noVNC (or use other VNC clients on port 5900). +- When running inside Docker, the browser will be shown only inside the container. You can open http://localhost:6080 to interact with the browser running inside the container via noVNC (or use other VNC clients on port 5900). - When running the scripts outside of Docker, the browser will be hidden by default; you can use `SHOW=1 ...` to show the UI (see options below). When running the first time, you have to login for each store you want to claim games on. From 6aacab592e81a69ddd12bd3c70f9d8d4b26c95e4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 12 Jan 2023 21:19:09 +0100 Subject: [PATCH 153/520] mention supported Raspberry Pi OS (64-bit) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 9a2063b..4a6e683 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,8 @@ Pull requests welcome :) _Works on Windows/macOS/Linux._ +Raspberry Pi (3, 4, Zero 2): Raspbian won't work since it's 32-bit, but Raspberry Pi OS (64-bit) or Ubuntu will. + ## Setup [Install Docker](https://docs.docker.com/get-docker/) and use ``` From db5b2e7607e703a0f78303f5dd4e9730a82e7a08 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 12 Jan 2023 21:40:17 +0100 Subject: [PATCH 154/520] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a6e683..5c4efe9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Claims free games periodically on - [Epic Games Store](https://www.epicgames.com/store/free-games) - [Amazon Prime Gaming](https://gaming.amazon.com) -- [GOG](https://www.gog.com) - WIP +- [GOG](https://www.gog.com) - testing - [Xbox Live Games with Gold](https://www.xbox.com/en-US/live/gold#gameswithgold) - planned Pull requests welcome :) From 7922044a60a298a37d44f19497b4e5a73cf69028 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 12 Jan 2023 21:58:59 +0100 Subject: [PATCH 155/520] Update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5c4efe9..453cd92 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ If you don't want to use Docker for quasi-headless mode, you could run inside a
## Usage -Both scripts start an automated Firefox instance, either with the browser GUI shown or hidden (*headless mode*). By default, you won't see any browser open on your host system. +All scripts start an automated Firefox instance, either with the browser GUI shown or hidden (*headless mode*). By default, you won't see any browser open on your host system. - When running inside Docker, the browser will be shown only inside the container. You can open http://localhost:6080 to interact with the browser running inside the container via noVNC (or use other VNC clients on port 5900). - When running the scripts outside of Docker, the browser will be hidden by default; you can use `SHOW=1 ...` to show the UI (see options below). @@ -41,9 +41,9 @@ Both scripts start an automated Firefox instance, either with the browser GUI sh When running the first time, you have to login for each store you want to claim games on. You can login indirectly via the terminal or directly in the browser. The scripts will wait until you are successfully logged in. -There will be prompts in the terminal asking you to enter email, password, and afterwards some OTP (one time password / security code) if you have 2FA/MFA (two-/multi-factor authentication) enabled. If you want to login yourself via the browser, you can just hit Escape to skip the prompts. +There will be prompts in the terminal asking you to enter email, password, and afterwards some OTP (one time password / security code) if you have 2FA/MFA (two-/multi-factor authentication) enabled. If you want to login yourself via the browser, you can press escape in the terminal to skip the prompts. -After login, the script will just continue claiming the current games. If it still waits after you are already logged in, you can restart it (and open an issue). +After login, the script will continue claiming the current games. If it still waits after you are already logged in, you can restart it (and open an issue). If you run the scripts regularly, you should not have to login again. ### Options Options are set via [environment variables](https://kinsta.com/knowledgebase/what-is-an-environment-variable/) which can be set in many ways and allow for flexible configuration. @@ -76,12 +76,12 @@ On Linux/macOS you can prefix the variables you want to set, for example `EMAIL= For Docker you can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@bar.baz ...` or using `--env-file` (see [docs](https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file)). If you are using [docker compose](https://docs.docker.com/compose/environment-variables/), you can put them in the `environment:` section. ### Automatic login, two-factor authentication -If you set the options for email, password and OTP key, there will be no prompts and logins automatic. This is optional since all stores should stay logged in since cookies are refreshed. -To get the OTP key, it is easiest to follow the store's guide for adding an authenticator app. You should also scan the shown QR code with your favorite app to have an alternative. +If you set the options for email, password and OTP key, there will be no prompts and logins should happen automatically. This is optional since all stores should stay logged in since cookies are refreshed. +To get the OTP key, it is easiest to follow the store's guide for adding an authenticator app. You should also scan the shown QR code with your favorite app to have an alternative method for 2FA. -- Epic Games: visit [password & security](https://www.epicgames.com/account/password), enable 'third-party authenticator app', copy the 'Manual Entry Key' and use it to set `EG_OTPKEY`. -- Prime Gaming: visit Amazon 'Your Account › Login & security', 2-step verification › Manage › Add new app › Can't scan the barcode, copy the bold key and use it to set `PG_OTPKEY` -- GOG: only offers OTP via email +- **Epic Games**: visit [password & security](https://www.epicgames.com/account/password), enable 'third-party authenticator app', copy the 'Manual Entry Key' and use it to set `EG_OTPKEY`. +- **Prime Gaming**: visit Amazon 'Your Account › Login & security', 2-step verification › Manage › Add new app › Can't scan the barcode, copy the bold key and use it to set `PG_OTPKEY` +- **GOG**: only offers OTP via email Beware that storing passwords and OTP keys as clear text may be a security risk. Use a unique/generated password! TODO: maybe at least offer to base64 encode for storage. From 78a14569c8e88edebbc6a1a64f8e84ef33a7b539 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 13 Jan 2023 17:51:32 +0100 Subject: [PATCH 156/520] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 453cd92..d019a58 100644 --- a/README.md +++ b/README.md @@ -123,7 +123,7 @@ If you're a developer, you can use `PWDEBUG=1 ...` to [inspect](https://playwrig
Click to expand -Tried [epicgames-freebies-claimer](https://github.com/Revadike/epicgames-freebies-claimer), but does not work anymore since epicgames introduced hcaptcha (see [issue](https://github.com/Revadike/epicgames-freebies-claimer/issues/172)). +Tried [epicgames-freebies-claimer](https://github.com/Revadike/epicgames-freebies-claimer), but had problems since epicgames introduced hcaptcha (see [issue](https://github.com/Revadike/epicgames-freebies-claimer/issues/172)). Played around with puppeteer before, now trying newer https://playwright.dev which is pretty similar. Playwright Inspector and `codegen` to generate scripts are nice, but failed to generate the right code for clicking a button in an iframe. From f2df47f13e6fe27e6c7f38261e443f93a6205420 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 13 Jan 2023 18:02:35 +0100 Subject: [PATCH 157/520] Update README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index d019a58..754fef4 100644 --- a/README.md +++ b/README.md @@ -148,4 +148,11 @@ Renamed repository from epicgames-claimer to free-games-claimer since a script f epic games: `headless` mode gets hcaptcha challenge. More details/references in [issue](https://github.com/vogler/free-games-claimer/issues/2). +https://github.com/vogler/free-games-claimer/pull/11 introduced a Dockerfile for running non-headless inside the container via xvfb which makes it headless for the host running the container. + +v1.0 Standalone scripts node epic-games and node prime-gaming using Chromium. + +Changed to Firefox for all scripts since Chromium led to captchas. Claiming then also worked in headless mode without Docker. + +Added options via env vars, configurable in `data/config.env`.
From 5babbb6bc1ecbdb2002f0b2b17b51c532e4882d4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 13 Jan 2023 18:03:29 +0100 Subject: [PATCH 158/520] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 754fef4..0129da4 100644 --- a/README.md +++ b/README.md @@ -155,4 +155,6 @@ v1.0 Standalone scripts node epic-games and node prime-gaming using Chromium. Changed to Firefox for all scripts since Chromium led to captchas. Claiming then also worked in headless mode without Docker. Added options via env vars, configurable in `data/config.env`. + +Added OTP generation via otplib for automatic login, even with 2FA. From 351670f426dc0af40092c9d681346f91f5cc0116 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 19 Jan 2023 00:57:56 +0100 Subject: [PATCH 159/520] eg: TODO locator for 2FA text (email or app?) --- epic-games.js | 1 + 1 file changed, 1 insertion(+) diff --git a/epic-games.js b/epic-games.js index 4f40525..c52095e 100644 --- a/epic-games.js +++ b/epic-games.js @@ -86,6 +86,7 @@ try { // handle MFA, but don't await it page.waitForNavigation({ url: '**/id/login/mfa**'}).then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); + // TODO locator for text (email or app?) const otp = cfg.eg_otpkey && authenticator.generate(cfg.eg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them await page.type('input[name="code-input-0"]', otp.toString()); await page.click('button[type="submit"]'); From 4055ec44c7aa05918a4d96a236be7767d344c31f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 19 Jan 2023 01:26:27 +0100 Subject: [PATCH 160/520] fix for #46: "This product is currently unavailable in your region" --- epic-games.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index c52095e..27624af 100644 --- a/epic-games.js +++ b/epic-games.js @@ -145,7 +145,13 @@ try { // it then creates an iframe for the purchase await page.waitForSelector('#webPurchaseContainer iframe'); // TODO needed? const iframe = page.frameLocator('#webPurchaseContainer iframe'); - await iframe.locator('button:has-text("Place Order")').click(); + if (await Promise.any([ + iframe.locator('button:has-text("Place Order")').click(), + iframe.locator(':has-text("unavailable in your region")').waitFor().then(_ => 'unavailable'), + ]) == 'unavailable') { // can't continue loop from the promise + console.error(' This product is unavailable in your region!'); + continue; + }; // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 const btnAgree = iframe.locator('button:has-text("I Agree")'); From c09da8eec6446736bc7943716ae89a5efcf5893b Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 19 Jan 2023 12:29:20 +0100 Subject: [PATCH 161/520] eg: simpler check if game is unavailable in region --- epic-games.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/epic-games.js b/epic-games.js index 27624af..926523e 100644 --- a/epic-games.js +++ b/epic-games.js @@ -145,13 +145,12 @@ try { // it then creates an iframe for the purchase await page.waitForSelector('#webPurchaseContainer iframe'); // TODO needed? const iframe = page.frameLocator('#webPurchaseContainer iframe'); - if (await Promise.any([ - iframe.locator('button:has-text("Place Order")').click(), - iframe.locator(':has-text("unavailable in your region")').waitFor().then(_ => 'unavailable'), - ]) == 'unavailable') { // can't continue loop from the promise + // skip game if unavailable in region, https://github.com/vogler/free-games-claimer/issues/46 TODO check games for account's region + if (await iframe.locator(':has-text("unavailable in your region")').count() > 0) { console.error(' This product is unavailable in your region!'); continue; - }; + } + await iframe.locator('button:has-text("Place Order")').click(); // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 const btnAgree = iframe.locator('button:has-text("I Agree")'); From 8139c0a78f49690fcc93bdb76339f4d4e570ad0f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 23 Jan 2023 14:45:29 +0100 Subject: [PATCH 162/520] gog: save metadata, screenshot, closes #18 --- gog.js | 54 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/gog.js b/gog.js index 4e2e0bf..f6e0196 100644 --- a/gog.js +++ b/gog.js @@ -1,6 +1,6 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; +import { dirs, jsonDb, datetime, filenamify } from './util.js'; import { cfg } from './config.js'; import prompts from 'prompts'; // alternatives: enquirer, inquirer @@ -20,6 +20,8 @@ const context = await firefox.launchPersistentContext(dirs.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, locale: "en-US", // ignore OS locale to be sure to have english text for locators -> done via /en in URL + // recordHar: { path: './data/gog.har' }, // https://toolbox.googleapps.com/apps/har_analyzer/ + // recordVideo: { dir: './data/videos' }, // console.log(await page.video().path()); }); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); @@ -69,17 +71,51 @@ try { // await page.waitForNavigation(); // TODO was blocking if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } - const user = await page.locator('#menuUsername').first().innerHTML(); - console.log(`Signed in as ${user}`); + const user = await page.locator('#menuUsername').first().textContent(); // innerText is uppercase due to styling! + console.log(`Signed in as '${user}'`); db.data[user] ||= {}; - console.log('TODO get title of current game (waiting for next offer)'); - await page.goto('https://www.gog.com/giveaway/claim'); - console.log(await page.innerText('body')); + const banner = page.locator('#giveaway'); + if (!await banner.count()) { + console.log('Currently no free giveaway!'); + } else { + const text = await page.locator('.giveaway-banner__title').innerText(); + const title = text.match(/Claim (.*) and don't miss/)[1]; + const slug = await banner.getAttribute('href'); + const url = `https://gog.com${slug}`; + console.log(`Current free game: ${title} - ${url}`); + db.data[user][title] ||= { title, time: datetime(), url }; + const p = path.resolve(dirs.screenshots, 'gog', `${filenamify(title)}.png`); + await banner.screenshot({ path: p }); // overwrites every time - only keep first? + // await banner.getByRole('button', { name: 'Add to library' }).click(); + // instead of clicking the button, we visit the auto-claim URL which gives as a JSON response which is easier than checking the state of a button + await page.goto('https://www.gog.com/giveaway/claim'); + const response = await page.innerText('body'); + // console.log(response); + // {} // when successfully claimed + // {"message":"Already claimed"} + // {"message":"Unauthorized"} + // {"message":"Giveaway has ended"} + let status; + if (response == '{}') { + status = 'claimed'; + console.log(' Claimed successfully!'); + } else { + const message = JSON.parse(response).message; + if (message == 'Already claimed') { + status = 'existed'; // same status text as for epic-games + console.log(' Already in library! Nothing to claim.'); + } else { + console.log(response); + status = message; + } + } + db.data[user][title].status ||= status; - console.log("Unsubscribe from 'Promotions and hot deals' newsletter"); - await page.goto('https://www.gog.com/en/account/settings/subscriptions'); - await page.locator('li:has-text("Promotions and hot deals") input').uncheck(); + console.log("Unsubscribe from 'Promotions and hot deals' newsletter"); + await page.goto('https://www.gog.com/en/account/settings/subscriptions'); + await page.locator('li:has-text("Promotions and hot deals") label').uncheck(); + } } catch (error) { console.error(error); // .toString()? } finally { From 46a5928d01f78866720acd2bbd285096cd97d091 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 23 Jan 2023 14:48:29 +0100 Subject: [PATCH 163/520] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0129da4..587c724 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Claims free games periodically on - [Epic Games Store](https://www.epicgames.com/store/free-games) - [Amazon Prime Gaming](https://gaming.amazon.com) -- [GOG](https://www.gog.com) - testing +- [GOG](https://www.gog.com) - [Xbox Live Games with Gold](https://www.xbox.com/en-US/live/gold#gameswithgold) - planned Pull requests welcome :) From fafd1ad6bf4d2f0a867027e33dfcb8b8b8dc6e80 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 23 Jan 2023 14:59:59 +0100 Subject: [PATCH 164/520] comment: no gog_otp --- config.js | 1 + 1 file changed, 1 insertion(+) diff --git a/config.js b/config.js index e6d2cd6..f60b23e 100644 --- a/config.js +++ b/config.js @@ -22,4 +22,5 @@ export const cfg = { // auth gog gog_email: process.env.GOG_EMAIL || process.env.EMAIL, gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD, + // OTP only via GOG_EMAIL, can't add app... }; From 0e7e7b08f39e137fdda9824099a32473161be934 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 15:32:37 +0100 Subject: [PATCH 165/520] add some logos --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index 587c724..12dabcc 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,9 @@ +

+logo-free-games-claimer +

+ # free-games-claimer + Claims free games periodically on - [Epic Games Store](https://www.epicgames.com/store/free-games) - [Amazon Prime Gaming](https://gaming.amazon.com) @@ -158,3 +163,9 @@ Added options via env vars, configurable in `data/config.env`. Added OTP generation via otplib for automatic login, even with 2FA. + +--- + +Logo with smaller aspect ratio (for Telegram bot etc.): + +![logo-fgc](https://user-images.githubusercontent.com/493741/214589922-093d6557-6393-421c-b577-da58ff3671bc.png) From 3b1b900d776f88327ebe26d7daa4c96b3e2101de Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 16:38:56 +0100 Subject: [PATCH 166/520] pg: TODO check for wrong credentials --- prime-gaming.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index ee07acb..e562452 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -58,6 +58,9 @@ try { await page.fill('[name=password]', password); await page.check('[name=rememberMe]'); await page.click('input[type="submit"]'); + page.waitForNavigation({ url: '**/ap/signin**'}).then(async () => { // TODO check for wrong credentials + console.error(await page.locator('.a-alert-content').first().innerText()); + }).catch(_ => { }); // handle MFA, but don't await it page.waitForNavigation({ url: '**/ap/mfa**'}).then(async () => { console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App'); @@ -65,7 +68,7 @@ try { const otp = cfg.pg_otpkey && authenticator.generate(cfg.pg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them await page.type('input[name=otpCode]', otp.toString()); await page.click('input[type="submit"]'); - }); + }).catch(_ => { }); } else { if (cfg.headless) { console.log('Please run `node prime-gaming show` to login in the opened browser.'); From cecc54082f08944b5fcefd2caffcb1ab04c4fb29 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 17:45:50 +0100 Subject: [PATCH 167/520] eg: title now in span instead of div --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 926523e..013c25f 100644 --- a/epic-games.js +++ b/epic-games.js @@ -123,7 +123,7 @@ try { await page.click('button:has-text("Continue")'); } - const title = await page.locator('h1 div').first().innerText(); + const title = await page.locator('h1').first().innerText(); const game_id = page.url().split('/').pop(); db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! console.log('Current free game:', title); From e57c2c440854051c84c99222090fb0b0dc579db0 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 16:43:51 +0100 Subject: [PATCH 168/520] docker: pip install apprise; 1.09GB -> 1.11GB --- Dockerfile | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b201581..69af099 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,7 @@ SHELL ["/bin/bash", "-o", "pipefail", "-c"] ARG DEBIAN_FRONTEND=noninteractive ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD true -# Install up-to-date node & npm, deps for virtual screen & noVNC, browser. +# Install up-to-date node & npm, deps for virtual screen & noVNC, browser, pip for apprise. # Playwright needs --with-deps for firefox. RUN apt-get update \ && apt-get install -y curl \ @@ -22,6 +22,7 @@ RUN apt-get update \ tini \ novnc websockify \ dos2unix \ + python3-pip \ && npx playwright install --with-deps firefox \ && apt-get clean \ && rm -rf \ @@ -32,6 +33,7 @@ RUN apt-get update \ /var/tmp/* RUN ln -s /usr/share/novnc/vnc_auto.html /usr/share/novnc/index.html +RUN pip install apprise WORKDIR /fgc COPY package*.json ./ From 2f0961b1b30407057d36489f7cf066c6edb80319 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 16:44:42 +0100 Subject: [PATCH 169/520] NOTIFY to set notification services --- README.md | 20 ++++++++++++++------ config.js | 1 + util.js | 19 +++++++++++++++++++ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 12dabcc..91f02b6 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Data is stored in the volume `fgc`. 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 && npx playwright install firefox` +4. Run `pip install apprise` to install [apprise](https://github.com/caronc/apprise) if you want notifications This downloads 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`. @@ -63,16 +64,17 @@ Available options/variables and their default values: | WIDTH | 1280 | Width of the opened browser (and screen vor VNC in Docker). | | HEIGHT | 1280 | Height of the opened browser (and screen vor VNC in Docker). | | VNC_PASSWORD | | VNC password for Docker. No password used by default! | +| NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. | | EMAIL | | Default email for any login. | | PASSWORD | | Default password for any login. | | EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | | EG_PASSWORD | | Epic Games password for login. Overrides PASSWORD. | -| EG_OTPKEY | | Epic Games MFA OTP key. | +| EG_OTPKEY | | Epic Games MFA OTP key. | | PG_EMAIL | | Prime Gaming email for login. Overrides EMAIL. | | PG_PASSWORD | | Prime Gaming password for login. Overrides PASSWORD. | -| PG_OTPKEY | | Prime Gaming MFA OTP key. | -| GOG_EMAIL | | GOG email for login. Overrides EMAIL. | -| GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | +| PG_OTPKEY | | Prime Gaming MFA OTP key. | +| GOG_EMAIL | | GOG email for login. Overrides EMAIL. | +| GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | See `config.js` for all options. @@ -80,6 +82,12 @@ See `config.js` for all options. On Linux/macOS you can prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. For Docker you can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@bar.baz ...` or using `--env-file` (see [docs](https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file)). If you are using [docker compose](https://docs.docker.com/compose/environment-variables/), you can put them in the `environment:` section. +### Notifications +The scripts will try to send notifications for successfully claimed games and any errors like needing to log in or encountered captchas (should not happen). + +[apprise](https://github.com/caronc/apprise) is used for notifications and offers many services including Pushover, Slack, Telegram, SMS, Email, desktop and custom notifications. +You just need to set `NOTIFY` to the notifications services you want to use, e.g. `NOTIFY='mailto://myemail:mypass@gmail.com' 'pbul://o.gn5kj6nfhv736I7jC3cj3QLRiyhgl98b'` - refer to their list of services and [examples](https://github.com/caronc/apprise#command-line-usage). + ### Automatic login, two-factor authentication If you set the options for email, password and OTP key, there will be no prompts and logins should happen automatically. This is optional since all stores should stay logged in since cookies are refreshed. To get the OTP key, it is easiest to follow the store's guide for adding an authenticator app. You should also scan the shown QR code with your favorite app to have an alternative method for 2FA. @@ -160,12 +168,12 @@ v1.0 Standalone scripts node epic-games and node prime-gaming using Chromium. Changed to Firefox for all scripts since Chromium led to captchas. Claiming then also worked in headless mode without Docker. Added options via env vars, configurable in `data/config.env`. - + Added OTP generation via otplib for automatic login, even with 2FA. --- -Logo with smaller aspect ratio (for Telegram bot etc.): +Logo with smaller aspect ratio (for Telegram bot etc.): 👾 - [emojipedia](https://emojipedia.org/alien-monster/) ![logo-fgc](https://user-images.githubusercontent.com/493741/214589922-093d6557-6393-421c-b577-da58ff3671bc.png) diff --git a/config.js b/config.js index f60b23e..726e6bd 100644 --- a/config.js +++ b/config.js @@ -11,6 +11,7 @@ export const cfg = { height: Number(process.env.HEIGHT) || 1280, // height of the opened browser timeout: (Number(process.env.TIMEOUT) || 20) * 1000, // 20s, default for playwright is 30s novnc_port: process.env.NOVNC_PORT, // running in docker if set + notify: process.env.NOTIFY, // apprise notification services // auth epic-games eg_email: process.env.EG_EMAIL || process.env.EMAIL, eg_password: process.env.EG_PASSWORD || process.env.PASSWORD, diff --git a/util.js b/util.js index a14bb1d..fb86c12 100644 --- a/util.js +++ b/util.js @@ -76,3 +76,22 @@ export const stealth = async (context) => { await context.addInitScript(evasion.cb, evasion.a); } }; + +// notifications via apprise CLI +import { exec } from 'child_process'; +import { cfg } from './config.js'; + +export const notify = (html) => { + if (!cfg.notify) return; + exec(`apprise ${cfg.notify} -i html -b '${html}'`, (error, stdout, stderr) => { + if (error) { + console.log(`error: ${error.message}`); + if (error.message.includes('command not found')) { + console.info('Run `pip install apprise`. See https://github.com/vogler/free-games-claimer#notifications'); + } + return; + } + if (stderr) console.error(`stderr: ${stderr}`); + if (stdout) console.log(`stdout: ${stdout}`); + }); +} From 109423925e499a26fdb41b86e88956525a148765 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 17:49:39 +0100 Subject: [PATCH 170/520] eg: notify about games and login --- epic-games.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index 013c25f..98bcf3d 100644 --- a/epic-games.js +++ b/epic-games.js @@ -2,7 +2,7 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwr import { authenticator } from 'otplib'; import path from 'path'; import { existsSync, writeFileSync } from 'fs'; -import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; +import { dirs, jsonDb, datetime, stealth, filenamify, notify } from './util.js'; import { cfg } from './config.js'; import prompts from 'prompts'; // alternatives: enquirer, inquirer @@ -59,6 +59,8 @@ if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); +const notify_games = []; + try { await context.addCookies([{name: 'OptanonAlertBoxClosed', value: new Date(Date.now() - 5*24*60*60*1000).toISOString(), domain: '.epicgames.com', path: '/'}]); // Accept cookies to get rid of banner to save space on screen. Set accept time to 5 days ago. @@ -82,6 +84,7 @@ try { await page.click('button[type="submit"]'); page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(() => { console.log('Got a captcha! You may have to solve it in the browser if the NopeCHA extension fails to do so.'); + notify('epic-games: got captcha during login. Please check.'); }).catch(_ => { }); // handle MFA, but don't await it page.waitForNavigation({ url: '**/id/login/mfa**'}).then(async () => { @@ -93,6 +96,7 @@ try { }).catch(_ => { }); } else { console.log('Waiting for you to login in the browser.'); + notify('epic-games: no longer signed in and not enough options set for automatic login.'); } await page.waitForNavigation({ url: URL_CLAIM }); context.setDefaultTimeout(cfg.timeout); @@ -127,6 +131,8 @@ try { const game_id = page.url().split('/').pop(); db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! console.log('Current free game:', title); + const notify_game = {title, url, status: 'failed'}; + notify_games.push(notify_game); // status is updated below if (btnText.toLowerCase() == 'in library') { console.log(' Already in library! Nothing to claim.'); @@ -148,6 +154,7 @@ try { // skip game if unavailable in region, https://github.com/vogler/free-games-claimer/issues/46 TODO check games for account's region if (await iframe.locator(':has-text("unavailable in your region")').count() > 0) { console.error(' This product is unavailable in your region!'); + db.data[user][game_id].status = notify_game.status = 'unavailable-in-region'; continue; } await iframe.locator('button:has-text("Place Order")').click(); @@ -169,7 +176,7 @@ try { }).catch(_ => { }); // may time out if not shown await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again = no-op db.data[user][game_id].status = 'claimed'; - db.data[user][game_id].time = datetime(); // claimed time overwrites failed time + db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time console.log(' Claimed successfully!'); context.setDefaultTimeout(cfg.timeout); } catch (e) { @@ -183,11 +190,18 @@ try { const p = path.resolve(dirs.screenshots, 'epic-games', `${game_id}.png`); if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... } + notify_game.status = db.data[user][game_id].status; } } catch (error) { console.error(error); // .toString()? + if (!error.message.contains('Target closed')) // e.g. when killed by Ctrl-C + notify(`epic-games failed: ${error.message}`); } finally { await db.write(); // write out json db + if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed; TODO don't notify if killed? + const list = notify_games.map(g => `- ${g.title} (${g.status})
`); + notify(`epic-games:
${list}`); + } } await writeFileSync(path.resolve(dirs.browser, 'cookies.json'), JSON.stringify(await context.cookies())); await context.close(); From 95b703efb11bfb045bf1c3c294e83e7e1ab25bb5 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 18:29:06 +0100 Subject: [PATCH 171/520] gog: notify about games and login --- gog.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/gog.js b/gog.js index f6e0196..4de1c22 100644 --- a/gog.js +++ b/gog.js @@ -1,6 +1,6 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { dirs, jsonDb, datetime, filenamify } from './util.js'; +import { dirs, jsonDb, datetime, filenamify, notify } from './util.js'; import { cfg } from './config.js'; import prompts from 'prompts'; // alternatives: enquirer, inquirer @@ -29,6 +29,8 @@ if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); +const notify_games = []; + try { await context.addCookies([{name: 'CookieConsent', value: '{stamp:%274oR8MJL+bxVlG6g+kl2we5+suMJ+Tv7I4C5d4k+YY4vrnhCD+P23RQ==%27%2Cnecessary:true%2Cpreferences:true%2Cstatistics:true%2Cmarketing:true%2Cmethod:%27explicit%27%2Cver:1%2Cutc:1672331618201%2Cregion:%27de%27}', domain: 'www.gog.com', path: '/'}]); // to not waste screen space when non-headless @@ -61,12 +63,13 @@ try { await page.waitForTimeout(1000); // TODO wait for something else below? }); } else { + console.log('Waiting for you to login in the browser.'); + notify('gog: no longer signed in and not enough options set for automatic login.'); if (cfg.headless) { console.log('Please run `node gog show` to login in the opened browser.'); await context.close(); // not needed? process.exit(1); } - console.log('Waiting for you to login in the browser.'); } // await page.waitForNavigation(); // TODO was blocking if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); @@ -111,6 +114,7 @@ try { } } db.data[user][title].status ||= status; + notify_games.push({ title, url, status }); console.log("Unsubscribe from 'Promotions and hot deals' newsletter"); await page.goto('https://www.gog.com/en/account/settings/subscriptions'); @@ -118,7 +122,13 @@ try { } } catch (error) { console.error(error); // .toString()? + if (!error.message.contains('Target closed')) // e.g. when killed by Ctrl-C + notify(`prime-gaming failed: ${error.message}`); } finally { await db.write(); // write out json db + if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed; TODO don't notify if killed? + const list = notify_games.map(g => `- ${g.title} (${g.status})
`); + notify(`gog:
${list}`); + } } await context.close(); From 13e6f05cd0732c21a226caf002c9998fbd23cc8a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 18:54:23 +0100 Subject: [PATCH 172/520] pg: notify about games and login --- epic-games.js | 2 +- prime-gaming.js | 28 ++++++++++++++++++++++------ 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/epic-games.js b/epic-games.js index 98bcf3d..6ab8d2d 100644 --- a/epic-games.js +++ b/epic-games.js @@ -194,7 +194,7 @@ try { } } catch (error) { console.error(error); // .toString()? - if (!error.message.contains('Target closed')) // e.g. when killed by Ctrl-C + if (error.message && !error.message.contains('Target closed')) // e.g. when killed by Ctrl-C notify(`epic-games failed: ${error.message}`); } finally { await db.write(); // write out json db diff --git a/prime-gaming.js b/prime-gaming.js index e562452..ebf4785 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,7 +1,7 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; import path from 'path'; -import { dirs, jsonDb, datetime, stealth, filenamify } from './util.js'; +import { dirs, jsonDb, datetime, stealth, filenamify, notify } from './util.js'; import { cfg } from './config.js'; import prompts from 'prompts'; // alternatives: enquirer, inquirer @@ -41,6 +41,8 @@ if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); +const notify_games = []; + try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever // need to wait for some elements to exist before checking if signed in or accepting cookies: @@ -60,7 +62,7 @@ try { await page.click('input[type="submit"]'); page.waitForNavigation({ url: '**/ap/signin**'}).then(async () => { // TODO check for wrong credentials console.error(await page.locator('.a-alert-content').first().innerText()); - }).catch(_ => { }); + }); // handle MFA, but don't await it page.waitForNavigation({ url: '**/ap/mfa**'}).then(async () => { console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App'); @@ -70,12 +72,13 @@ try { await page.click('input[type="submit"]'); }).catch(_ => { }); } else { + console.log('Waiting for you to login in the browser.'); + notify('prime-gaming: no longer signed in and not enough options set for automatic login.'); if (cfg.headless) { console.log('Please run `node prime-gaming show` to login in the opened browser.'); await context.close(); // not needed? process.exit(1); } - console.log('Waiting for you to login in the browser.'); } await page.waitForNavigation({ url: 'https://gaming.amazon.com/home?signedIn=true' }); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); @@ -108,6 +111,7 @@ try { await card.screenshot({ path: p }); await (await card.$('button:has-text("Claim game")')).click(); db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; + notify_games.push({ title, status: 'claimed', url: URL_CLAIM }); // await page.pause(); } // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft @@ -129,6 +133,10 @@ try { // 3 Full PC Games on Legacy Games const store = store_text.toLowerCase().replace(/.* on /, ''); console.log(' External store:', store); + const url = page.url().split('?')[0]; + db.data[user][title] ||= { title, time: datetime(), url, store }; + const notify_game = {title, url, status: `failed - link ${store}`}; + notify_games.push(notify_game); // status is updated below if (await page.locator('div:has-text("Link game account")').count()) { console.error(' Account linking is required to claim this offer!'); } else { @@ -139,16 +147,18 @@ try { 'legacy games': 'https://www.legacygames.com/primedeal', 'microsoft games': 'https://redeem.microsoft.com', }; - let code; if (store in redeem) { // did not work for linked origin: && !await page.locator('div:has-text("Successfully Claimed")').count() - code = await page.inputValue('input[type="text"]'); + const code = await page.inputValue('input[type="text"]'); console.log(' Code to redeem game:', code); if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); } console.log(' URL to redeem game:', redeem[store]); + db.data[user][title].code = code; + notify_game.status = `redeem ${code} on ${store}`; + } else { + notify_game.status = `claimed on ${store}`; } - db.data[user][title] ||= { title, time: datetime(), store, code, url: page.url() }; // save screenshot of potential code just in case const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${filenamify(title)}.png`); await page.screenshot({ path: p, fullPage: true }); @@ -163,7 +173,13 @@ try { await page.locator(games_sel).screenshot({ path: p }); } catch (error) { console.error(error); // .toString()? + if (error.message && !error.message.contains('Target closed')) // e.g. when killed by Ctrl-C + notify(`prime-gaming failed: ${error.message}`); } finally { await db.write(); // write out json db + if (notify_games.length) { // list should only include claimed games + const list = notify_games.map(g => `- ${g.title} (${g.status})
`); + notify(`prime-gaming:
${list}`); + } } await context.close(); From 0913ca3da79dd8129d6f1dca1541c542696b3b97 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 19:26:34 +0100 Subject: [PATCH 173/520] pg: exit on login error --- prime-gaming.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index ebf4785..b51db42 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -60,8 +60,12 @@ try { await page.fill('[name=password]', password); await page.check('[name=rememberMe]'); await page.click('input[type="submit"]'); - page.waitForNavigation({ url: '**/ap/signin**'}).then(async () => { // TODO check for wrong credentials - console.error(await page.locator('.a-alert-content').first().innerText()); + page.waitForNavigation({ url: '**/ap/signin**'}).then(async () => { // check for wrong credentials + const error = await page.locator('.a-alert-content').first().innerText(); + console.error(error); + notify(`prime-gaming: login: ${error}`); + await context.close(); // finishes potential recording + process.exit(1); }); // handle MFA, but don't await it page.waitForNavigation({ url: '**/ap/mfa**'}).then(async () => { @@ -76,7 +80,7 @@ try { notify('prime-gaming: no longer signed in and not enough options set for automatic login.'); if (cfg.headless) { console.log('Please run `node prime-gaming show` to login in the opened browser.'); - await context.close(); // not needed? + await context.close(); // finishes potential recording process.exit(1); } } From 9355ff3e0104f96c37777e3a71d5ab516c65e2e4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 19:35:18 +0100 Subject: [PATCH 174/520] notify: forgot to join list, gets rid of commas --- epic-games.js | 2 +- gog.js | 2 +- prime-gaming.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index 6ab8d2d..4ce8b9a 100644 --- a/epic-games.js +++ b/epic-games.js @@ -199,7 +199,7 @@ try { } finally { await db.write(); // write out json db if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed; TODO don't notify if killed? - const list = notify_games.map(g => `- ${g.title} (${g.status})
`); + const list = notify_games.map(g => `- ${g.title} (${g.status})`).join('
'); notify(`epic-games:
${list}`); } } diff --git a/gog.js b/gog.js index 4de1c22..51722b3 100644 --- a/gog.js +++ b/gog.js @@ -127,7 +127,7 @@ try { } finally { await db.write(); // write out json db if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed; TODO don't notify if killed? - const list = notify_games.map(g => `- ${g.title} (${g.status})
`); + const list = notify_games.map(g => `- ${g.title} (${g.status})`).join('
'); notify(`gog:
${list}`); } } diff --git a/prime-gaming.js b/prime-gaming.js index b51db42..b45e556 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -182,7 +182,7 @@ try { } finally { await db.write(); // write out json db if (notify_games.length) { // list should only include claimed games - const list = notify_games.map(g => `- ${g.title} (${g.status})
`); + const list = notify_games.map(g => `- ${g.title} (${g.status})`).join('
'); notify(`prime-gaming:
${list}`); } } From f8932af2a1df3492bcb2c6d7f26fac35f9588c34 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 20:44:30 +0100 Subject: [PATCH 175/520] Screenshot sample Telegram notifications --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 91f02b6..706d4e1 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,8 @@ Claims free games periodically on Pull requests welcome :) +![Telegram Screenshot](https://user-images.githubusercontent.com/493741/214667078-eb5c1877-2bdd-40c1-b94e-4a50d6852c06.png) + _Works on Windows/macOS/Linux._ Raspberry Pi (3, 4, Zero 2): Raspbian won't work since it's 32-bit, but Raspberry Pi OS (64-bit) or Ubuntu will. From 72a61458fc72c68c07bdaf3186f6a8361bfc7f95 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Jan 2023 20:56:35 +0100 Subject: [PATCH 176/520] notify: escapeHtml for titles --- epic-games.js | 7 +++---- gog.js | 5 ++--- prime-gaming.js | 7 +++---- util.js | 4 ++++ 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/epic-games.js b/epic-games.js index 4ce8b9a..3077fd7 100644 --- a/epic-games.js +++ b/epic-games.js @@ -2,7 +2,7 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwr import { authenticator } from 'otplib'; import path from 'path'; import { existsSync, writeFileSync } from 'fs'; -import { dirs, jsonDb, datetime, stealth, filenamify, notify } from './util.js'; +import { dirs, jsonDb, datetime, stealth, filenamify, notify, html_game_list } from './util.js'; import { cfg } from './config.js'; import prompts from 'prompts'; // alternatives: enquirer, inquirer @@ -131,7 +131,7 @@ try { const game_id = page.url().split('/').pop(); db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! console.log('Current free game:', title); - const notify_game = {title, url, status: 'failed'}; + const notify_game = { title, url, status: 'failed' }; notify_games.push(notify_game); // status is updated below if (btnText.toLowerCase() == 'in library') { @@ -199,8 +199,7 @@ try { } finally { await db.write(); // write out json db if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed; TODO don't notify if killed? - const list = notify_games.map(g => `- ${g.title} (${g.status})`).join('
'); - notify(`epic-games:
${list}`); + notify(`epic-games:
${html_game_list(notify_games)}`); } } await writeFileSync(path.resolve(dirs.browser, 'cookies.json'), JSON.stringify(await context.cookies())); diff --git a/gog.js b/gog.js index 51722b3..685fcfa 100644 --- a/gog.js +++ b/gog.js @@ -1,6 +1,6 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { dirs, jsonDb, datetime, filenamify, notify } from './util.js'; +import { dirs, jsonDb, datetime, filenamify, notify, html_game_list } from './util.js'; import { cfg } from './config.js'; import prompts from 'prompts'; // alternatives: enquirer, inquirer @@ -127,8 +127,7 @@ try { } finally { await db.write(); // write out json db if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed; TODO don't notify if killed? - const list = notify_games.map(g => `- ${g.title} (${g.status})`).join('
'); - notify(`gog:
${list}`); + notify(`gog:
${html_game_list(notify_games)}`); } } await context.close(); diff --git a/prime-gaming.js b/prime-gaming.js index b45e556..66e7c82 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,7 +1,7 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; import path from 'path'; -import { dirs, jsonDb, datetime, stealth, filenamify, notify } from './util.js'; +import { dirs, jsonDb, datetime, stealth, filenamify, notify, html_game_list } from './util.js'; import { cfg } from './config.js'; import prompts from 'prompts'; // alternatives: enquirer, inquirer @@ -139,7 +139,7 @@ try { console.log(' External store:', store); const url = page.url().split('?')[0]; db.data[user][title] ||= { title, time: datetime(), url, store }; - const notify_game = {title, url, status: `failed - link ${store}`}; + const notify_game = { title, url, status: `failed - link ${store}` }; notify_games.push(notify_game); // status is updated below if (await page.locator('div:has-text("Link game account")').count()) { console.error(' Account linking is required to claim this offer!'); @@ -182,8 +182,7 @@ try { } finally { await db.write(); // write out json db if (notify_games.length) { // list should only include claimed games - const list = notify_games.map(g => `- ${g.title} (${g.status})`).join('
'); - notify(`prime-gaming:
${list}`); + notify(`prime-gaming:
${html_game_list(notify_games)}`); } } await context.close(); diff --git a/util.js b/util.js index fb86c12..47d1134 100644 --- a/util.js +++ b/util.js @@ -95,3 +95,7 @@ export const notify = (html) => { if (stdout) console.log(`stdout: ${stdout}`); }); } + +export const escapeHtml = (unsafe) => unsafe.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); + +export const html_game_list = games => games.map(g => `- ${escapeHtml(g.title)} (${g.status})`).join('
'); From 01acfc231161380a599a27a673ae5a85fe6a6d91 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 27 Jan 2023 10:11:26 +0100 Subject: [PATCH 177/520] docker: pin playwright version since we install it before `npm i` https://github.com/microsoft/playwright/issues/13188 --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 69af099..6df9858 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ RUN apt-get update \ novnc websockify \ dos2unix \ python3-pip \ - && npx playwright install --with-deps firefox \ + && npx playwright@1.29 install --with-deps firefox \ && apt-get clean \ && rm -rf \ /tmp/* \ From 98dff72888259d9dd5f221d1fb6d01ed76e4e74d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 28 Jan 2023 10:26:27 +0100 Subject: [PATCH 178/520] eg: notify: set status existed correctly, fixes #50 --- epic-games.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 3077fd7..e1d8648 100644 --- a/epic-games.js +++ b/epic-games.js @@ -136,6 +136,7 @@ try { if (btnText.toLowerCase() == 'in library') { console.log(' Already in library! Nothing to claim.'); + notify_game.status = 'existed'; db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed if (db.data[user][game_id].status == 'failed') db.data[user][game_id].status = 'manual'; // was failed but now it's claimed } else { // GET @@ -186,11 +187,11 @@ try { await page.screenshot({ path: p, fullPage: true }); db.data[user][game_id].status = 'failed'; } + notify_game.status = db.data[user][game_id].status; // claimed or failed const p = path.resolve(dirs.screenshots, 'epic-games', `${game_id}.png`); if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... } - notify_game.status = db.data[user][game_id].status; } } catch (error) { console.error(error); // .toString()? From 88b4dcfcac86d29da3dc5e7df908d33c3c7fea83 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 28 Jan 2023 13:14:42 +0100 Subject: [PATCH 179/520] also run `node gog` by default, closes #52 --- Dockerfile | 2 +- README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6df9858..95b4d0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -63,4 +63,4 @@ ENV SHOW 1 # Script to setup display server & VNC is always executed. ENTRYPOINT ["docker-entrypoint.sh"] # Default command to run. This is replaced by appending own command, e.g. `docker run ... node prime-gaming` to only run this script. -CMD node epic-games; node prime-gaming +CMD node epic-games; node prime-gaming; node gog diff --git a/README.md b/README.md index 706d4e1..8a38086 100644 --- a/README.md +++ b/README.md @@ -23,8 +23,8 @@ Raspberry Pi (3, 4, Zero 2): Raspbian won't work since it's 32-bit, but Raspberr ``` docker run --rm -it -p 6080:6080 -v fgc:/fgc/data ghcr.io/vogler/free-games-claimer ``` -which will run `node epic-games; node prime-gaming`. If you only want to claim games for one store, you can override the default by appending e.g. `node epic-games` at the end of the `docker run` command. -Data is stored in the volume `fgc`. +which will run `node epic-games; node prime-gaming; node gog`. If you only want to claim games for one of the stores, you can override the default command by appending e.g. `node epic-games` at the end of the `docker run` command. +Data (including json files with claimed games, codes to redeem, screenshots) is stored in the Docker volume `fgc`.
I want to run without Docker or develop locally. From 0c2834eb5e3678ad468f6d174efd4b77d83de236 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 28 Jan 2023 19:55:22 +0100 Subject: [PATCH 180/520] readme: add `--pull=always` to `docker run`, #51 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8a38086..7841f0e 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Raspberry Pi (3, 4, Zero 2): Raspbian won't work since it's 32-bit, but Raspberr ## Setup [Install Docker](https://docs.docker.com/get-docker/) and use ``` -docker run --rm -it -p 6080:6080 -v fgc:/fgc/data ghcr.io/vogler/free-games-claimer +docker run --rm -it -p 6080:6080 -v fgc:/fgc/data --pull=always ghcr.io/vogler/free-games-claimer ``` which will run `node epic-games; node prime-gaming; node gog`. If you only want to claim games for one of the stores, you can override the default command by appending e.g. `node epic-games` at the end of the `docker run` command. Data (including json files with claimed games, codes to redeem, screenshots) is stored in the Docker volume `fgc`. From 9e0effa8c004edcd231ee3baa7ebfe6b31da317e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 28 Jan 2023 20:33:21 +0100 Subject: [PATCH 181/520] Update README.md --- README.md | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7841f0e..de25860 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ _Works on Windows/macOS/Linux._ Raspberry Pi (3, 4, Zero 2): Raspbian won't work since it's 32-bit, but Raspberry Pi OS (64-bit) or Ubuntu will. -## Setup +## How to run [Install Docker](https://docs.docker.com/get-docker/) and use ``` docker run --rm -it -p 6080:6080 -v fgc:/fgc/data --pull=always ghcr.io/vogler/free-games-claimer @@ -53,10 +53,10 @@ There will be prompts in the terminal asking you to enter email, password, and a After login, the script will continue claiming the current games. If it still waits after you are already logged in, you can restart it (and open an issue). If you run the scripts regularly, you should not have to login again. -### Options -Options are set via [environment variables](https://kinsta.com/knowledgebase/what-is-an-environment-variable/) which can be set in many ways and allow for flexible configuration. +### Configuration / Options +Options are set via [environment variables](https://kinsta.com/knowledgebase/what-is-an-environment-variable/) which allow for flexible configuration. -TODO: On the first run, the script will guide you through configuration and save all settings to `data/config.env`. You can edit this file directly or run `node fgc config` to run the configuration assistant again. +TODO: ~~On the first run, the script will guide you through configuration and save all settings to `data/config.env`. You can edit this file directly or run `node fgc config` to run the configuration assistant again.~~ Available options/variables and their default values: @@ -80,8 +80,11 @@ Available options/variables and their default values: See `config.js` for all options. -#### Other ways to set options -On Linux/macOS you can prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. +#### How to set options +You can put options in `data/config.env` which will be loaded by [dotenv](https://github.com/motdotla/dotenv). + +On Linux/macOS you can also prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. + For Docker you can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@bar.baz ...` or using `--env-file` (see [docs](https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file)). If you are using [docker compose](https://docs.docker.com/compose/environment-variables/), you can put them in the `environment:` section. ### Notifications @@ -106,27 +109,32 @@ Run `node epic-games` (locally or in Docker). ### Amazon Prime Gaming Run `node prime-gaming` (locally or in Docker). -Claiming the Amazon Games works, external Epic Games also work if the account is linked. -Keys for {Origin, GOG.com, Legacy Games} are printed to the console and need to be redeemed manually at the URL printed to the terminal ([issue](https://github.com/vogler/free-games-claimer/issues/5)). -A screenshot of the page with the code is saved to `data/screenshots` as well. +Claiming the Amazon Games works out-of-the-box, however, for games on external stores you need to either link your account or redeem a key. + +- Stores that require account linking: Epic Games, Battle.net. +- Stores that require redeeming a key: Origin, GOG.com, Legacy Games. + + Keys and URLs are printed to the console, included in notifications and saved in `data/prime-gaming.json`. A screenshot of the page with the key is also saved to `data/screenshots`. + [TODO](https://github.com/vogler/free-games-claimer/issues/5): ~~redeem keys on external stores.~~ ### Run periodically #### How often? Epic Games usually has two free games *every week*, before Christmas every day. Prime Gaming has new games *every month* or more often during Prime days. +GOG usually has one new game every couples of weeks. -It is save to run both scripts every day. +It is save to run the scripts every day. #### How to schedule? The container/scripts will claim currently available games and then exit. -If you want it to run regularly, you have to schedule the runs yourself. - -TODO: add some server-mode where the script just keeps running and claims games e.g. every day. +If you want it to run regularly, you have to schedule the runs yourself: - Linux/macOS: `crontab -e` - macOS: [launchd](https://stackoverflow.com/questions/132955/how-do-i-set-a-task-to-run-every-so-often) - Windows: [task scheduler](https://active-directory-wp.com/docs/Usage/How_to_add_a_cron_job_on_Windows/Scheduled_tasks_and_cron_jobs_on_Windows/index.html), [other options](https://stackoverflow.com/questions/132971/what-is-the-windows-version-of-cron) +TODO: ~~add some server-mode where the script just keeps running and claims games e.g. every day.~~ + ### Problems? Check the open [issues](https://github.com/vogler/free-games-claimer/issues) and comment there or open a new issue. @@ -172,6 +180,8 @@ Changed to Firefox for all scripts since Chromium led to captchas. Claiming then Added options via env vars, configurable in `data/config.env`. Added OTP generation via otplib for automatic login, even with 2FA. + +Added notifications via [apprise](https://github.com/caronc/apprise).
--- From c9cefcb7e1ad07a54d58707c424ddbec6ac5b00f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 28 Jan 2023 20:35:10 +0100 Subject: [PATCH 182/520] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index de25860..3fbc30b 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,7 @@ For Docker you can pass variables using `-e VAR=VAL`, for example `docker run -e The scripts will try to send notifications for successfully claimed games and any errors like needing to log in or encountered captchas (should not happen). [apprise](https://github.com/caronc/apprise) is used for notifications and offers many services including Pushover, Slack, Telegram, SMS, Email, desktop and custom notifications. -You just need to set `NOTIFY` to the notifications services you want to use, e.g. `NOTIFY='mailto://myemail:mypass@gmail.com' 'pbul://o.gn5kj6nfhv736I7jC3cj3QLRiyhgl98b'` - refer to their list of services and [examples](https://github.com/caronc/apprise#command-line-usage). +You just need to set `NOTIFY` to the notification services you want to use, e.g. `NOTIFY='mailto://myemail:mypass@gmail.com' 'pbul://o.gn5kj6nfhv736I7jC3cj3QLRiyhgl98b'` - refer to their list of services and [examples](https://github.com/caronc/apprise#command-line-usage). ### Automatic login, two-factor authentication If you set the options for email, password and OTP key, there will be no prompts and logins should happen automatically. This is optional since all stores should stay logged in since cookies are refreshed. From d7e5cc4a3a5a045ac034f32fbefa6597efb5a84a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 28 Jan 2023 20:36:33 +0100 Subject: [PATCH 183/520] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3fbc30b..230cf40 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ Run `node prime-gaming` (locally or in Docker). Claiming the Amazon Games works out-of-the-box, however, for games on external stores you need to either link your account or redeem a key. - Stores that require account linking: Epic Games, Battle.net. -- Stores that require redeeming a key: Origin, GOG.com, Legacy Games. +- Stores that require redeeming a key: Origin, GOG.com, Microsoft Games, Legacy Games. Keys and URLs are printed to the console, included in notifications and saved in `data/prime-gaming.json`. A screenshot of the page with the key is also saved to `data/screenshots`. [TODO](https://github.com/vogler/free-games-claimer/issues/5): ~~redeem keys on external stores.~~ From c5b0065a5091b905459d1719ad75a36bf5eab052 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 29 Jan 2023 19:28:02 +0100 Subject: [PATCH 184/520] TypeError: String includes not contains, #53; only first line of error --- epic-games.js | 4 ++-- gog.js | 4 ++-- prime-gaming.js | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/epic-games.js b/epic-games.js index e1d8648..113632b 100644 --- a/epic-games.js +++ b/epic-games.js @@ -195,8 +195,8 @@ try { } } catch (error) { console.error(error); // .toString()? - if (error.message && !error.message.contains('Target closed')) // e.g. when killed by Ctrl-C - notify(`epic-games failed: ${error.message}`); + if (error.message && !error.message.includes('Target closed')) // e.g. when killed by Ctrl-C + notify(`epic-games failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed; TODO don't notify if killed? diff --git a/gog.js b/gog.js index 685fcfa..c62e48e 100644 --- a/gog.js +++ b/gog.js @@ -122,8 +122,8 @@ try { } } catch (error) { console.error(error); // .toString()? - if (!error.message.contains('Target closed')) // e.g. when killed by Ctrl-C - notify(`prime-gaming failed: ${error.message}`); + if (error.message && !error.message.includes('Target closed')) // e.g. when killed by Ctrl-C + notify(`gog failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed; TODO don't notify if killed? diff --git a/prime-gaming.js b/prime-gaming.js index 66e7c82..21d68e3 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -177,8 +177,8 @@ try { await page.locator(games_sel).screenshot({ path: p }); } catch (error) { console.error(error); // .toString()? - if (error.message && !error.message.contains('Target closed')) // e.g. when killed by Ctrl-C - notify(`prime-gaming failed: ${error.message}`); + if (error.message && !error.message.includes('Target closed')) // e.g. when killed by Ctrl-C + notify(`prime-gaming failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db if (notify_games.length) { // list should only include claimed games From d4bf4a7af0b20454f412dc5e5fed4ade981eefe7 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 29 Jan 2023 19:40:44 +0100 Subject: [PATCH 185/520] gog: wait for username after login, barrier before, fixes #53 --- gog.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/gog.js b/gog.js index c62e48e..6093f15 100644 --- a/gog.js +++ b/gog.js @@ -37,14 +37,15 @@ try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever // page.click('#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll').catch(_ => { }); // does not work reliably, solved by setting CookieConsent above - // await Promise.any([page.waitForSelector('a:has-text("Sign in")', {}), page.waitForSelector('#menuUsername')]); - while (await page.locator('a:has-text("Sign in")').first().isVisible()) { + const signIn = page.locator('a:has-text("Sign in")').first(); + await Promise.any([signIn.waitFor(), page.waitForSelector('#menuUsername')]); + while (await signIn.isVisible()) { console.error('Not signed in anymore.'); - await page.click('a:has-text("Sign in")'); + await signIn.click(); // it then creates an iframe for the login await page.waitForSelector('#GalaxyAccountsFrameContainer iframe'); // TODO needed? const iframe = page.frameLocator('#GalaxyAccountsFrameContainer iframe'); - if (!cfg.debug) context.setDefaultTimeout(0); // give user time to log in without timeout + context.setDefaultTimeout(0); // give user time to log in without timeout console.info('Press ESC to skip if you want to login in the browser (not possible in headless mode).'); const email = cfg.gog_email || await prompt({message: 'Enter email'}); const password = cfg.gog_password || await prompt({type: 'password', message: 'Enter password'}); @@ -53,6 +54,7 @@ try { await iframe.locator('#login_username').fill(email); await iframe.locator('#login_password').fill(password); await iframe.locator('#login_login').click(); + await page.waitForSelector('#menuUsername') // handle MFA, but don't await it iframe.locator('form[name=second_step_authentication]').waitFor().then(async () => { console.log('Two-Step Verification - Enter security code'); @@ -61,7 +63,7 @@ try { await iframe.locator('#second_step_authentication_token_letter_1').type(otp.toString(), {delay: 10}); await iframe.locator('#second_step_authentication_send').click(); await page.waitForTimeout(1000); // TODO wait for something else below? - }); + }).catch(_ => { }); } else { console.log('Waiting for you to login in the browser.'); notify('gog: no longer signed in and not enough options set for automatic login.'); @@ -72,7 +74,7 @@ try { } } // await page.waitForNavigation(); // TODO was blocking - if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); + context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('#menuUsername').first().textContent(); // innerText is uppercase due to styling! console.log(`Signed in as '${user}'`); @@ -88,8 +90,10 @@ try { const url = `https://gog.com${slug}`; console.log(`Current free game: ${title} - ${url}`); db.data[user][title] ||= { title, time: datetime(), url }; + if (cfg.dryrun) process.exit(1); const p = path.resolve(dirs.screenshots, 'gog', `${filenamify(title)}.png`); await banner.screenshot({ path: p }); // overwrites every time - only keep first? + // await banner.getByRole('button', { name: 'Add to library' }).click(); // instead of clicking the button, we visit the auto-claim URL which gives as a JSON response which is easier than checking the state of a button await page.goto('https://www.gog.com/giveaway/claim'); @@ -122,7 +126,7 @@ try { } } catch (error) { console.error(error); // .toString()? - if (error.message && !error.message.includes('Target closed')) // e.g. when killed by Ctrl-C + if (error.message && !error.message.includes('Target closed') && !error.message.includes('Browser closed')) // e.g. when killed by Ctrl-C notify(`gog failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db From 0393a3998b2d877be15992dca948913ab00bfac8 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 29 Jan 2023 19:52:18 +0100 Subject: [PATCH 186/520] info about escaping prompts only if needed --- epic-games.js | 3 ++- gog.js | 3 ++- prime-gaming.js | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index 113632b..2e26078 100644 --- a/epic-games.js +++ b/epic-games.js @@ -74,7 +74,8 @@ try { context.setDefaultTimeout(0); // give user time to log in without timeout await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); - console.info('Press ESC to skip if you want to login in the browser.'); + if (cfg.eg_email && cfg.eg_password) console.info('Using email and password from environment.'); + else console.info('Press ESC to skip if you want to login in the browser.'); const email = cfg.eg_email || await prompt({message: 'Enter email'}); const password = cfg.eg_password || await prompt({type: 'password', message: 'Enter password'}); if (email && password) { diff --git a/gog.js b/gog.js index 6093f15..01049c5 100644 --- a/gog.js +++ b/gog.js @@ -46,7 +46,8 @@ try { await page.waitForSelector('#GalaxyAccountsFrameContainer iframe'); // TODO needed? const iframe = page.frameLocator('#GalaxyAccountsFrameContainer iframe'); context.setDefaultTimeout(0); // give user time to log in without timeout - console.info('Press ESC to skip if you want to login in the browser (not possible in headless mode).'); + if (cfg.gog_email && cfg.gog_password) console.info('Using email and password from environment.'); + else console.info('Press ESC to skip if you want to login in the browser (not possible in headless mode).'); const email = cfg.gog_email || await prompt({message: 'Enter email'}); const password = cfg.gog_password || await prompt({type: 'password', message: 'Enter password'}); if (email && password) { diff --git a/prime-gaming.js b/prime-gaming.js index 21d68e3..5a5297e 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -52,7 +52,8 @@ try { console.error('Not signed in anymore.'); await page.click('button:has-text("Sign in")'); if (!cfg.debug) context.setDefaultTimeout(0); // give user time to log in without timeout - console.info('Press ESC to skip if you want to login in the browser (not possible in default headless mode).'); + if (cfg.pg_email && cfg.pg_password) console.info('Using email and password from environment.'); + else console.info('Press ESC to skip if you want to login in the browser (not possible in default headless mode).'); const email = cfg.pg_email || await prompt({message: 'Enter email'}); const password = cfg.pg_password || await prompt({type: 'password', message: 'Enter password'}); if (email && password) { From 21a4e062094ff4ce7b7b3b036c5727117b889531 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 29 Jan 2023 20:25:54 +0100 Subject: [PATCH 187/520] gog: check for reCAPTCHA on login; better info for eg --- epic-games.js | 2 +- gog.js | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/epic-games.js b/epic-games.js index 2e26078..8b25bcb 100644 --- a/epic-games.js +++ b/epic-games.js @@ -84,7 +84,7 @@ try { await page.fill('#password', password); await page.click('button[type="submit"]'); page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(() => { - console.log('Got a captcha! You may have to solve it in the browser if the NopeCHA extension fails to do so.'); + console.error('Got a captcha during login (likely due to too many attempts)! You may solve it in the browser, get a new IP or try again in a few hours.'); notify('epic-games: got captcha during login. Please check.'); }).catch(_ => { }); // handle MFA, but don't await it diff --git a/gog.js b/gog.js index 01049c5..2369610 100644 --- a/gog.js +++ b/gog.js @@ -55,7 +55,6 @@ try { await iframe.locator('#login_username').fill(email); await iframe.locator('#login_password').fill(password); await iframe.locator('#login_login').click(); - await page.waitForSelector('#menuUsername') // handle MFA, but don't await it iframe.locator('form[name=second_step_authentication]').waitFor().then(async () => { console.log('Two-Step Verification - Enter security code'); @@ -63,14 +62,22 @@ try { const otp = await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 4 || 'The code must be 4 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them await iframe.locator('#second_step_authentication_token_letter_1').type(otp.toString(), {delay: 10}); await iframe.locator('#second_step_authentication_send').click(); - await page.waitForTimeout(1000); // TODO wait for something else below? + await page.waitForTimeout(1000); // TODO still needed with wait for username below? }).catch(_ => { }); + // iframe.locator('iframe[title=reCAPTCHA]').waitFor().then(() => { + // iframe.locator('.g-recaptcha').waitFor().then(() => { + iframe.locator('text=Invalid captcha').waitFor().then(() => { + console.error('Got a captcha during login (likely due to too many attempts)! You may solve it in the browser, get a new IP or try again in a few hours.'); + notify('gog: got captcha during login. Please check.'); + // TODO solve reCAPTCHA? + }).catch(_ => { }); + await page.waitForSelector('#menuUsername') } else { console.log('Waiting for you to login in the browser.'); notify('gog: no longer signed in and not enough options set for automatic login.'); if (cfg.headless) { - console.log('Please run `node gog show` to login in the opened browser.'); - await context.close(); // not needed? + console.log('Run `SHOW=1 node gog` to login in the opened browser.'); + await context.close(); process.exit(1); } } From eb17a496281b691b11cb61a43f69cc0abbb2492d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 3 Feb 2023 16:03:01 +0100 Subject: [PATCH 188/520] extract prompt into util.js --- epic-games.js | 7 +------ gog.js | 7 +------ prime-gaming.js | 7 +------ util.js | 12 ++++++++++++ 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/epic-games.js b/epic-games.js index 8b25bcb..704bfd1 100644 --- a/epic-games.js +++ b/epic-games.js @@ -2,14 +2,9 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwr import { authenticator } from 'otplib'; import path from 'path'; import { existsSync, writeFileSync } from 'fs'; -import { dirs, jsonDb, datetime, stealth, filenamify, notify, html_game_list } from './util.js'; +import { dirs, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list } from './util.js'; import { cfg } from './config.js'; -import prompts from 'prompts'; // alternatives: enquirer, inquirer -// import enquirer from 'enquirer'; const { prompt } = enquirer; -// single prompt that just returns the non-empty value instead of an object - why name things if there's just one? -const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; - const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; diff --git a/gog.js b/gog.js index 2369610..feef173 100644 --- a/gog.js +++ b/gog.js @@ -1,13 +1,8 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { dirs, jsonDb, datetime, filenamify, notify, html_game_list } from './util.js'; +import { dirs, jsonDb, datetime, filenamify, prompt, notify, html_game_list } from './util.js'; import { cfg } from './config.js'; -import prompts from 'prompts'; // alternatives: enquirer, inquirer -// import enquirer from 'enquirer'; const { prompt } = enquirer; -// single prompt that just returns the non-empty value instead of an object - why name things if there's just one? -const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; - const URL_CLAIM = 'https://www.gog.com/en'; console.log(datetime(), 'started checking gog'); diff --git a/prime-gaming.js b/prime-gaming.js index 5a5297e..d4e52ab 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,14 +1,9 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; import path from 'path'; -import { dirs, jsonDb, datetime, stealth, filenamify, notify, html_game_list } from './util.js'; +import { dirs, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list } from './util.js'; import { cfg } from './config.js'; -import prompts from 'prompts'; // alternatives: enquirer, inquirer -// import enquirer from 'enquirer'; const { prompt } = enquirer; -// single prompt that just returns the non-empty value instead of an object - why name things if there's just one? -const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; - // const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? const URL_CLAIM = 'https://gaming.amazon.com/home'; diff --git a/util.js b/util.js index 47d1134..f2123a0 100644 --- a/util.js +++ b/util.js @@ -12,6 +12,8 @@ export const dirs = { screenshots: dataDir('screenshots'), }; + +// json database import { Low } from 'lowdb'; import { JSONFile } from 'lowdb/node'; export const jsonDb = async file => { @@ -20,13 +22,16 @@ export const jsonDb = async file => { return db; }; + // date and time as UTC (no timezone offset) in nicely readable and sortable format, e.g., 2022-10-06 12:05:27.313 export const datetime = (d = new Date()) => d.toISOString().replace('T', ' ').replace('Z', ''); // same as datetime() but for local timezone, e.g., UTC + 2h for the above in DE export const datetimeLocal = (d = new Date()) => datetime(new Date(d.getTime() - new Date().getTimezoneOffset() * 60000)); export const filenamify = s => s.replaceAll(':', '.').replace(/[^a-z0-9 _\-.]/gi, '_'); // alternative: https://www.npmjs.com/package/filenamify - On Unix-like systems, / is reserved. On Windows, <>:"/\|?* along with trailing periods are reserved. + // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 +// gets userAgent and then removes "Headless" from it const newStealthContext = async (browser, contextOptions = {}, debug = false) => { if (!debug) { // only need to fix userAgent in headless mode const dummyContext = await browser.newContext(); @@ -77,6 +82,13 @@ export const stealth = async (context) => { } }; + +import prompts from 'prompts'; // alternatives: enquirer, inquirer +// import enquirer from 'enquirer'; const { prompt } = enquirer; +// single prompt that just returns the non-empty value instead of an object - why name things if there's just one? +export const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; + + // notifications via apprise CLI import { exec } from 'child_process'; import { cfg } from './config.js'; From b9e9abe546e77ce56443d198c9a4f1be29ae118c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 4 Feb 2023 21:37:04 +0100 Subject: [PATCH 189/520] page.waitForNavigation -> page.waitForURL --- epic-games.js | 4 ++-- gog.js | 1 - prime-gaming.js | 9 ++++----- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/epic-games.js b/epic-games.js index 704bfd1..e08bf68 100644 --- a/epic-games.js +++ b/epic-games.js @@ -83,7 +83,7 @@ try { notify('epic-games: got captcha during login. Please check.'); }).catch(_ => { }); // handle MFA, but don't await it - page.waitForNavigation({ url: '**/id/login/mfa**'}).then(async () => { + page.waitForURL('**/id/login/mfa**').then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); // TODO locator for text (email or app?) const otp = cfg.eg_otpkey && authenticator.generate(cfg.eg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them @@ -94,7 +94,7 @@ try { console.log('Waiting for you to login in the browser.'); notify('epic-games: no longer signed in and not enough options set for automatic login.'); } - await page.waitForNavigation({ url: URL_CLAIM }); + await page.waitForURL(URL_CLAIM); context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('#user span').first().innerHTML(); diff --git a/gog.js b/gog.js index feef173..02dc68f 100644 --- a/gog.js +++ b/gog.js @@ -76,7 +76,6 @@ try { process.exit(1); } } - // await page.waitForNavigation(); // TODO was blocking context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('#menuUsername').first().textContent(); // innerText is uppercase due to styling! diff --git a/prime-gaming.js b/prime-gaming.js index d4e52ab..c340014 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -56,7 +56,7 @@ try { await page.fill('[name=password]', password); await page.check('[name=rememberMe]'); await page.click('input[type="submit"]'); - page.waitForNavigation({ url: '**/ap/signin**'}).then(async () => { // check for wrong credentials + page.waitForURL('**/ap/signin**').then(async () => { // check for wrong credentials const error = await page.locator('.a-alert-content').first().innerText(); console.error(error); notify(`prime-gaming: login: ${error}`); @@ -64,7 +64,7 @@ try { process.exit(1); }); // handle MFA, but don't await it - page.waitForNavigation({ url: '**/ap/mfa**'}).then(async () => { + page.waitForURL('**/ap/mfa**').then(async () => { console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App'); await page.check('[name=rememberDevice]'); const otp = cfg.pg_otpkey && authenticator.generate(cfg.pg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them @@ -80,7 +80,7 @@ try { process.exit(1); } } - await page.waitForNavigation({ url: 'https://gaming.amazon.com/home?signedIn=true' }); + await page.waitForURL('https://gaming.amazon.com/home?signedIn=true'); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('[data-a-target="user-dropdown-first-name-text"]').first().innerText(); @@ -125,8 +125,7 @@ try { const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); if (cfg.dryrun) continue; - await (await card.$('text=Claim')).click(); - // await page.waitForNavigation(); + await (await card.$('text=Claim')).click(); // goes to URL of game, no need to wait await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); // Full game for PC [and MAC] on: gog.com, Origin, Legacy Games, EPIC GAMES, Battle.net From 65a389c3d926f3031a073458a79ccb33a019abc4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 5 Feb 2023 23:33:45 +0100 Subject: [PATCH 190/520] docker: don't pin playwright, split: system deps + firefox after `npm install` `npx playwright install --with-deps firefox` fails since package.json references 1.29 but current version is 1.30: `browserType.launchPersistentContext: Executable doesn't exist at /root/.cache/ms-playwright/firefox-1369/firefox/firefox` Need to either pin the version there or install the browser after `npm install` which will make it take into account the package.json version. See https://github.com/microsoft/playwright/issues/13188, https://github.com/microsoft/playwright/issues/12835 Image size: 1. 1.12GB - `npx playwright install --with-deps firefox` -> fails due to wrong browser version 2. 1.12GB - `npx playwright@1.29 install --with-deps firefox` -> pinning version works, but need to also update Dockerfile when updating package.json 3. 920MB - `npx playwright install-deps firefox` -> fails, system deps only, no browser 4. 1.12GB - 3. + `npx install firefox` after `npm install` -> works, no pinning needed, system deps stuff is cleaned up with the rest of the apt installs; breaks if system deps change... --- Dockerfile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 95b4d0f..6967e08 100644 --- a/Dockerfile +++ b/Dockerfile @@ -23,7 +23,7 @@ RUN apt-get update \ novnc websockify \ dos2unix \ python3-pip \ - && npx playwright@1.29 install --with-deps firefox \ + && npx playwright install-deps firefox \ && apt-get clean \ && rm -rf \ /tmp/* \ @@ -38,7 +38,8 @@ RUN pip install apprise WORKDIR /fgc COPY package*.json ./ -RUN npm install +# If firefox is installed (~/.cache/ms-playwright/firefox-*) before `npm install` it may be a newer version than in package.json and playwright will not find it; system deps are installed sep. via apt above to avoid having to pin the version there. +RUN npm install && npx playwright install firefox COPY . . From dff712d99875a12061c772e2f6a82ad7ba333a8e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 9 Feb 2023 16:28:27 +0100 Subject: [PATCH 191/520] skip prompt for password if email is missing --- epic-games.js | 2 +- gog.js | 2 +- prime-gaming.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index e08bf68..710d574 100644 --- a/epic-games.js +++ b/epic-games.js @@ -72,7 +72,7 @@ try { if (cfg.eg_email && cfg.eg_password) console.info('Using email and password from environment.'); else console.info('Press ESC to skip if you want to login in the browser.'); const email = cfg.eg_email || await prompt({message: 'Enter email'}); - const password = cfg.eg_password || await prompt({type: 'password', message: 'Enter password'}); + const password = email && (cfg.eg_password || await prompt({type: 'password', message: 'Enter password'})); if (email && password) { await page.click('text=Sign in with Epic Games'); await page.fill('#email', email); diff --git a/gog.js b/gog.js index 02dc68f..edecd5f 100644 --- a/gog.js +++ b/gog.js @@ -44,7 +44,7 @@ try { if (cfg.gog_email && cfg.gog_password) console.info('Using email and password from environment.'); else console.info('Press ESC to skip if you want to login in the browser (not possible in headless mode).'); const email = cfg.gog_email || await prompt({message: 'Enter email'}); - const password = cfg.gog_password || await prompt({type: 'password', message: 'Enter password'}); + const password = email && (cfg.gog_password || await prompt({type: 'password', message: 'Enter password'})); if (email && password) { iframe.locator('a[href="/logout"]').click().catch(_ => { }); // Click 'Change account' (email from previous login is set in some cookie) await iframe.locator('#login_username').fill(email); diff --git a/prime-gaming.js b/prime-gaming.js index c340014..d276a72 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -50,7 +50,7 @@ try { if (cfg.pg_email && cfg.pg_password) console.info('Using email and password from environment.'); else console.info('Press ESC to skip if you want to login in the browser (not possible in default headless mode).'); const email = cfg.pg_email || await prompt({message: 'Enter email'}); - const password = cfg.pg_password || await prompt({type: 'password', message: 'Enter password'}); + const password = email && (cfg.pg_password || await prompt({type: 'password', message: 'Enter password'})); if (email && password) { await page.fill('[name=email]', email); await page.fill('[name=password]', password); From 11a28f0c73de435775e9be2303b3260a70b06cdd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 9 Feb 2023 16:47:59 +0100 Subject: [PATCH 192/520] `ncu -u` updated lowdb, playwright --- package-lock.json | 44 ++++++++++++++++++++++---------------------- package.json | 4 ++-- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3280933..1318365 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,9 @@ "dependencies": { "cross-env": "^7.0.3", "dotenv": "^16.0.3", - "lowdb": "^5.0.5", + "lowdb": "^5.1.0", "otplib": "^12.0.1", - "playwright": "^1.29.0", + "playwright": "^1.30.0", "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" } @@ -328,9 +328,9 @@ } }, "node_modules/lowdb": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-5.0.5.tgz", - "integrity": "sha512-7EWKmIMhNKA8TXFhL8t0p6N2LC53l3ZqsWQGSksGhhjrcms9rbKlyrAh2PzSGK5v0KPJ2W5VItBnC3NDRzOnzQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-5.1.0.tgz", + "integrity": "sha512-OEysJ2S3j05RqehEypEv3h6EgdV4Y7LTq7LngRNqe1IxsInOm66/sa3fzoI6mmqs2CC+zIJW3vfncGNv2IGi3A==", "dependencies": { "steno": "^3.0.0" }, @@ -425,12 +425,12 @@ } }, "node_modules/playwright": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.29.0.tgz", - "integrity": "sha512-vtXgX3FPNcAJq1QoIVCvmiHHKOLVTZkSYEo60n+EnX5MrNznAJzGquxT8c2sv+BG3CDyLeKm351e491HnF7yjw==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.30.0.tgz", + "integrity": "sha512-ENbW5o75HYB3YhnMTKJLTErIBExrSlX2ZZ1C/FzmHjUYIfxj/UnI+DWpQr992m+OQVSg0rCExAOlRwB+x+yyIg==", "hasInstallScript": true, "dependencies": { - "playwright-core": "1.29.0" + "playwright-core": "1.30.0" }, "bin": { "playwright": "cli.js" @@ -440,9 +440,9 @@ } }, "node_modules/playwright-core": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.29.0.tgz", - "integrity": "sha512-pboOm1m0RD6z1GtwAbEH60PYRfF87vKdzOSRw2RyO0Y0a7utrMyWN2Au1ojGvQr4umuBMODkKTv607YIRypDSQ==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.30.0.tgz", + "integrity": "sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==", "bin": { "playwright": "cli.js" }, @@ -926,9 +926,9 @@ "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" }, "lowdb": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-5.0.5.tgz", - "integrity": "sha512-7EWKmIMhNKA8TXFhL8t0p6N2LC53l3ZqsWQGSksGhhjrcms9rbKlyrAh2PzSGK5v0KPJ2W5VItBnC3NDRzOnzQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-5.1.0.tgz", + "integrity": "sha512-OEysJ2S3j05RqehEypEv3h6EgdV4Y7LTq7LngRNqe1IxsInOm66/sa3fzoI6mmqs2CC+zIJW3vfncGNv2IGi3A==", "requires": { "steno": "^3.0.0" } @@ -1001,17 +1001,17 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.29.0.tgz", - "integrity": "sha512-vtXgX3FPNcAJq1QoIVCvmiHHKOLVTZkSYEo60n+EnX5MrNznAJzGquxT8c2sv+BG3CDyLeKm351e491HnF7yjw==", + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.30.0.tgz", + "integrity": "sha512-ENbW5o75HYB3YhnMTKJLTErIBExrSlX2ZZ1C/FzmHjUYIfxj/UnI+DWpQr992m+OQVSg0rCExAOlRwB+x+yyIg==", "requires": { - "playwright-core": "1.29.0" + "playwright-core": "1.30.0" } }, "playwright-core": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.29.0.tgz", - "integrity": "sha512-pboOm1m0RD6z1GtwAbEH60PYRfF87vKdzOSRw2RyO0Y0a7utrMyWN2Au1ojGvQr4umuBMODkKTv607YIRypDSQ==" + "version": "1.30.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.30.0.tgz", + "integrity": "sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==" }, "prompts": { "version": "2.4.2", diff --git a/package.json b/package.json index 6f90c03..cfd941c 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,9 @@ "dependencies": { "cross-env": "^7.0.3", "dotenv": "^16.0.3", - "lowdb": "^5.0.5", + "lowdb": "^5.1.0", "otplib": "^12.0.1", - "playwright": "^1.29.0", + "playwright": "^1.30.0", "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" }, From d3e4c58c803a07e95745c689b044db39627a638f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 9 Feb 2023 16:53:42 +0100 Subject: [PATCH 193/520] gog: only unsubscribe from newsletter if a game was claimed? --- gog.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/gog.js b/gog.js index edecd5f..713c09b 100644 --- a/gog.js +++ b/gog.js @@ -122,9 +122,11 @@ try { db.data[user][title].status ||= status; notify_games.push({ title, url, status }); - console.log("Unsubscribe from 'Promotions and hot deals' newsletter"); - await page.goto('https://www.gog.com/en/account/settings/subscriptions'); - await page.locator('li:has-text("Promotions and hot deals") label').uncheck(); + if (status == 'claimed') { // TODO check if this is enough or if newsleter is enabled if 'existed' + console.log("Unsubscribe from 'Promotions and hot deals' newsletter"); + await page.goto('https://www.gog.com/en/account/settings/subscriptions'); + await page.locator('li:has-text("Promotions and hot deals") label').uncheck(); + } } } catch (error) { console.error(error); // .toString()? From 3ada6bbc23cc0520afa22000ab810552e7093f55 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 9 Feb 2023 19:49:51 +0100 Subject: [PATCH 194/520] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 230cf40..77da4da 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,11 @@ _Works on Windows/macOS/Linux._ Raspberry Pi (3, 4, Zero 2): Raspbian won't work since it's 32-bit, but Raspberry Pi OS (64-bit) or Ubuntu will. ## How to run -[Install Docker](https://docs.docker.com/get-docker/) and use +Easy option: [install Docker](https://docs.docker.com/get-docker/) (or [podman](https://podman-desktop.io/)) and run this command in a terminal (Windows: `cmd`, `.bat` file): ``` docker run --rm -it -p 6080:6080 -v fgc:/fgc/data --pull=always ghcr.io/vogler/free-games-claimer ``` -which will run `node epic-games; node prime-gaming; node gog`. If you only want to claim games for one of the stores, you can override the default command by appending e.g. `node epic-games` at the end of the `docker run` command. +This will run `node epic-games; node prime-gaming; node gog` - if you only want to claim games for one of the stores, you can override the default command by appending e.g. `node epic-games` at the end of the `docker run` command. Data (including json files with claimed games, codes to redeem, screenshots) is stored in the Docker volume `fgc`.
From f873d95a8987bde3069aa6873a82aaa20df8aac4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 10 Feb 2023 17:55:55 +0100 Subject: [PATCH 195/520] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 77da4da..b17fdf5 100644 --- a/README.md +++ b/README.md @@ -129,7 +129,7 @@ It is save to run the scripts every day. The container/scripts will claim currently available games and then exit. If you want it to run regularly, you have to schedule the runs yourself: -- Linux/macOS: `crontab -e` +- Linux/macOS: `crontab -e` ([example](https://github.com/vogler/free-games-claimer/discussions/56)) - macOS: [launchd](https://stackoverflow.com/questions/132955/how-do-i-set-a-task-to-run-every-so-often) - Windows: [task scheduler](https://active-directory-wp.com/docs/Usage/How_to_add_a_cron_job_on_Windows/Scheduled_tasks_and_cron_jobs_on_Windows/index.html), [other options](https://stackoverflow.com/questions/132971/what-is-the-windows-version-of-cron) From acbfa9156e201c3d8dac1c3905381147be019c68 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 14 Feb 2023 10:29:21 +0100 Subject: [PATCH 196/520] pg: ignore empty login error message, fixes #58 --- prime-gaming.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index d276a72..a6e5630 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -58,7 +58,8 @@ try { await page.click('input[type="submit"]'); page.waitForURL('**/ap/signin**').then(async () => { // check for wrong credentials const error = await page.locator('.a-alert-content').first().innerText(); - console.error(error); + if (!error.trim.length) return; + console.error('Login error:', error); notify(`prime-gaming: login: ${error}`); await context.close(); // finishes potential recording process.exit(1); @@ -75,7 +76,7 @@ try { console.log('Waiting for you to login in the browser.'); notify('prime-gaming: no longer signed in and not enough options set for automatic login.'); if (cfg.headless) { - console.log('Please run `node prime-gaming show` to login in the opened browser.'); + console.log('Please run `SHOW=1 node prime-gaming` to login in the opened browser.'); await context.close(); // finishes potential recording process.exit(1); } @@ -171,7 +172,7 @@ try { // await page.screenshot({ path: p, fullPage: true }); await page.locator(games_sel).screenshot({ path: p }); } catch (error) { - console.error(error); // .toString()? + console.error('Catch error:', error); // .toString()? if (error.message && !error.message.includes('Target closed')) // e.g. when killed by Ctrl-C notify(`prime-gaming failed: ${error.message.split('\n')[0]}`); } finally { From af34113eaf35fda78d4b7d11707db9b3534f704e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 14 Feb 2023 18:45:23 +0100 Subject: [PATCH 197/520] mention pm2 for restarting --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index b17fdf5..b3da0ed 100644 --- a/README.md +++ b/README.md @@ -132,6 +132,7 @@ If you want it to run regularly, you have to schedule the runs yourself: - Linux/macOS: `crontab -e` ([example](https://github.com/vogler/free-games-claimer/discussions/56)) - macOS: [launchd](https://stackoverflow.com/questions/132955/how-do-i-set-a-task-to-run-every-so-often) - Windows: [task scheduler](https://active-directory-wp.com/docs/Usage/How_to_add_a_cron_job_on_Windows/Scheduled_tasks_and_cron_jobs_on_Windows/index.html), [other options](https://stackoverflow.com/questions/132971/what-is-the-windows-version-of-cron) +- any OS: use a process manager like [pm2](https://pm2.keymetrics.io/docs/usage/restart-strategies/) TODO: ~~add some server-mode where the script just keeps running and claims games e.g. every day.~~ From c058bafcf3a7d8227459f46cb36048627ffc335c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 14 Feb 2023 10:38:57 +0100 Subject: [PATCH 198/520] remove migrateDb comment: remove this after some time since it will run fine without and people can still use this commit to adjust their data/epic-games.json and data/prime-gaming.json --- epic-games.js | 11 ----------- prime-gaming.js | 10 ---------- 2 files changed, 21 deletions(-) diff --git a/epic-games.js b/epic-games.js index 710d574..e29ee42 100644 --- a/epic-games.js +++ b/epic-games.js @@ -12,16 +12,6 @@ console.log(datetime(), 'started checking epic-games'); const db = await jsonDb('epic-games.json'); db.data ||= {}; -const migrateDb = (user) => { - if (user in db.data || !('claimed' in db.data)) return; - db.data[user] = {}; - for (const e of db.data.claimed) { - const k = e.url.split('/').pop(); - db.data[user][k] = e; - } - delete db.data.claimed; - delete db.data.runs; -} // https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox @@ -99,7 +89,6 @@ try { } const user = await page.locator('#user span').first().innerHTML(); console.log(`Signed in as ${user}`); - migrateDb(user); // TODO remove this after some time since it will run fine without and people can still use this commit to adjust their data epic-games.json db.data[user] ||= {}; // Detect free games diff --git a/prime-gaming.js b/prime-gaming.js index a6e5630..2001361 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -11,15 +11,6 @@ console.log(datetime(), 'started checking prime-gaming'); const db = await jsonDb('prime-gaming.json'); db.data ||= {}; -const migrateDb = (user) => { - if (user in db.data || !('claimed' in db.data)) return; - db.data[user] = {}; - for (const e of db.data.claimed) { - db.data[user][e.title] = e; - } - delete db.data.claimed; - delete db.data.runs; -} // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(dirs.browser, { @@ -89,7 +80,6 @@ try { // await page.click('button[aria-label="User dropdown and more options"]'); // const twitch = await page.locator('[data-a-target="TwitchDisplayName"]').first().innerText(); // console.log(`Twitch user name is ${twitch}`); - migrateDb(user); // TODO remove this after some time since it will run fine without and people can still use this commit to adjust their data/prime-gaming.json db.data[user] ||= {}; await page.click('button[data-type="Game"]'); From c65f1530905dd6c54d60b2f0482d5ffe9f4646d0 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 14 Feb 2023 10:43:24 +0100 Subject: [PATCH 199/520] remove not needed await --- epic-games.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index e29ee42..735b101 100644 --- a/epic-games.js +++ b/epic-games.js @@ -92,7 +92,7 @@ try { db.data[user] ||= {}; // Detect free games - const game_loc = await page.locator('a:has(span:text-is("Free Now"))'); + const game_loc = page.locator('a:has(span:text-is("Free Now"))'); await game_loc.last().waitFor(); // clicking on `game_sel` sometimes led to a 404, see https://github.com/vogler/free-games-claimer/issues/25 // debug showed that in those cases the href was still correct, so we `goto` the urls instead of clicking. @@ -188,5 +188,5 @@ try { notify(`epic-games:
${html_game_list(notify_games)}`); } } -await writeFileSync(path.resolve(dirs.browser, 'cookies.json'), JSON.stringify(await context.cookies())); +writeFileSync(path.resolve(dirs.browser, 'cookies.json'), JSON.stringify(await context.cookies())); await context.close(); From ec2d31ed793ac0b14d8a6265ec68e0e80abc5854 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 14 Feb 2023 10:49:06 +0100 Subject: [PATCH 200/520] remove unused NopeCHA extension --- epic-games.js | 8 +- nopecha/background.js | 1 - nopecha/hcaptcha.js | 1 - nopecha/hcaptcha_fast.js | 1 - nopecha/hcaptcha_language.js | 1 - nopecha/icon/128.png | Bin 9225 -> 0 bytes nopecha/icon/16.png | Bin 1103 -> 0 bytes nopecha/icon/32.png | Bin 1928 -> 0 bytes nopecha/icon/48.png | Bin 2828 -> 0 bytes nopecha/manifest.json | 1 - nopecha/popup.css | 289 ----------------------------------- nopecha/popup.html | 149 ------------------ nopecha/popup.js | 1 - nopecha/recaptcha.js | 1 - nopecha/recaptcha_fast.js | 1 - nopecha/recaptcha_voice.js | 1 - nopecha/setup.js | 1 - nopecha/utils.js | 1 - 18 files changed, 5 insertions(+), 452 deletions(-) delete mode 100644 nopecha/background.js delete mode 100644 nopecha/hcaptcha.js delete mode 100644 nopecha/hcaptcha_fast.js delete mode 100644 nopecha/hcaptcha_language.js delete mode 100644 nopecha/icon/128.png delete mode 100644 nopecha/icon/16.png delete mode 100644 nopecha/icon/32.png delete mode 100644 nopecha/icon/48.png delete mode 100644 nopecha/manifest.json delete mode 100644 nopecha/popup.css delete mode 100644 nopecha/popup.html delete mode 100644 nopecha/popup.js delete mode 100644 nopecha/recaptcha.js delete mode 100644 nopecha/recaptcha_fast.js delete mode 100644 nopecha/recaptcha_voice.js delete mode 100644 nopecha/setup.js delete mode 100644 nopecha/utils.js diff --git a/epic-games.js b/epic-games.js index 735b101..70a379d 100644 --- a/epic-games.js +++ b/epic-games.js @@ -14,7 +14,7 @@ const db = await jsonDb('epic-games.json'); db.data ||= {}; // https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 -const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox +// const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(dirs.browser, { @@ -153,7 +153,8 @@ try { const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); captcha.waitFor().then(async () => { // don't await, since element may not be shown - console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') + // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') + console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.') // await page.waitForTimeout(2000); // const p = path.resolve(dirs.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); // await captcha.screenshot({ path: p }); @@ -167,7 +168,8 @@ try { context.setDefaultTimeout(cfg.timeout); } catch (e) { console.log(e); - console.error(' Failed to claim! Try again if NopeCHA timed out. Click the extension to see if you ran out of credits (refill after 24h). To avoid captchas try to get a new IP or set a cookie from https://www.hcaptcha.com/accessibility'); + // console.error(' Failed to claim! Try again if NopeCHA timed out. Click the extension to see if you ran out of credits (refill after 24h). To avoid captchas try to get a new IP or set a cookie from https://www.hcaptcha.com/accessibility'); + console.error(' Failed to claim! To avoid captchas try to get a new IP address.'); const p = path.resolve(dirs.screenshots, 'epic-games', 'failed', `${game_id}_${filenamify(datetime())}.png`); await page.screenshot({ path: p, fullPage: true }); db.data[user][game_id].status = 'failed'; diff --git a/nopecha/background.js b/nopecha/background.js deleted file mode 100644 index df00fce..0000000 --- a/nopecha/background.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{function a(t){return JSON.parse(JSON.stringify(t))}class s{static cache={};static async set({tab_id:t,data:{name:a,value:e,tab_specific:c}}){return c&&(a=t+"_"+a),s.cache[a]=e,s.cache[a]}static async get({tab_id:t,data:{name:a,tab_specific:e}}){return e&&(a=t+"_"+a),s.cache[a]}static async remove({tab_id:t,data:{name:a,tab_specific:e}}){e&&(a=t+"_"+a);e=s.cache[a];return delete s.cache[a],e}static async append({tab_id:t,data:{name:a,value:e,tab_specific:c}}){return(a=c?t+"_"+a:a)in s.cache||(s.cache[a]=[]),s.cache[a].push(e),s.cache[a]}static async empty({tab_id:t,data:{name:a,tab_specific:e}}){e&&(a=t+"_"+a);e=s.cache[a];return s.cache[a]=[],e}static async inc({tab_id:t,data:{name:a,tab_specific:e}}){return(a=e?t+"_"+a:a)in s.cache||(s.cache[a]=0),s.cache[a]++,s.cache[a]}static async dec({tab_id:t,data:{name:a,tab_specific:e}}){return(a=e?t+"_"+a:a)in s.cache||(s.cache[a]=0),s.cache[a]--,s.cache[a]}static async zero({tab_id:t,data:{name:a,tab_specific:e}}){return e&&(a=t+"_"+a),s.cache[a]=0,s.cache[a]}}class n{static reloads={};static _reload({tab_id:a}){return new Promise(t=>chrome.tabs.reload(a,{bypassCache:!0},t))}static async reload({tab_id:t,data:{delay:a,overwrite:e}={delay:0,overwrite:!0}}){a=parseInt(a);let c=n.reloads[t]?.delay-(Date.now()-n.reloads[t]?.start);return c=isNaN(c)||c<0?0:c,!!(e||0==c||a<=c)&&(clearTimeout(n.reloads[t]?.timer),n.reloads[t]={delay:a,start:Date.now(),timer:setTimeout(()=>n._reload({tab_id:t}),a)},!0)}static close({tab_id:a}){return new Promise(t=>chrome.tabs.remove(a,t))}static async open({data:{url:t}}){chrome.tabs.create({url:t})}static info({tab_id:t}){return new Promise(a=>{try{chrome.tabs.get(t,t=>a(t))}catch(t){a(!1)}})}}class e{static DEFAULT={version:2,hcaptcha_auto_solve:!0,hcaptcha_solve_delay:3e3,hcaptcha_auto_open:!0,hcaptcha_open_delay:1e3,recaptcha_auto_solve:!0,recaptcha_solve_delay:1e3,recaptcha_auto_open:!0,recaptcha_open_delay:1e3,recaptcha_solve_method:"image",debug:!1};static data={};static _save(){return new Promise(t=>chrome.storage.sync.set({settings:e.data},t))}static load(){return new Promise(a=>{chrome.storage.sync.get(["settings"],async({settings:t})=>{t?(e.data=t,e.data.version!==e.DEFAULT.version&&await e.reset()):await e.reset(),a()})})}static async get(){return e.data}static async set({data:{id:t,value:a}}){e.data[t]=a,await e._save()}static async reset(){e.data=a(e.DEFAULT);var t=chrome.runtime.getManifest();t.key&&(e.data.key=t.key),await e._save()}}class r{static inject({tab_id:t,data:{func:a,args:e}}){const c={target:{tabId:t,allFrames:!0},world:"MAIN",injectImmediately:!0,func:a,args:e};return new Promise(t=>chrome.scripting.executeScript(c,t))}}class t{static async reset({tab_id:t}){return await r.inject({tab_id:t,data:{func:function(){try{window.grecaptcha?.reset()}catch{}},args:[]}}),!0}static fetch({tab_id:t}){return new Promise(async a=>{const e="recaptcha_response",c=(await r.inject({tab_id:t,data:{func:function(t){window.grecaptcha&&window.postMessage({method:"set_cache",data:{name:t,value:window.grecaptcha.getResponse()}})},args:[e]}}),setInterval(async()=>{var t=await s.get({data:{name:e}});if(t)return clearInterval(c),await s.remove({data:{name:e}}),a(t)},1e3))})}}class i{static STATUS_URL="https://api.nopecha.com/status?v="+chrome.runtime.getManifest().version;static STATUS_CHECK_INTERVAL=1e4;static status="Online";static checking_status=!1;static async run_status_check(){return setInterval(()=>{i.check_status()},i.STATUS_CHECK_INTERVAL),!0}static async check_status(){if(i.checking_status)return!1;i.checking_status=!0;let t="Offline";try{const a=await fetch(i.STATUS_URL);t=await a.text()}catch{}return await i.set_status({data:{status:t}}),i.checking_status=!1,t}static async set_status({data:{status:c}}){if(i.status!==c){let t,a=[0,0,0,0],e="";if("Online"===(i.status=c))t={16:"icon/16.png",32:"icon/32.png",48:"icon/48.png",128:"icon/128.png"};else if("Offline"===c)t={16:"icon/16.png",32:"icon/32.png",48:"icon/48.png",128:"icon/128.png"},e="Off",a="#a44";else if("Slow"===c)t={16:"icon/16.png",32:"icon/32.png",48:"icon/48.png",128:"icon/128.png"},e="Slow",a="#f8d66d";else{if("Update Required"!==c)return!1;t={16:"icon/16.png",32:"icon/32.png",48:"icon/48.png",128:"icon/128.png"},e="Update",a="#f8d66d"}return chrome.action.setIcon({path:t}),chrome.action.setBadgeText({text:e}),chrome.action.setBadgeBackgroundColor({color:a}),!0}}static async get_status(){return await i.check_status(),i.status}static async check_plan({data:{key:t}}){if(i.checking_plan)return!1;i.checking_plan=!0;let a={plan:"free",credit:0};try{"undefined"===t&&(t="");const e=await fetch(i.STATUS_URL+"&k="+t);a=JSON.parse(await e.text())}catch{}return i.checking_plan=!1,a}static async get_plan({data:{key:t}}){return await i.check_plan({data:{key:t}})}}const o={set_cache:s.set,get_cache:s.get,remove_cache:s.remove,append_cache:s.append,empty_cache:s.empty,inc_cache:s.inc,dec_cache:s.dec,zero_cache:s.zero,fetch:class{static async fetch({data:{url:t,options:a}}){try{const e=await fetch(t,a);return await e.text()}catch{return null}}}.fetch,reload_tab:n.reload,close_tab:n.close,open_tab:n.open,info_tab:n.info,get_settings:e.get,set_settings:e.set,reset_settings:e.reset,reset_recaptcha:t.reset,fetch_recaptcha:t.fetch,translate:class d{static base_url="https://translate.googleapis.com/translate_a/single";static async translate({data:{from:t,to:a,text:e}}){let c=await fetch(d.base_url+`?client=gtx&sl=${t}&tl=${a}&dt=t&q=`+encodeURI(e)).then(t=>t.json());return c=c&&c[0]&&c[0][0]&&c[0].map(t=>t[0]).join("")}}.translate,get_server_plan:i.get_plan};(async()=>{chrome.declarativeNetRequest.updateDynamicRules({addRules:[{id:1,priority:1,action:{type:"redirect",redirect:{transform:{queryTransform:{addOrReplaceParams:[{key:"hl",value:"en-US"}]}}}},condition:{regexFilter:"^https://[^\\.]*\\.(google|recaptcha)\\.(com|net)/recaptcha",resourceTypes:["sub_frame","script"]}}],removeRuleIds:[1]}),await e.load(),chrome.runtime.onMessage.addListener((t,a,e)=>{const c=!["get_settings","set_settings","set_cache"].includes(t.method);return c,o[t.method]({tab_id:a?.tab?.id,data:t.data}).then(t=>{c;try{e(t)}catch(t){}}),!0})})()})(); \ No newline at end of file diff --git a/nopecha/hcaptcha.js b/nopecha/hcaptcha.js deleted file mode 100644 index e35f1f2..0000000 --- a/nopecha/hcaptcha.js +++ /dev/null @@ -1 +0,0 @@ -(async()=>{class d{static time(){return Date.now||(Date.now=()=>(new Date).getTime()),Date.now()}static sleep(t=1e3){return new Promise(e=>setTimeout(e,t))}static async random_sleep(e,t){t=Math.floor(Math.random()*(t-e)+e);return d.sleep(t)}static pad(e){var t=2-String(e).length+1;return 0{try{chrome.runtime.sendMessage({method:e,data:a},t)}catch(e){t()}})}}class h{static async fetch(e,t){return p.exec("fetch",{url:e,options:t})}}class g{static INFERENCE_URL="https://api.nopecha.com";static MAX_WAIT_POST=60;static MAX_WAIT_GET=60;static ERRORS={UNKNOWN:9,INVALID_REQUEST:10,RATE_LIIMTED:11,BANNED_USER:12,NO_JOB:13,INCOMPLETE_JOB:14,INVALID_KEY:15,NO_CREDIT:16,UPDATE_REQUIRED:17};static async post({captcha_type:e,task:t,image_urls:a,grid:r,key:n}){for(var i=Date.now(),c=await p.exec("info_tab");!(Date.now()-i>1e3*g.MAX_WAIT_POST);){const u={type:e,task:t,image_urls:a,v:chrome.runtime.getManifest().version,key:n,url:c.url};r&&(u.grid=r);var o=await h.fetch(g.INFERENCE_URL,{method:"POST",body:JSON.stringify(u),headers:{"Content-Type":"application/json"}});try{var s=JSON.parse(o);if("error"in s){if(s.error===g.ERRORS.RATE_LIMITED){await d.sleep(2e3);continue}if(s.error===g.ERRORS.INVALID_KEY)break;if(s.error===g.ERRORS.NO_CREDIT)break;break}var l="id"in s?s.id:s.data;return await g.get({job_id:l,key:n})}catch(e){break}}return{job_id:null,clicks:null}}static async get({key:e,job_id:t}){for(var a=Date.now();!(Date.now()-a>1e3*g.MAX_WAIT_GET);){await d.sleep(500);var r=await h.fetch(g.INFERENCE_URL+`?id=${t}&key=`+e);try{var n=JSON.parse(r);if("error"in n){if(n.error!==g.ERRORS.INCOMPLETE_JOB)return{job_id:t,clicks:null};continue}return{job_id:t,clicks:n.data}}catch(e){break}}return{job_id:t,clicks:null}}}function u(e){const t=e?.style.background?.trim()?.match(/(?!^)".*?"/g);return t&&0!==t.length?t[0].replaceAll('"',""):null}async function y(){let e=document.querySelector("h2.prompt-text")?.innerText?.replace(/\s+/g," ")?.trim();if(!e)return null;var t={"0430":"a","0441":"c","0501":"d","0435":"e","04bb":"h","0456":"i","0458":"j","04cf":"l","03bf":"o","043e":"o","0440":"p","0455":"s","0445":"x","0443":"y","0065":"e","0069":"i","30fc":"一","571f":"士"};const a=[];for(const i of e){var r=function(e,t,a){for(;(""+e).length{let s=!1;const l=setInterval(async()=>{if(!s){s=!0;var e=await y();if(e){var t=document.querySelector(".challenge-example > .image > .image"),t=u(t);if(t&&""!==t){var a=document.querySelectorAll(".task-image");if(9!==a.length)s=!1;else{const n=[],i=[];for(const c of a){var r=c.querySelector("div.image");if(!r)return void(s=!1);r=u(r);if(!r||""===r)return void(s=!1);n.push(c),i.push(r)}a=JSON.stringify(i);if(f!==a)return f=a,clearInterval(l),s=!1,o({task:e,task_url:t,cells:n,urls:i});s=!1}}else s=!1}else s=!1}},n)});var n,i=d.time(),c=(await g.post({captcha_type:"hcaptcha",task:t,image_urls:r,key:e.key}))["clicks"];if(c){e=e.hcaptcha_solve_delay-(d.time()-i);0{let a=null,t=!1,r=!1;function n(e,t,r=!1){e&&(r||a!==e)&&(!0===t&&"false"===e.getAttribute("aria-pressed")||!1===t&&"true"===e.getAttribute("aria-pressed"))&&e.click()}document.addEventListener("mousedown",e=>{"false"===e?.target?.parentNode?.getAttribute("aria-pressed")?(t=!0,r=!0):"true"===e?.target?.parentNode?.getAttribute("aria-pressed")&&(t=!0,r=!1),a=e?.target?.parentNode}),document.addEventListener("mouseup",e=>{t=!1,a=null}),document.addEventListener("mousemove",e=>{t&&(a!==e?.target?.parentNode&&null!==a&&n(a,r,!0),n(e?.target?.parentNode,r))})})(); \ No newline at end of file diff --git a/nopecha/hcaptcha_language.js b/nopecha/hcaptcha_language.js deleted file mode 100644 index a757835..0000000 --- a/nopecha/hcaptcha_language.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{let e;function t(){var e=navigator.language.split("-")[0];for(const t of document.querySelectorAll('script[src*=".hcaptcha.com/1/api.js"]')){const r=new URL(t.src);"en"!==(r.searchParams.get("hl")||e)&&(r.searchParams.set("hl","en"),t.src=r.toString())}}e=new MutationObserver(t),setTimeout(()=>{t(),e.observe(document.head,{childList:!0})},0)})(); \ No newline at end of file diff --git a/nopecha/icon/128.png b/nopecha/icon/128.png deleted file mode 100644 index b002b52806d63c724f1b3c62b1042ceab0168278..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9225 zcmV+kB=*~hP)EX>4Tx04R}tkv&MmKpe$iQ>9WW3U&~2$WWauh>8duLvQCFs9KfGs~Ejq!fI|*F6G!y^HfK|8swiZZ&T)ARrRYFvGNo*NG=L zZG-bZag>#0mH3=^#H0%nKXP61_>FVXWr1f#%}jcZI7%!Q+gNF1Rx&l>3F4Tl>69;I zJytnyan{OJ*1RWwVI;3FFL9mbAd*&ouk{0bF=;p3E1OmjD0&24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&L@3kMb(ob9#%03ZNKL_t(|+U=croLxtC=f9`w z-uGVbo4O@ymn_TjzRP&QyRn_H7;u7Pa7=&%l8}&)kW2f==8D(3fenFm-8eOkf z6cs=Xf`C9wRwGlhHX`@381qbEYFCGu=H=Nl?%iDhz;ULo=YMxfXx%*lJ--^ngNu!7 zjra&b(V(An+X7ic-N+VCm@0_MfvYeI6>6gN--4(~#u7zI#?tg_v8^jcQunl~-sdyf z#~aT5r}hc}CWw01{ow*r?0JX8eb)sHUKz$wwebOJJt{FsjEYBmCV4G&h`1C87F5%Z z17grod>25{r4Rv3@~0TQ3Pv1)pgxfwN&pfd6hRf$5N9(u8?Gow>qAPzSDMcMQcnc{ zW@R#Sll} z92OUZk<*$7 zowWX~x3a47ysu~l07prEFaPmnS-1O}Chj@k>VOD}H|b_olQ3HpFCK{^bZ`ham`8iM z(4hiSiZI?|)F)bBkNjnI)XFG=vx3WnNKFG$Q%6u&i_}yhHbjlZrQnlAdgjTHbK0d=o>UwuaV|7Ym>-($59ipIz&qYM?)#F!|?59TTD?Sj5u zf`L5NClaCTFO}d26+w-`qmav?O)c1#CQNM&Y66@Qj2Mgu6^BueYJk(4D9SZF;xecG zLEV{O*j53+LEX1Mmr|+g7Au|K$wvK6h{I?RFeql9rRE$_PY-TK2Vs8?#>FG*TNCY_ zGHaiUCt8kW(lP(^0#3k*C8%wJscqQidNc?z8l-kcfCAn`2oOojhGMqm_sLHENyCD_ zIU&?SaH0SpD(?Px*6sd;F$194RBRhmLR1CgBm6)=#jV>3`g+OvIAuy=)q6?AwGNAW z9p;rUiwICLBV%g%XQ&XWqTPFOeci;hjhNZ9(5AY?E>WM(!5|niy7zR7g(dk&J^}o7 zMFOPMv+{TTIOjXR5RgYj5gd4%h+GkD98=o46SsXUnK(*=G>J$SO{K%3_LG!pqv=7G zY}D8)9@J0>LNc>XBb+u3j3hs;;EW<hr(#vDX{`e&fske%_WHdoHie z%IgQbRGFxcYudJ6(ULJU-F~}OP`n7f7*pKSPZ)bd%!tT8amZtcvEJY@6s-`p))QpI z#M4NP$$iT$?C95(%X?eb*cMePEtd8{f730WxxeM;%>|7x?W zqvh@EI(nm3-+9C7Wfi4m6D=Qna&6NKYj*zLK>yH(Hgxq)Q4p^czppIPR0IP$zol_c zU0wCxoqy)EuYUFFGrCV00KEO-^|Ll?+WOeq-Mbf5SU$m00hrNT|58g!^9{@HdEK5# zHv&KK#QLVyYt}rnro#N=V;2D1y86!B)7kmh?>xD&Y0?3(ar2IkZR+knufm$+OFMc7 z&wFXjj*m?$0Iqp(rP&fwa( z{&z8|0AR9duoO+a{ODNr5i^wUcyn!W*Kg)rbdi?=MN`j{rR!gM*K>2;todq=ZZ2D+TVZ$eLx*c(~poVJM zLxa6&t}v&W15r+l5jVoDRlMMhO0$(JIs=-y`eJdH=Nltpcm4EJ|Q^zd<+(5@U*|@LPl9X4A zW)L3`7{M4*_B*BPbfy5UR%Xk zGpBNTYa`PdbJ)OBh=twVc{c6t<`+Bmuz9dh_VsciC;{FIVh0d!kf@QI%jX9B_Ra!! zj|%{{Bm@pbY+7hn0u*nOtln}_LumQc^X75Wh4VRoS{*a8mYgU?WTXzjqZB-Jl$4h{ z2Y6!TX8!Y~O>7@3P72eTvmtN0XaTpKKZkj(wX|g|HF%7qC29yxg&|cs3yv3e^|EAD zJO81r zy`5^(o@G=w4-bTR8ZwUgGpf0D=6T$G^&;+jdJW%xW)0DCN<7V3!(FdCllLw@lSOq| z!XU<~MKlJZ#8`r~I0RL40*yJ#8FTBnZO%E|bM+$bfASR`T)Ba`Tyqvs6}$vkEnsoD zBZ$!)9spq@o($+v*%$J(>KuRhmc`t%a4J)6oR<8Vksdt;8C%+yHgiw{KnPJTY6>{x z*7LYw(MFTG^l_h zq{hKDO(EyM=>o1?IGuZc^bFevhsuNj#cBy9z?lGN4v&CWH3D}n`Sokq+J0{~9w_@l zUeH|6*M93p?mVlFY2r~rtS0rnN$pHhJ>+HINAPKeQS$vj!C+`G%5C!-_{KdqaPesq zcQkS3X>I)dyRYXB3u|dGo~&lD2x1~EeoXr*<241Z0W4Mpi{h;Vfxx}Nw*efRfod9Y#K(nqWMm)(pEjnelzZry)o zQ$1h2>soG@T0<6t*Z_;5YAB_>c+?wIC8-|sf~pWhS}JamMoo~mixILIE@{s2<-4wC z@oCe_&isp}Hu3p)Ucu{H41u@%M3Okt?Tz<|odVzhRmG?#<%?>t&S2BZW`mG5hHIx) z@x{Ad&%)-0GD!iCRfiEzuH&hTr|w?1qVxXizBcf)|5krQ0MMuZq(0cPVo7$_vR}`+ z&Wyp0`mymF0`-}Id*6N)=T6NrI@eJ}@uo!9K_muWDq*&7qx6ec=zs2c;+I~=wC_TO zia6s?Tp}aVGI1oS!aut);1hQ&W>#HI*_c10zJ|}e<0{Ur%b-4ow^7nUP7SIS(I8P@ z14FQR8--uI%)pB0iB_$KExXV{fk-^wB)R@d0IE2N3GrMpCCC4G>lHMHVHswth-y+$ zs}Z5!o~hsS;s>Jk=a=M{{#E^}+5iK~KOsF^R{le6@3Jeb4&eoiFCC<_-m%jMZn%hR zXVp-p9+Bkns)@K)4bZ^?Ln~I&_4v=|S-%PA_MJD1F*MAXN$WLN(71RpuB9HMic5Et zLCC0bd0W6A-F7J-{@&7YyImr}M{c=@OQvKIB8<V;cOmRJHFO^ zZ(KB+x12qF#1&QJfB-9CnW2wS(=Or^(@?tY9c6OBo0g7Thl!Aba_L3Rg_7{EMf9rqN|Jp;#4p0F@OaEMg z-c5g56>S#llP(=bJtIBy%HZz3`>HdUnKPJSQ_d?f4ppVJdM&Fz`{hG2e}C%Rwv|^t z|5XNGd;xr_ht&ffqn4>b%w1P4!i;0?0=)Bz`7~Qe7_I?|1+O@bDXdt@>Mwrdkj&qo zdRK4cm!JIyO6%5BL{asqCaTP+Fgv5%^ZIkf$@c@BU}l_xQL3Y@LD;kIFAI-gL&3} z^8sA@R>UcB>M4N5AY44Vk;|qxAM>0qnbFFnv!`HF6@vjIfKhbQR@Q&>0ZRF!bK#18 zeXRfLzYy%+gNgyMBYYDPu0CTb^BT%k3OLL+m-4a%QT|s3dOPMG0D#O;=lipz_Oi9q z*`IDXYZf!JiF-8M&dH@E@;m-x3Hgp=%Wy9B4Y2w94S4{9~T; zTh5=u)TFp=pJfk7VTkSj`Vhtb!DAXX-?@j5|MP#*QjAA1Dya=*m>UW=oilsFT-h*c z#}>NZe*ge%VeopHpq48NTz2|&avI~sfGWm=S5LHkJG-Cy`MAdKUAda#hK(4ncr~eL z6|BOAGg`36paw*Qb7xJ#2v{BNOR0!citDzpch%}~&3V_)m!sRaCf-ci*UpL|BXIfY z(l%mJ=c!`KX~SG|NEXIxyp()G+L z;s8JzqyWy>4c?tEE4t28(R8@)%yU@zo*UkQmd#>;_qcXvudr%GPOEsu$*W-iy&C< z8Q4&s4yuD|H(=u!0~iT0#t;b9WrgPI+)&p%Z5Wq|wt+HbAzj9%Hj(zf(pkdQZuXT{r=-Lax59EYHZ+)j;;ZLi)%X z1`0=?FVI&iF*v$CunOW8A6Tjz$`iK_>uSN81X2p91q_i_3KO&;6~JZ*tLygxATxb# zm)NQvuuOy>xMQfuu0jkN69Z_BQ%`J#x_R@<@{HQ~^U=WIM3TgTwC8(&Vqy~{E}QYFI0Vo3 z?m@bb!OQ55VmiAAvEJdtgMw8-14HvAXOCYJ8$I6x^|x716LQ8EF~ZeEIJapmRt=p@4Ov*jFx() zrkd$*zYWc12~x(ZB;vm_iuuV)6L+~tWLvQ{Q@?)z0CLrJYopBcr>uAAh`C7Gqj&sx z{AEWs&u!_VBm%@FC)Sq8STb{`Gw-+Fg$<5wL5(rYyYqI!bIwdVuns4QM5qX_bPVv= z#$Cre=cOBV@_gq&5&%MyNFW{&$eg>7`S1KS%u#l5vSGmK@4gF}cY1o)QG}>XlU|hP zwsf<4Z-1HOZo2DDdY^(3O1YU&Wvat92LPb{_J46SE%hrBY%FR8AxdxB8Xv`~ zeEaG3?C~Bi2Akf;7o<~dxa@q+{Lp);p3-t;yQr?3Gv9kB^*6tsVvrqmL4*>KqZiKu zPp_rmj@_D$yz=0)>*+!<#DGVQ;EbXsr2fXMSondv$Aru=HEirXxK5%e3F&t5Q^usgrTc3Ztq5s(hmOOD)j(*S>@N-Y%kiksuRN z-8O}A#tckD6BvsRhW}@h7UorPc-Hhee&@c&c(Jp$ENgjDTQgs}=UOhQ2}g4NhwrfR zDflFg(5_xe+jo%f+Jh@bgqbY4);2P;XW$xZK_roQYLXNG4(kcPd}k*z7!%uo?xxGvSI<2A`nnswJ_cb2DBbFtzp$-i z$@{PG-%@mNtxSr82{jjhLT;9|;|BlRl%pYR|jHb3*|JSyLS?9cYVEWA;>~FYYn-Wq^dUES7{^H-3@p6v? zi5avdtV=Fk46pf2`hIcI)|} z)m!<*51(W0P?Gsuk|e9idcmqCAixeUrxnBu&pEw;)CyJv(I83P#-Qq0IS}#RzW;Nc z+S*xGzuQTM{`yO{4Nbk~gLd{=S7+Y*&4cQeLz><8=6{NTuM{7@`?@ADAs3n(XHN~kP|LU7Wyrj3BIz*L710LTs$lpA)j2Cv5^Oo6U{Ma-R z&RBFu%{Bl0s#&Io^}7bO%2i8dLQ2C{?C#>d|NK*a`|5M~jm2}BQDdm~Nk?HFcIozu zV-Fvecq|dZj>z%7RonRbPgl{ApZFTV$G7Za{XhPMci(Ux@3>$(Gebd4hVU?vyVxY> z2@#gHMdguaSM%kiuh3g6casf|7mHDd)E?G-J1hV~?L(w6a$U>t%_!x%zNNPm@wbn? zz(dci;~iI?$&HI7Wiwj2cwQUx+v;hm$q^Vs zte)PX5?eZZS-!E8=eKn*ILR$)R(0%U)gybU{c*tJxzo8|Ruc=_8fmP~U_^+$rz;<^ zad$V*Z0X?ntsNBIM4a*s-$P{6i?#xsF+?^TKLFzFwDm#X)FB)1s6q{D6g35`#u&=~ zG6m;&vV9j%w(mMn^q-(K5XU^WzMaR`w;%U88x+(d>Oqql1A`;BRZ*s{y7Ne&^QzQ* zZE6p+QYP355dA1W8)+aiEj&vYF#GXEiQH94ZdP6S$a2EJJ4A=Q0=n{g!b{ z0Dzv%tUnA4>s)EzW7#;aQ&XfQmdNM26{b+3Ng)NqH$CC(jO$}bD!EG8n*KuA_LTwC z_Q|pXb?;;EHDPH;FnJ<@w{`rFZH@66;^P8< zn95y%lTFr4VyoC(P+j4`7n;3)ICt5P;z5HGhDXa+E7gx$N^QAez?v){Ue`` z=^sq<2GaaKm)8C_)MTlx4iVK?MP$jr-k$tH$H-+Y%?(va¬f<0Z)?QoyS+&{tqZ zdpA#QYG+$te%$>APAUML-cZlm7N5=HX<6DVES%AbF~bd(54~fy?dqgIKMDY8c8Ecl zJEMkNCOqf}I_meH-a&SE4JApPX*(G&g7uEJmMqPU^@lFa)g%LSs1(y~2SzHKRU{ch zJ#|$fGp02jAk&Nv&pF9!@W91wmN#8Elb5#^dEnXSY4002^#O44tTx{N`tw+13ZO9= zO$sfHDqj4dA&~AgF9%m3l783^Mrlu{QC~~y6NejB!EimSU^JPBa)^11l(;9^$D<`n z(il3u@cCfxB8(){Bv6|>)T8UOhB%dFVZd3+`D@qF01ZO#1NwHI)% z4C0A!M5y>Q+eZ;i8YYc=(3B~H@iNjwcL=GGSl~Gw$iK;TI1wv+8DN!$8b79txZv7uIc4nSDiskU{5^&+fXs_ydUG89BClHr2oewc$`>f*Bh$C@YDgo zX}K&lVVDSNnSelul&zojag6Ycg@4kEFllaC6HZi{xJhyY10z&22ad!LOye4&$JE{Q zgp~lh3q|sk?4ehe2LfIcXM}tdv$s$@bpX&?irCR#B$fc1wp>xs1IoU}vvZ6;+0GMW z)SD+ENzs6e7q;h1^p#4d4gd`B!j@hJ!+>I#r)Vu<8CwyFo`kgnI`%Ir;B1M3(DKyg z?&Id4AGbKbk~N#yJOH^^2wj@WsO5{yoa{D+s1U@$ra|G+Uv4;V&pmDc=q?rc!Sao4 zmzh|U*ci&2Q-0DzfL+qccbBgtA0NM($Kx7}S-NE_Rk?t7E}qMDiYRf~omNq7Qr|X4 zOd3Wy=m`fo>KV42efk%Fcp#*~M`i!kwaFMFiXPUHPj6J&Pk3-_U z;ISA=L}J*h0rx-iB2TvOJg%@i?%}v4Yc|o*H^4hCJ(shaYRUN_0JTj3pIS+u4+JoF((U>++PA}@EX9rnHOvZB4jvzyv^ zZfhq_{baCW@-l*A?^&^R7b~{zD+3of_JC12%CQeOh8}^naYSS2iBnN`LY>IyI2WcI zi>hV#nUg6VQOOE^jZ*~x6#!J|WTr_i4z1ASlOLz|a70Gn2Y-ukVl-?pW@|8wpA{qqOoQCGRAsnq)-Vigcf772nMzTi@!E@PQd zEh}5I)gNAb+2SWY^|}^4CIGzQt3UbFi@SS2+UMNZpR4Tp6BY^yZxXdo7leBC+$r}h zoIUk@pSfYtLC;?gnSXrKSAP8N=R5lTcECAvL=Uad2}1ieW=U0c_6=QVip9M_>NS_dNJ)%Mk-$!`h8EZ5kM= zISNyuLX*1bU;Q@Ijs3-%?K}6}bi@F#$lJ#5>dFp3nWckMk6Qb-BL=|wz9BjBJM>oQ zXiS1l0|Roz0MH{j_NvfHgaE-GqSol305BDaf69TNzbHozfC{~Kr~-i3UU=Z}0H~<` zQ_mAPRIKix0I05X06pa>bBN154*J!W+U$PrK!r|j@vqEotUYo7)Q7rCq+%YNGKQWQ zS~7ms5d)y1s_MRJRk=JB5S$7I0BzZ@zp0x0j>_J9`+ZN}xTB-%cf%mKz2H5SaPY~- zb_lFts95@8U3K+mm;S-c&mNP(|Hlu%Z2O{U?JPs#K^# f6{=8$4nqGQ-IY)^404Gh00000NkvXXu0mjfq9Nm) diff --git a/nopecha/icon/16.png b/nopecha/icon/16.png deleted file mode 100644 index 1dd1d1e68238bad31e98328ebbf5d0701ffe5774..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1103 zcmV-V1hD&wP)EX>4Tx04R}tkv&MmKpe$iQ>9WW3U&~2$WWauh>8duLvQCFs9KfGs~Ejq!fI|*F6G!y^HfK|8swiZZ&T)ARrRYFvGNo*NG=L zZG-bZag>#0mH3=^#H0%nKXP61_>FVXWr1f#%}jcZI7%!Q+gNF1Rx&l>3F4Tl>69;I zJytnyan{OJ*1RWwVI;3FFL9mbAd*&ouk{0bF=;p3E1OmjD0&24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&L@3kV1Hw9$(I00K)%L_t(I%axN$NK|nY#(#J2 zy>p%M>gd>D%1kyoW)GstKrGD`ZhAlzlq3d05iUdu3IdU|$aYbbE;6Wu77<|&0)vp* zWVk4#2N>Fn`uOzV2Z%j1&_92vtJZO-v+g&(6Nx*x|c+Y;Xm@+1%|M zk0`!mDszPl2uh5x;*rkAF#u(;F=NU082LYqg9ljq8wiA-Z10hv2%y1{ON-lqF3U>> zU6yHauc5(VU0k~;__V4AK#AVSKT6zR4{*KOMO~?t@bnCI6*aiKJpjBsu@lRf7n9pX z#i*ZC1776#kNRlm>KBTENRtq86`*E|lTWP|Nz4avwVz?H!-^1)H~$@9*BxX%lU^*N_5%XHPQ$7V5ecVL7E({lhEF0^pGY8|sb!!-8~6No49S6rJi?;o@;WoBx< zFGxe-OGl)X7y(X}*f>3Z7yz28G8a^^X6ek975p1;Z_q~|5FrpvU^c465=oFil?{XEX>4Tx04R}tkv&MmKpe$iQ>9WW3U&~2$WWauh>8duLvQCFs9KfGs~Ejq!fI|*F6G!y^HfK|8swiZZ&T)ARrRYFvGNo*NG=L zZG-bZag>#0mH3=^#H0%nKXP61_>FVXWr1f#%}jcZI7%!Q+gNF1Rx&l>3F4Tl>69;I zJytnyan{OJ*1RWwVI;3FFL9mbAd*&ouk{0bF=;p3E1OmjD0&24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&L@3kNvIO5k|_00o3eL_t(o!_AgyY!qb}$A9na z>@nMx#V*~hQlZcmY!Kww0v3!hSR|2H}YPKxl|YqsC|; z8iRsy;tXVZu&d*iGuEG~qCANh^gKEGd({Ec!GBog`J4>$O zfvSo4y>6PL3Er+hMmVMq>0q``V`cRu#us`ylkDT;@NvFq`75{RWa>?~G{QhtmO!#@ zPOUK8ONG;)W4wOeBv#c-!zBb526HEmWzmc>p8cer51Kl1Y#%Bs=EWs*@p;^!8{l!V zaC#|KI}fpT@6p^;WD*nNb@OWr9^SJ}B(}^RuO$vOir{E1?yevhUJbx4MFo7edLgpC zlj9pUqeo8Rn?0QgD;}iXg34FF!agsLjZfcB&>=aoaVxQ38qi9|GGWaMl;Qw)zx55f zyW;?~hBo0djv(86^uD}`O2uWymnt%?4Gia!8?MFUP-w3E2tCp-X0d$->Dcu>B|b09 z$^%*3WfjGY_2tp=?QUYfH1tb%( z2-CLN+Y-&L)we{kq{Lr6C&#wvj)frLkGANL?lYEMuR5pR|Fzk$wcZKU=Wo8W|0MU` zIGM7=i|_=C(K|ZvRaB5)S;4N~PV&>4#6JYiB>4VtGc`9%Vbb$Y6Fb_3Rvf?|n$EGV zUS9rnkOE^kuBms;Sp2mBNbY=GbH#V;PbtC5b43U+%je~lyF!E}1W|<`nX&k;;RFwF zKSH9m^}8j^w4FP|0*^Cw~KitJ{r4{9O+FB+HVWJt9z8xm!3QJ z-8V>zuiv~!ZHly*BPfQHmyUBDdbBz-NQwbmY91pEGb1iMj?ZD{C@)Ki(E$8;y1hTFV}k&+M|+6&CMdfmfZOFF-Dfhbq>%MH53}dI zuE`Ok;RxJ1u?UYUbEfvdTag%I+{zpqiKfDWvf%LdgyA;2^}Zo*K;klHa5R~|^c1}J zdn@DpKKw-iOb7I31iDm7S_<*A5|EM(K=i*$NZJazbdazFrUl79Nw}q#jg76BPQb8j zp84T0&-`$BM7tuJf~wcPx~b>%$>k&6hI)6~^6J;VoS&1@>tEi!qA8x*$Q49+zP9qv zbG4fkfJh=ehbxRo(wLJ?fka+u|5#^zssI6(8tE~uD@wqpDv=2P1N;ruh9zVSLVJS% O0000EX>4Tx04R}tkv&MmKpe$iQ>9WW3U&~2$WWauh>8duLvQCFs9KfGs~Ejq!fI|*F6G!y^HfK|8swiZZ&T)ARrRYFvGNo*NG=L zZG-bZag>#0mH3=^#H0%nKXP61_>FVXWr1f#%}jcZI7%!Q+gNF1Rx&l>3F4Tl>69;I zJytnyan{OJ*1RWwVI;3FFL9mbAd*&ouk{0bF=;p3E1OmjD0&24YJ`L;(K){{a7>y{D4^000SaNLh0L z04^f{04^f|c%?sf00007bV*G`2j&L@3kNLSkTe7U00{_5L_t(&-rbmca8%VD$3N%X zyYFPlZUP|zLwJM`P~;&IEf-zZ5GfkImrBrQnWVB2T`LJDx02$u^C1CS`;Y*oU1)sntHdFO51`9B2^`Q&>u zT~_ZO4Ha1q8Xb+JI=YaZ9T4wDs@#cHLZH2V2o}NkAj0QCAh7XnOFII0c;~)Q_g@Fl zxqHnr5ACl@ZF#k2VIMw<>lP` zOwVZSPOk2-Gr?ynK=oXcfq#5jU)-KdPaZ3BA(Tu{zU28`r6U5+x$Wkv`{kBO!Pv4VUGL5e zy|H@EumB>j)jU+t`|)RUj@c8Z{Lo8~6wi8vM zXBN*4+`6N#4?yo~d&YQnGy_lbW>k_<%#EvHj?cGn}WDCy10Ko^)#`%rLuT&oYv$rLBvv>~-J?$vYi#P9 zYYuFt@`1IuCY7*eSrzMcHPUO@1Mbf=4Q{#oLQEk^oH)s$ho2xF3ir)>r7tE(>;y5&#=FQs%1P_o7^>FOj z7eOl~`ds{=YSJJd->;g)M6VMR9DDY~0l@>r4j-ds^Hvm^s*(WngZV=)X@N#s&5Ob> zt(Y!}bj}vo!`AKM(s4MYfz;6b;f|cwgby4-N8(5!sVpA+@Y%(FG+;-22_HC=(?`#T zyOCPsl(4Y0;0#JOjfl0(mI|3V6Kf=G&8d%cI&&{DArOWH4OR|rOevL-Y*HdbZe1l@ zPEIM_l&#K4t+tQ}=@@sx0bBTny?T377cpC*g&=rkbpS3O`U`0_a5)c zh;OftVC~|ZJ_@g>MhJ*0&4*20!|xw5U+KK^1JZZn#%9}5vQ>+rxxXP2XYb)QKu~<; z6$EM)4P2nJrct`;1`wLY&L|rj1~0%3^{q5^MG*ofueyo6*)s+vpk^`SuU-zop2IEF zN8&^4R3Wh(6SoFdZfZ3FlKzT2M0fmcljM-%?cpu^c$;=TW|E^&qW%Ls=2OS$Qc_3p_|6 zvxg7RTG0_t^Z4t#S+~Dw*jIn&#bw;HVm=c*4umlJ#%--Q+7sh}O&{~OqaB0l{qVvK z6zhGYMZxZ$yY%1u3k&I75x&)aO;q3hekJj!>i%6V7_I84?qnL+jRV*Fk=(PWB! zP3`>Qg9hp&i4jSO3ZI)ht1GyuY&>}`2ccM!9bdHY*hdX?r>&d|_VB_DOwjvLMqsn! zJA1yJQwya;{!0RVtrCVDN@|DF77xC65Z*g@&H}DJn&7T&pPb#5=Y~alVeFv$EQp!<@QUPM|7Z>r5STJOW7kok-1Fw7;NIobL&cc*~;_11U=3^ zO0#XvDb{qF5|z!0(i+RwXux5JZ1XB}mt`xI&I~yWiAJM(dKrdi4<;^$+XhqENf^Y@gz+xU1*`1Qd~%$$K5yAc(R>DDovit z$&`}ueOwcYCE0$cg>~|SZb&hJE>@%n>DI7fjey5q; zb4RyPuZI|sX!4!r=mFS%vWp%ivc1fq`?RCe%F={w$@b>X(F4$)O7rs0tIJGCjI1^nCqscD97Lp{kUi&xsJ3 zC_<4WDNA7*g2E%+eetH!Hr>$_8VDQ><1~$If;C;eDblvWVG4p@boI5@yP|PkIoM2H zI5zq;3Q*S@<##*k$J7RwvxFQ{NXD4?Tn;%bcPxGKwXzeD)C+OjTAEUtgwkJu97o@r zQs9*arqplGE)LxO#*eRR7*IkdB8lf(lj)_5QA;UJN?8DxH?;Mv2WkhDP|Q{}oF^7n zr(^8{kW~77iJx%4zKuMq4PnekYcUvr-!ww!3xGn03=P@`Ci*-p3f<0YZLP<2ElGh$ eEBzY(FZLgCZ@LL@c(k|x0000"], "js": ["recaptcha.js", "recaptcha_voice.js"], "all_frames": true, "run_at": "document_end"}, {"matches": [""], "js": ["hcaptcha_language.js"], "all_frames": true, "run_at": "document_end"}, {"matches": ["*://*.google.com/recaptcha/*", "*://*.recaptcha.net/recaptcha/*", "*://recaptcha.net/recaptcha/*"], "js": ["recaptcha_fast.js"], "all_frames": true, "run_at": "document_start"}, {"matches": ["*://nopecha.com/setup"], "js": ["setup.js"], "all_frames": true, "run_at": "document_end"}], "host_permissions": [""], "icons": {"16": "icon/16.png", "32": "icon/32.png", "48": "icon/48.png", "128": "icon/128.png"}} \ No newline at end of file diff --git a/nopecha/popup.css b/nopecha/popup.css deleted file mode 100644 index e3f2991..0000000 --- a/nopecha/popup.css +++ /dev/null @@ -1,289 +0,0 @@ -:root { - --input_scale_x: 1; - --input_scale_y: 0.8; -} - -html * { - font-family: monospace, monospaSFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - font-size: 12px; -} - -html, body { - margin: 0; - padding: 0; - - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - -.hidden { - display: none !important; - opacity: 0; -} - -.light { - font-size: 0.9em; - opacity: 0.5; -} -.green { - /* color: #8cd47e; */ - color: #1a73e8; -} -.red { - color: #ff6961; -} -.yellow { - color: #ca1; -} -.clickable { - cursor: pointer; - transition: 250ms all; -} -.clickable:hover { - opacity: 0.8; -} - -#main { - padding: 8px 16px; -} - -#footer { - margin-top: 8px; - width: 100%; - text-align: center; - color: #999; - height: 20px; -} - -#manage { - width: 100%; - text-align: center; - background-color: #1a73e8; - color: #fff; - height: 20px; - line-height: 20px; - border-radius: 4px; - border: 1px solid #1a73e8; - padding: 8px; - font-size: 1.2em; - transition: 200ms all; -} -#manage:hover { - color: #1a73e8; - background-color: transparent; -} - -.vspace { - min-height: 8px; -} - -.settings_group { - width: 280px; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - padding: 6px 4px; -} -.settings_group.vertical { - flex-direction: column; -} -.settings_group > .label { - flex-grow: 1; - font-size: 1.2em; - line-height: calc(34px * var(--input_scale_y)); - padding-right: 16px; -} -.settings_group > .value { - font-size: 1.2em; - line-height: calc(34px * var(--input_scale_y)); -} -.settings_group > input { - border-radius: 0; -} -.settings_group > input[type="text"], -.settings_group > input[type="button"], -.settings_group > select { - font-size: 0.9em; - outline: none; - border: 1px solid #999; - width: calc(60px * var(--input_scale_x)); - height: calc(34px * var(--input_scale_y)); -} -.settings_group > input[type="button"] { - background-color: #f0f0f0; - cursor: pointer; - transition: 200ms all; -} -.settings_group > input[type="button"]:hover { - background-color: #fff; -} - -.switch { - position: relative; - display: inline-block; - width: calc(60px * var(--input_scale_x)); - height: calc(34px * var(--input_scale_y)); -} -.switch input { - opacity: 0; - width: 0; - height: 0; -} -.slider { - position: absolute; - cursor: pointer; - top: 0; - left: 0; - right: 0; - bottom: 0; - background-color: #ccc; - -webkit-transition: .4s; - transition: .4s; -} -.slider:before { - position: absolute; - content: ""; - height: calc(26px * var(--input_scale_y)); - width: calc(26px * var(--input_scale_x)); - left: calc(4px * var(--input_scale_x)); - bottom: calc(4px * var(--input_scale_y)); - background-color: white; - -webkit-transition: .4s; - transition: .4s; -} -input:checked + .slider { - background-color: #2196F3; -} -input:focus + .slider { - box-shadow: 0 0 1px #2196F3; -} -input:checked + .slider:before { - -webkit-transform: translateX(calc(26px * var(--input_scale_x))); - -ms-transform: translateX(calc(26px * var(--input_scale_x))); - transform: translateX(calc(26px * var(--input_scale_x))); -} - -#key { - padding: 4px; - border-radius: 2px; -} - -.loading { - display: inline-block; - position: relative; - width: 40px; - height: 100%; -} -.loading div { - position: absolute; - top: 8px; - width: 6px; - height: 6px; - border-radius: 50%; - background: #777; - animation-timing-function: cubic-bezier(0, 1, 1, 0); -} -.loading div:nth-child(1) { - left: 4px; - animation: loading1 0.6s infinite; -} -.loading div:nth-child(2) { - left: 4px; - animation: loading2 0.6s infinite; -} -.loading div:nth-child(3) { - left: 16px; - animation: loading2 0.6s infinite; -} -.loading div:nth-child(4) { - left: 28px; - animation: loading3 0.6s infinite; -} -@keyframes loading1 { - 0% { - transform: scale(0); - } - 100% { - transform: scale(1); - } -} -@keyframes loading3 { - 0% { - transform: scale(1); - } - 100% { - transform: scale(0); - } -} -@keyframes loading2 { - 0% { - transform: translate(0, 0); - } - 100% { - transform: translate(12px, 0); - } -} - -.tab_btn_row { - background-color: #efefef; - display: flex; - flex-direction: row; - flex-wrap: nowrap; -} -.tab_btn_row .tab_btn { - flex-grow: 1; - padding: 4px 8px; - border-top: 1px solid transparent; - border-left: 1px solid transparent; - border-right: 1px solid transparent; - border-bottom: 1px solid #ccc; - border-radius: 4px 4px 0 0; - text-align: center; - font-size: 1.2em; - transition: 200ms all; -} -.tab_btn:not(.active):hover { - background-color: #fafafa; -} -.tab_btn.active { - background-color: #fff; - border-top: 1px solid #ccc; - border-left: 1px solid #ccc; - border-right: 1px solid #ccc; - border-bottom: 1px solid transparent; -} - -.content { - margin: 6px 0; - padding: 4px 8px 0 8px; - border-top: 1px solid #ccc; - border-left: 1px solid #ccc; - border-right: 1px solid #ccc; - border-bottom: 1px solid #ccc; -} -.tab_content.bordered { - padding: 16px 8px 8px 8px; - border-top: 1px solid transparent; - border-left: 1px solid #ccc; - border-right: 1px solid #ccc; - border-bottom: 1px solid #ccc; - border-radius: 0 0 4px 4px; -} - -.footer_group { - width: 296px; -} - -.warning_box { - border-color: #FCD62E; - border-radius: 0.25rem; - border-width: 0.125rem; - padding: 0 0.5rem; - margin: 0 4px; - background-color: #FEF9C3; - border-style: solid; -} diff --git a/nopecha/popup.html b/nopecha/popup.html deleted file mode 100644 index 5957b1c..0000000 --- a/nopecha/popup.html +++ /dev/null @@ -1,149 +0,0 @@ - - - - - - - - -
- - -
-
-
Manage Subscription
-
- -
- - -
- - - -
-
Subscription
-
-
-
-
- -
-
Credits
-
-
-
-
- -
-
Refills
-
-
-
-
-
- -
- -
-
hCaptcha
-
reCAPTCHA
-
- -
- -
-
-
Auto Solve
- -
- -
-
Solve Delay (ms)
- -
- -
-
Auto Open
- -
- -
-
Open Delay (ms)
- -
-
- - - -
- - - - -
- - - \ No newline at end of file diff --git a/nopecha/popup.js b/nopecha/popup.js deleted file mode 100644 index 5688306..0000000 --- a/nopecha/popup.js +++ /dev/null @@ -1 +0,0 @@ -Date.now||(Date.now=function(){return(new Date).getTime()});class BG{static exec(t,n){return new Promise(e=>{try{chrome.runtime.sendMessage({method:t,data:n},e)}catch{e()}})}}class Util{static sleep(t){return new Promise(e=>setTimeout(e,t))}static pad_left(e,t,n){for(;(""+e).lengtht(s.id,s.checked));for(const c of document.querySelectorAll('.settings_group input[type="text"]'))c.addEventListener("input",()=>n(c.id,c.value));for(const l of document.querySelectorAll(".settings_group select"))l.addEventListener("change",()=>a(l.id,l.value));document.querySelector("#manage").addEventListener("click",async()=>{await BG.exec("open_tab",{url:"https://nopecha.com/manage"})}),document.querySelector("#footer").addEventListener("click",async()=>{await BG.exec("open_tab",{url:"https://nopecha.com/discord"})});let i=null;document.querySelector("#key").addEventListener("input",()=>{clearTimeout(i),i=setTimeout(check_plan,500)});for(const d of document.querySelectorAll(".tab_btn")){d.dataset.target;d.addEventListener("click",()=>{for(const e of document.querySelectorAll(".tab"))e.classList.add("hidden");for(const t of document.querySelectorAll(".tab_btn"))t.classList.remove("active");d.classList.add("active"),document.querySelector(d.dataset.target).classList.remove("hidden")})}}async function render_plan(){var t=await BG.exec("get_settings");if(t&&plan&&!rendering_server_plan){rendering_server_plan=!0;const a=document.querySelector("#plan"),i=document.querySelector("#credit"),r=document.querySelector("#refills"),s=document.querySelector("#incorrect_key"),c=document.querySelector("#ipbanned_warning");var n=Date.now()/1e3;let e=null;plan.lastreset&&plan.duration&&(e=Math.floor(Math.max(0,plan.duration-(n-plan.lastreset)))),a.innerHTML=plan.plan,"free"===plan.plan?(""!==t.key?s.classList.remove("hidden"):s.classList.add("hidden"),a.classList.remove("green"),a.classList.add("red")):(s.classList.add("hidden"),a.classList.remove("red"),a.classList.add("green")),plan.plan.includes("Banned")?c.classList.remove("hidden"):c.classList.add("hidden"),0===e?(i.classList.remove("green"),i.classList.remove("red"),i.innerHTML='
'):(i.innerHTML=plan.credit+" / "+plan.quota,0===plan.credit?(i.classList.remove("green"),i.classList.add("red")):(i.classList.remove("red"),i.classList.add("green"))),e?(n=Util.time_to_hms(e),r.innerHTML=""+n):r.innerHTML='
',0!==plan.duration&&0===e&&await check_plan(),rendering_server_plan=!1}}async function main(){await initialize_ui(),await check_plan(),await render_plan(),setInterval(render_plan,250)}document.addEventListener("DOMContentLoaded",main); \ No newline at end of file diff --git a/nopecha/recaptcha.js b/nopecha/recaptcha.js deleted file mode 100644 index 26c7ce1..0000000 --- a/nopecha/recaptcha.js +++ /dev/null @@ -1 +0,0 @@ -(async()=>{class _{static time(){return Date.now||(Date.now=()=>(new Date).getTime()),Date.now()}static sleep(t=1e3){return new Promise(e=>setTimeout(e,t))}static async random_sleep(e,t){t=Math.floor(Math.random()*(t-e)+e);return _.sleep(t)}static pad(e){var t=2-String(e).length+1;return 0{try{chrome.runtime.sendMessage({method:e,data:a},t)}catch(e){t()}})}}class d{static async fetch(e,t){return g.exec("fetch",{url:e,options:t})}}class m{static INFERENCE_URL="https://api.nopecha.com";static MAX_WAIT_POST=60;static MAX_WAIT_GET=60;static ERRORS={UNKNOWN:9,INVALID_REQUEST:10,RATE_LIIMTED:11,BANNED_USER:12,NO_JOB:13,INCOMPLETE_JOB:14,INVALID_KEY:15,NO_CREDIT:16,UPDATE_REQUIRED:17};static async post({captcha_type:e,task:t,image_urls:a,grid:r,key:c}){for(var i=Date.now(),n=await g.exec("info_tab");!(Date.now()-i>1e3*m.MAX_WAIT_POST);){const u={type:e,task:t,image_urls:a,v:chrome.runtime.getManifest().version,key:c,url:n.url};r&&(u.grid=r);var l=await d.fetch(m.INFERENCE_URL,{method:"POST",body:JSON.stringify(u),headers:{"Content-Type":"application/json"}});try{var s=JSON.parse(l);if("error"in s){if(s.error===m.ERRORS.RATE_LIMITED){await _.sleep(2e3);continue}if(s.error===m.ERRORS.INVALID_KEY)break;if(s.error===m.ERRORS.NO_CREDIT)break;break}var o="id"in s?s.id:s.data;return await m.get({job_id:o,key:c})}catch(e){break}}return{job_id:null,clicks:null}}static async get({key:e,job_id:t}){for(var a=Date.now();!(Date.now()-a>1e3*m.MAX_WAIT_GET);){await _.sleep(500);var r=await d.fetch(m.INFERENCE_URL+`?id=${t}&key=`+e);try{var c=JSON.parse(r);if("error"in c){if(c.error!==m.ERRORS.INCOMPLETE_JOB)return{job_id:t,clicks:null};continue}return{job_id:t,clicks:c.data}}catch(e){break}}return{job_id:t,clicks:null}}}function a(){var e="true"===document.querySelector(".recaptcha-checkbox")?.getAttribute("aria-checked"),t=document.querySelector("#recaptcha-verify-button")?.disabled;return e||t}function y(c=15e3){return new Promise(async e=>{for(var t=_.time();;){var a=document.querySelectorAll(".rc-imageselect-tile"),r=document.querySelectorAll(".rc-imageselect-dynamic-selected");if(0c)return e(!1);await _.sleep(100)}})}async function w(e){let t=null;if(!(t=1{let f=!1;const h=setInterval(async()=>{if(!f){f=!0;var r=document.querySelector(".rc-imageselect-instructions")?.innerText?.split("\n"),c=await w(r);if(c){var r=3===r.length,i=document.querySelectorAll("table tr td");if(9!==i.length&&16!==i.length)f=!1;else{const s=[],o=Array(i.length).fill(null);let e=null,t=!1,a=0;for(const u of i){var n=u?.querySelector("img");if(!n)return void(f=!1);var l=n?.src?.trim();if(!l||""===l)return void(f=!1);300<=n.naturalWidth?e=l:100==n.naturalWidth&&(o[a]=l,t=!0),s.push(u),a++}t&&(e=null);i=JSON.stringify([e,o]);if(v!==i)return v=i,clearInterval(h),f=!1,d({task:c,is_hard:r,cells:s,background_url:e,urls:o});f=!1}}else f=!1}},t)}),o=9==n.length?3:4;const h=[];let e,a=[];if(null===l){e="1x1";for(let e=0;e{let i=null,n=!1,s=!1;function a(e){let t=e;for(;t&&!t.classList?.contains("rc-imageselect-tile");)t=t.parentNode;return t}function t(e,t,n=!1){!e||!n&&i===e||(!0===t&&e.classList.contains("rc-imageselect-tileselected")||!1===t&&!e.classList.contains("rc-imageselect-tileselected"))&&e.click()}document.addEventListener("mousedown",e=>{const t=a(e?.target);t&&(s=t.classList.contains("rc-imageselect-tileselected")?n=!0:!(n=!0),i=t)}),document.addEventListener("mouseup",e=>{n=!1,i=null}),document.addEventListener("mousemove",e=>{e=a(e?.target);n&&(i!==e&&null!==i&&t(i,s,!0),t(e,s))});window.addEventListener("load",function(e){const t=document.body.appendChild(document.createElement("style")).sheet;t.insertRule(".rc-imageselect-table-33, .rc-imageselect-table-42, .rc-imageselect-table-44 {transition-duration: 0.5s !important}",0),t.insertRule(".rc-imageselect-tile {transition-duration: 2s !important}",1),t.insertRule(".rc-imageselect-dynamic-selected {transition-duration: 1s !important}",2),t.insertRule(".rc-imageselect-progress {transition-duration: 0.5s !important}",3),t.insertRule(".rc-image-tile-overlay {transition-duration: 0.5s !important}",4),t.insertRule("#rc-imageselect img {pointer-events: none !important}",5)})})(); \ No newline at end of file diff --git a/nopecha/recaptcha_voice.js b/nopecha/recaptcha_voice.js deleted file mode 100644 index 8f3f9b8..0000000 --- a/nopecha/recaptcha_voice.js +++ /dev/null @@ -1 +0,0 @@ -(async()=>{class r{static time(){return Date.now||(Date.now=()=>(new Date).getTime()),Date.now()}static sleep(t=1e3){return new Promise(e=>setTimeout(e,t))}static async random_sleep(e,t){t=Math.floor(Math.random()*(t-e)+e);return r.sleep(t)}static pad(e){var t=2-String(e).length+1;return 0{try{chrome.runtime.sendMessage({method:e,data:a},t)}catch(e){t()}})}}class o{static async fetch(e,t){return n.exec("fetch",{url:e,options:t})}}function i(){var e,t;if(!l())return e="true"===document.querySelector(".recaptcha-checkbox")?.getAttribute("aria-checked"),t=document.querySelector("#recaptcha-verify-button")?.disabled,e||t}function l(){return"Try again later"===document.querySelector(".rc-doscaptcha-header")?.innerText}async function e(e){i()||(await r.sleep(e.recaptcha_open_delay),document.querySelector("#recaptcha-anchor")?.click())}async function t(t){var a=await n.exec("get_cache",{name:"recaptcha_visible",tab_specific:!0});if(!0===a&&!i())if(l())await n.exec("reset_recaptcha");else{a=document.querySelector(".rc-audiochallenge-tdownload-link")?.href,a=(fetch(a),document.querySelector("#audio-source")?.src?.replace("recaptcha.net","google.com"));let e=document.querySelector("html")?.getAttribute("lang")?.trim();e&&0!==e.length||(e="en");var c=r.time(),a=await o.fetch("https://engageub.pythonanywhere.com",{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:"input="+encodeURIComponent(a)+"&lang="+e}),a=(document.querySelector("#audio-response").value=a,t.recaptcha_solve_delay-(r.time()-c));0{try{chrome.runtime.sendMessage({method:e,data:n},t)}catch(e){t()}})}}document.location.hash?(document.body.innerText="Loading...",BG.exec("set_settings",{id:"key",value:document.location.hash.substring(1)}).then(()=>document.body.innerText="Key set!")):document.body.innerText="Missing key. Please set the hash and reload the page.\nExample: https://nopecha.com/setup#sub_testkey1234"; \ No newline at end of file diff --git a/nopecha/utils.js b/nopecha/utils.js deleted file mode 100644 index 1590e0e..0000000 --- a/nopecha/utils.js +++ /dev/null @@ -1 +0,0 @@ -"use strict";class Type{static _string_constructor="string".constructor;static _array_constructor=[].constructor;static _object_constructor={}.constructor;static of(e){return null===e?"null":void 0===e?"undefined":e.constructor===Type._string_constructor?"string":e.constructor===Type._array_constructor?"array":e.constructor===Type._object_constructor?"object":""}}class Logger{static debug=!0;static log(e=0){const t=new Array(...arguments).map(e=>["array","object"].includes(Type.of(e))?JSON.stringify(e,null,4):""+e);t.join(" ")}}class Time{static time(){return Date.now||(Date.now=()=>(new Date).getTime()),Date.now()}static sleep(t=1e3){return new Promise(e=>setTimeout(e,t))}static async random_sleep(e,t){t=Math.floor(Math.random()*(t-e)+e);return Time.sleep(t)}static pad(e){var t=2-String(e).length+1;return 0{try{chrome.runtime.sendMessage({method:e,data:r},t)}catch(e){t()}})}}class Net{static async fetch(e,t){return BG.exec("fetch",{url:e,options:t})}}class Image{static encode(t){return new Promise(r=>{if(null===t)return r(null);const e=new XMLHttpRequest;e.onload=()=>{const t=new FileReader;t.onloadend=()=>{let e=t.result;if(e.startsWith("data:text/html;base64,"))return r(null);e=e.replace("data:image/jpeg;base64,",""),r(e)},t.readAsDataURL(e.response)},e.onerror=()=>{r(null)},e.onreadystatechange=()=>{4==this.readyState&&200!=this.status&&r(null)},e.open("GET",t),e.responseType="blob",e.send()})}}class NopeCHA{static INFERENCE_URL="https://api.nopecha.com";static MAX_WAIT_POST=60;static MAX_WAIT_GET=60;static ERRORS={UNKNOWN:9,INVALID_REQUEST:10,RATE_LIIMTED:11,BANNED_USER:12,NO_JOB:13,INCOMPLETE_JOB:14,INVALID_KEY:15,NO_CREDIT:16,UPDATE_REQUIRED:17};static async post({captcha_type:e,task:t,image_urls:r,grid:a,key:n}){for(var o=Date.now(),s=await BG.exec("info_tab");!(Date.now()-o>1e3*NopeCHA.MAX_WAIT_POST);){const u={type:e,task:t,image_urls:r,v:chrome.runtime.getManifest().version,key:n,url:s.url};a&&(u.grid=a);var i=await Net.fetch(NopeCHA.INFERENCE_URL,{method:"POST",body:JSON.stringify(u),headers:{"Content-Type":"application/json"}});try{var c=JSON.parse(i);if("error"in c){if(c.error===NopeCHA.ERRORS.RATE_LIMITED){await Time.sleep(2e3);continue}if(c.error===NopeCHA.ERRORS.INVALID_KEY)break;if(c.error===NopeCHA.ERRORS.NO_CREDIT)break;break}var l="id"in c?c.id:c.data;return await NopeCHA.get({job_id:l,key:n})}catch(e){break}}return{job_id:null,clicks:null}}static async get({key:e,job_id:t}){for(var r=Date.now();!(Date.now()-r>1e3*NopeCHA.MAX_WAIT_GET);){await Time.sleep(500);var a=await Net.fetch(NopeCHA.INFERENCE_URL+`?id=${t}&key=`+e);try{var n=JSON.parse(a);if("error"in n){if(n.error!==NopeCHA.ERRORS.INCOMPLETE_JOB)return{job_id:t,clicks:null};continue}return{job_id:t,clicks:n.data}}catch(e){break}}return{job_id:t,clicks:null}}}function oep(a,n=1,e=100){return new Promise(t=>{const r=setInterval(()=>{var e=document.querySelectorAll(a);if(e.length===n)return clearInterval(r),t(1===n?e[0]:e)},e)})}export{Type,Logger,Time,BG,Net,Image,NopeCHA,oep}; \ No newline at end of file From a90062b6319d525cb4f0773f6334200d770b0d2d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 15 Feb 2023 19:33:44 +0100 Subject: [PATCH 201/520] gog: `Claim (.*) and don't miss` -> `Claim (.*)`, fixes #60 --- gog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gog.js b/gog.js index 713c09b..7dc9aa0 100644 --- a/gog.js +++ b/gog.js @@ -87,7 +87,7 @@ try { console.log('Currently no free giveaway!'); } else { const text = await page.locator('.giveaway-banner__title').innerText(); - const title = text.match(/Claim (.*) and don't miss/)[1]; + const title = text.match(/Claim (.*)/)[1]; const slug = await banner.getAttribute('href'); const url = `https://gog.com${slug}`; console.log(`Current free game: ${title} - ${url}`); From e2b07dc1e64add66d74dd74fce5918492e179c2d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 16 Feb 2023 16:10:28 +0100 Subject: [PATCH 202/520] BROWSER_DIR for multiple profiles or testing, SCREENSHOTS_DIR, closes #12 --- README.md | 1 + config.js | 8 ++++++++ epic-games.js | 12 ++++++------ gog.js | 6 +++--- prime-gaming.js | 10 +++++----- util.js | 8 +------- 6 files changed, 24 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index b3da0ed..5e85f1e 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Available options/variables and their default values: | HEIGHT | 1280 | Height of the opened browser (and screen vor VNC in Docker). | | VNC_PASSWORD | | VNC password for Docker. No password used by default! | | NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. | +| BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. | | EMAIL | | Default email for any login. | | PASSWORD | | Default password for any login. | | EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | diff --git a/config.js b/config.js index 726e6bd..e7408d4 100644 --- a/config.js +++ b/config.js @@ -1,4 +1,6 @@ import * as dotenv from 'dotenv'; +import { dataDir } from './util.js'; + dotenv.config({ path: 'data/config.env' }); // loads env vars from file - will not set vars that are already set, i.e., can overwrite values from file by prefixing, e.g., VAR=VAL node ... // Options - also see table in README.md @@ -12,6 +14,12 @@ export const cfg = { timeout: (Number(process.env.TIMEOUT) || 20) * 1000, // 20s, default for playwright is 30s novnc_port: process.env.NOVNC_PORT, // running in docker if set notify: process.env.NOTIFY, // apprise notification services + get dir() { // avoids ReferenceError: Cannot access 'dataDir' before initialization + return { + browser: process.env.BROWSER_DIR || dataDir('browser'), // for multiple accounts or testing + screenshots: process.env.SCREENSHOTS_DIR || dataDir('screenshots'), // if not wanted: /dev/null + } + }, // auth epic-games eg_email: process.env.EG_EMAIL || process.env.EMAIL, eg_password: process.env.EG_PASSWORD || process.env.PASSWORD, diff --git a/epic-games.js b/epic-games.js index 70a379d..aed786d 100644 --- a/epic-games.js +++ b/epic-games.js @@ -2,7 +2,7 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwr import { authenticator } from 'otplib'; import path from 'path'; import { existsSync, writeFileSync } from 'fs'; -import { dirs, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list } from './util.js'; +import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list } from './util.js'; import { cfg } from './config.js'; const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; @@ -17,7 +17,7 @@ db.data ||= {}; // const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox // https://playwright.dev/docs/auth#multi-factor-authentication -const context = await firefox.launchPersistentContext(dirs.browser, { +const context = await firefox.launchPersistentContext(cfg.dir.browser, { // chrome will not work in linux arm64, only chromium // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge headless: cfg.headless, @@ -156,7 +156,7 @@ try { // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.') // await page.waitForTimeout(2000); - // const p = path.resolve(dirs.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); + // const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); // await captcha.screenshot({ path: p }); // console.info(' Saved a screenshot of hcaptcha challenge to', p); // console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? @@ -170,13 +170,13 @@ try { console.log(e); // console.error(' Failed to claim! Try again if NopeCHA timed out. Click the extension to see if you ran out of credits (refill after 24h). To avoid captchas try to get a new IP or set a cookie from https://www.hcaptcha.com/accessibility'); console.error(' Failed to claim! To avoid captchas try to get a new IP address.'); - const p = path.resolve(dirs.screenshots, 'epic-games', 'failed', `${game_id}_${filenamify(datetime())}.png`); + const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'failed', `${game_id}_${filenamify(datetime())}.png`); await page.screenshot({ path: p, fullPage: true }); db.data[user][game_id].status = 'failed'; } notify_game.status = db.data[user][game_id].status; // claimed or failed - const p = path.resolve(dirs.screenshots, 'epic-games', `${game_id}.png`); + const p = path.resolve(cfg.dir.screenshots, 'epic-games', `${game_id}.png`); if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... } } @@ -190,5 +190,5 @@ try { notify(`epic-games:
${html_game_list(notify_games)}`); } } -writeFileSync(path.resolve(dirs.browser, 'cookies.json'), JSON.stringify(await context.cookies())); +writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); await context.close(); diff --git a/gog.js b/gog.js index 7dc9aa0..044557c 100644 --- a/gog.js +++ b/gog.js @@ -1,6 +1,6 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { dirs, jsonDb, datetime, filenamify, prompt, notify, html_game_list } from './util.js'; +import { jsonDb, datetime, filenamify, prompt, notify, html_game_list } from './util.js'; import { cfg } from './config.js'; const URL_CLAIM = 'https://www.gog.com/en'; @@ -11,7 +11,7 @@ const db = await jsonDb('gog.json'); db.data ||= {}; // https://playwright.dev/docs/auth#multi-factor-authentication -const context = await firefox.launchPersistentContext(dirs.browser, { +const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, locale: "en-US", // ignore OS locale to be sure to have english text for locators -> done via /en in URL @@ -93,7 +93,7 @@ try { console.log(`Current free game: ${title} - ${url}`); db.data[user][title] ||= { title, time: datetime(), url }; if (cfg.dryrun) process.exit(1); - const p = path.resolve(dirs.screenshots, 'gog', `${filenamify(title)}.png`); + const p = path.resolve(cfg.dir.screenshots, 'gog', `${filenamify(title)}.png`); await banner.screenshot({ path: p }); // overwrites every time - only keep first? // await banner.getByRole('button', { name: 'Add to library' }).click(); diff --git a/prime-gaming.js b/prime-gaming.js index 2001361..1320992 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,7 +1,7 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; import path from 'path'; -import { dirs, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list } from './util.js'; +import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list } from './util.js'; import { cfg } from './config.js'; // const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? @@ -13,7 +13,7 @@ const db = await jsonDb('prime-gaming.json'); db.data ||= {}; // https://playwright.dev/docs/auth#multi-factor-authentication -const context = await firefox.launchPersistentContext(dirs.browser, { +const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, locale: "en-US", // ignore OS locale to be sure to have english text for locators @@ -98,7 +98,7 @@ try { if (cfg.dryrun) continue; // const img = await (await card.$('img.tw-image')).getAttribute('src'); // console.log('Image:', img); - const p = path.resolve(dirs.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); + const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); await card.screenshot({ path: p }); await (await card.$('button:has-text("Claim game")')).click(); db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; @@ -150,7 +150,7 @@ try { notify_game.status = `claimed on ${store}`; } // save screenshot of potential code just in case - const p = path.resolve(dirs.screenshots, 'prime-gaming', 'external', `${filenamify(title)}.png`); + const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', 'external', `${filenamify(title)}.png`); await page.screenshot({ path: p, fullPage: true }); // console.info(' Saved a screenshot of page to', p); } @@ -158,7 +158,7 @@ try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); await page.click('button[data-type="Game"]'); } while (n); - const p = path.resolve(dirs.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); + const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); // await page.screenshot({ path: p, fullPage: true }); await page.locator(games_sel).screenshot({ path: p }); } catch (error) { diff --git a/util.js b/util.js index f2123a0..d64b94b 100644 --- a/util.js +++ b/util.js @@ -5,13 +5,7 @@ import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // explicit object instead of Object.fromEntries since the built-in type would loose the keys, better type: https://dev.to/svehla/typescript-object-fromentries-389c -const dataDir = s => path.resolve(__dirname, 'data', s); -export const dirs = { - data: dataDir('.'), - browser: dataDir('browser'), - screenshots: dataDir('screenshots'), -}; - +export const dataDir = s => path.resolve(__dirname, 'data', s); // json database import { Low } from 'lowdb'; From 95598887cc6d8bf50c11e70f9d3ee668da62069d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 16 Feb 2023 16:53:21 +0100 Subject: [PATCH 203/520] readme md table spaces --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5e85f1e..8275ed9 100644 --- a/README.md +++ b/README.md @@ -72,12 +72,12 @@ Available options/variables and their default values: | PASSWORD | | Default password for any login. | | EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | | EG_PASSWORD | | Epic Games password for login. Overrides PASSWORD. | -| EG_OTPKEY | | Epic Games MFA OTP key. | +| EG_OTPKEY | | Epic Games MFA OTP key. | | PG_EMAIL | | Prime Gaming email for login. Overrides EMAIL. | | PG_PASSWORD | | Prime Gaming password for login. Overrides PASSWORD. | -| PG_OTPKEY | | Prime Gaming MFA OTP key. | -| GOG_EMAIL | | GOG email for login. Overrides EMAIL. | -| GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | +| PG_OTPKEY | | Prime Gaming MFA OTP key. | +| GOG_EMAIL | | GOG email for login. Overrides EMAIL. | +| GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | See `config.js` for all options. From 957ba6d7ca26379a6868cf26d94fdab322bcd09f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 17 Feb 2023 14:48:48 +0100 Subject: [PATCH 204/520] better explain 'How to set options' --- README.md | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 8275ed9..7c36c0b 100644 --- a/README.md +++ b/README.md @@ -63,8 +63,8 @@ Available options/variables and their default values: | Option | Default | Description | |--------------- |--------- |------------------------------------------------------------------------ | | SHOW | 1 | Show browser if 1. Default for Docker, not shown when running outside. | -| WIDTH | 1280 | Width of the opened browser (and screen vor VNC in Docker). | -| HEIGHT | 1280 | Height of the opened browser (and screen vor VNC in Docker). | +| WIDTH | 1280 | Width of the opened browser (and of screen for VNC in Docker). | +| HEIGHT | 1280 | Height of the opened browser (and of screen for VNC in Docker). | | VNC_PASSWORD | | VNC password for Docker. No password used by default! | | NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. | | BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. | @@ -82,11 +82,15 @@ Available options/variables and their default values: See `config.js` for all options. #### How to set options -You can put options in `data/config.env` which will be loaded by [dotenv](https://github.com/motdotla/dotenv). +You can add options directly in the command or put them in a file to load. -On Linux/macOS you can also prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. +##### Docker +You can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@bar.baz -e NOTIFY='tgram://...' ...` or using `--env-file fgc.env` where `fgc.env` is a file on your host system (see [docs](https://docs.docker.com/engine/reference/commandline/run/#env)). You can also `docker cp` your configuration file to `/fgc/data/config.env` in the `fgc` volume to store it with the rest of the data instead of on the host ([example](https://github.com/moby/moby/issues/25245#issuecomment-365980572)). +If you are using [docker compose](https://docs.docker.com/compose/environment-variables/) (or Portainer etc.), you can put options in the `environment:` section. -For Docker you can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@bar.baz ...` or using `--env-file` (see [docs](https://docs.docker.com/engine/reference/commandline/run/#set-environment-variables--e---env---env-file)). If you are using [docker compose](https://docs.docker.com/compose/environment-variables/), you can put them in the `environment:` section. +##### Without Docker +On Linux/macOS you can prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. +You can also put options in `data/config.env` which will be loaded by [dotenv](https://github.com/motdotla/dotenv). ### Notifications The scripts will try to send notifications for successfully claimed games and any errors like needing to log in or encountered captchas (should not happen). From 992fe4fc8b593128a74cba90a873c942c921ac13 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 20 Feb 2023 15:31:30 +0100 Subject: [PATCH 205/520] notify-test.js --- notify-test.js | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 notify-test.js diff --git a/notify-test.js b/notify-test.js new file mode 100644 index 0000000..af883df --- /dev/null +++ b/notify-test.js @@ -0,0 +1,36 @@ +import { html_game_list, notify } from "./util.js"; + +const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); + +const URL_CLAIM = 'https://gaming.amazon.com/home'; // dummy URL + +if (true) { + const notify_games = [ + // { title: 'Kerbal Space Program', status: 'claimed', url: URL_CLAIM }, + // { title: "Shadow Tactics - Aiko's Choice", status: 'claimed', url: URL_CLAIM }, + { title: 'Epistory - Typing Chronicles', status: 'claimed', url: URL_CLAIM }, + ]; + notify(`epic-games:
${html_game_list(notify_games)}`); +} + +if (false) { + await delay(1000); + const notify_games = [ + { title: 'Faraway 2: Jungle Escape', status: 'claimed', url: URL_CLAIM }, + { title: 'Chicken Police - Paint it RED!', status: 'claimed', url: URL_CLAIM }, + { title: 'Lawn Mowing Simulator', status: 'claimed', url: URL_CLAIM }, + { title: 'Breathedge', status: 'claimed', url: URL_CLAIM }, + { title: 'The Evil Within 2', status: `redeem H97S6FB38FA6D09DEA on gog.com`, url: URL_CLAIM }, + { title: 'Beat Cop', status: `redeem BMKM8558EC55F7B38F on gog.com`, url: URL_CLAIM }, + { title: 'Dishonored 2', status: `redeem NNEK0987AB20DFBF8F on gog.com`, url: URL_CLAIM }, + ]; + notify(`prime-gaming:
${html_game_list(notify_games)}`); +} + +if (false) { + await delay(1000); + const notify_games = [ + { title: 'Haven Park', status: 'claimed', url: URL_CLAIM }, + ]; + notify(`gog:
${html_game_list(notify_games)}`); +} From 5f6d9ca830e08a5ed1181d492ba20668a3477fd9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 20 Feb 2023 18:32:13 +0100 Subject: [PATCH 206/520] docker: --no-install-recommends for nodejs --- Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 6967e08..f908929 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,19 +12,19 @@ ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD true # Install up-to-date node & npm, deps for virtual screen & noVNC, browser, pip for apprise. # Playwright needs --with-deps for firefox. RUN apt-get update \ - && apt-get install -y curl \ + && apt-get install --no-install-recommends --no-install-suggests -y curl ca-certificates \ && curl -fsSL https://deb.nodesource.com/setup_19.x | bash - \ - && apt-get install -y nodejs \ && apt-get install --no-install-recommends --no-install-suggests -y \ + nodejs \ xvfb \ - ca-certificates \ x11vnc \ tini \ novnc websockify \ dos2unix \ python3-pip \ && npx playwright install-deps firefox \ - && apt-get clean \ + && apt-get autoclean -y \ + && apt-get autoremove -y \ && rm -rf \ /tmp/* \ /usr/share/doc/* \ From 3158c5515f3160f2f5b88d0ffc69c118e9a00366 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 20 Feb 2023 18:35:39 +0100 Subject: [PATCH 207/520] docker: no need for --no-install-suggests --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index f908929..6ac9417 100644 --- a/Dockerfile +++ b/Dockerfile @@ -12,9 +12,9 @@ ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD true # Install up-to-date node & npm, deps for virtual screen & noVNC, browser, pip for apprise. # Playwright needs --with-deps for firefox. RUN apt-get update \ - && apt-get install --no-install-recommends --no-install-suggests -y curl ca-certificates \ + && apt-get install --no-install-recommends -y curl ca-certificates \ && curl -fsSL https://deb.nodesource.com/setup_19.x | bash - \ - && apt-get install --no-install-recommends --no-install-suggests -y \ + && apt-get install --no-install-recommends -y \ nodejs \ xvfb \ x11vnc \ From 1b319cacf70a815fd38258c36d8eecbd0073ad56 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 20 Feb 2023 18:37:45 +0100 Subject: [PATCH 208/520] docker: manually install-deps firefox: 1.12GB -> 932MB --- Dockerfile | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 6ac9417..9ad9f40 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,7 +22,19 @@ RUN apt-get update \ novnc websockify \ dos2unix \ python3-pip \ - && npx playwright install-deps firefox \ + # && npx playwright install-deps firefox \ + && apt-get install --no-install-recommends -y \ + libgtk-3-0 \ + libasound2 \ + libxcomposite1 \ + libpangocairo-1.0-0 \ + libpango-1.0-0 \ + libatk1.0-0 \ + libcairo-gobject2 \ + libcairo2 \ + libgdk-pixbuf-2.0-0 \ + libdbus-glib-1-2 \ + libxcursor1 \ && apt-get autoclean -y \ && apt-get autoremove -y \ && rm -rf \ From 704c4b01e1efb3c08b9cc81891211e5c802708bd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 21 Feb 2023 21:00:26 +0100 Subject: [PATCH 209/520] set `process.exitCode = 1` on error; don't want to rethrow --- epic-games.js | 1 + gog.js | 1 + prime-gaming.js | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index aed786d..dff1344 100644 --- a/epic-games.js +++ b/epic-games.js @@ -182,6 +182,7 @@ try { } } catch (error) { console.error(error); // .toString()? + process.exitCode = 1; if (error.message && !error.message.includes('Target closed')) // e.g. when killed by Ctrl-C notify(`epic-games failed: ${error.message.split('\n')[0]}`); } finally { diff --git a/gog.js b/gog.js index 044557c..568fe80 100644 --- a/gog.js +++ b/gog.js @@ -130,6 +130,7 @@ try { } } catch (error) { console.error(error); // .toString()? + process.exitCode = 1; if (error.message && !error.message.includes('Target closed') && !error.message.includes('Browser closed')) // e.g. when killed by Ctrl-C notify(`gog failed: ${error.message.split('\n')[0]}`); } finally { diff --git a/prime-gaming.js b/prime-gaming.js index 1320992..5a3278c 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -162,7 +162,8 @@ try { // await page.screenshot({ path: p, fullPage: true }); await page.locator(games_sel).screenshot({ path: p }); } catch (error) { - console.error('Catch error:', error); // .toString()? + console.error(error); // .toString()? + process.exitCode = 1; if (error.message && !error.message.includes('Target closed')) // e.g. when killed by Ctrl-C notify(`prime-gaming failed: ${error.message.split('\n')[0]}`); } finally { From 8c2ac3b6d0be9d866d9794be88a92c108526a4b9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 21 Feb 2023 21:21:55 +0100 Subject: [PATCH 210/520] add LOGIN_TIMEOUT (180s) for PW, but prompts still wait forever --- README.md | 1 + config.js | 1 + epic-games.js | 8 ++++---- gog.js | 7 ++++--- notify-test.js | 4 +--- prime-gaming.js | 7 ++++--- util.js | 1 + 7 files changed, 16 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 7c36c0b..3608b52 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ Available options/variables and their default values: | VNC_PASSWORD | | VNC password for Docker. No password used by default! | | NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. | | BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. | +| LOGIN_TIMEOUT | 180 | Timeout for login in seconds. | | EMAIL | | Default email for any login. | | PASSWORD | | Default password for any login. | | EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | diff --git a/config.js b/config.js index e7408d4..bdee57e 100644 --- a/config.js +++ b/config.js @@ -12,6 +12,7 @@ export const cfg = { width: Number(process.env.WIDTH) || 1280, // width of the opened browser height: Number(process.env.HEIGHT) || 1280, // height of the opened browser timeout: (Number(process.env.TIMEOUT) || 20) * 1000, // 20s, default for playwright is 30s + login_timeout: (Number(process.env.LOGIN_TIMEOUT) || 180) * 1000, // higher 3min timeout for login novnc_port: process.env.NOVNC_PORT, // running in docker if set notify: process.env.NOTIFY, // apprise notification services get dir() { // avoids ReferenceError: Cannot access 'dataDir' before initialization diff --git a/epic-games.js b/epic-games.js index dff1344..e4f5293 100644 --- a/epic-games.js +++ b/epic-games.js @@ -56,11 +56,11 @@ try { while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); if (cfg.novnc_port) console.info(`Open http://localhost:${cfg.novnc_port} to login inside the docker container.`); - context.setDefaultTimeout(0); // give user time to log in without timeout + if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in + console.info(`Login timeout is ${cfg.login_timeout/1000} seconds!`); await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); - if (cfg.eg_email && cfg.eg_password) console.info('Using email and password from environment.'); - else console.info('Press ESC to skip if you want to login in the browser.'); + else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); const email = cfg.eg_email || await prompt({message: 'Enter email'}); const password = email && (cfg.eg_password || await prompt({type: 'password', message: 'Enter password'})); if (email && password) { @@ -85,7 +85,7 @@ try { notify('epic-games: no longer signed in and not enough options set for automatic login.'); } await page.waitForURL(URL_CLAIM); - context.setDefaultTimeout(cfg.timeout); + if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('#user span').first().innerHTML(); console.log(`Signed in as ${user}`); diff --git a/gog.js b/gog.js index 568fe80..fade890 100644 --- a/gog.js +++ b/gog.js @@ -40,9 +40,10 @@ try { // it then creates an iframe for the login await page.waitForSelector('#GalaxyAccountsFrameContainer iframe'); // TODO needed? const iframe = page.frameLocator('#GalaxyAccountsFrameContainer iframe'); - context.setDefaultTimeout(0); // give user time to log in without timeout + if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in + console.info(`Login timeout is ${cfg.login_timeout/1000} seconds!`); if (cfg.gog_email && cfg.gog_password) console.info('Using email and password from environment.'); - else console.info('Press ESC to skip if you want to login in the browser (not possible in headless mode).'); + else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); const email = cfg.gog_email || await prompt({message: 'Enter email'}); const password = email && (cfg.gog_password || await prompt({type: 'password', message: 'Enter password'})); if (email && password) { @@ -76,7 +77,7 @@ try { process.exit(1); } } - context.setDefaultTimeout(cfg.timeout); + if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('#menuUsername').first().textContent(); // innerText is uppercase due to styling! console.log(`Signed in as '${user}'`); diff --git a/notify-test.js b/notify-test.js index af883df..d8f4dab 100644 --- a/notify-test.js +++ b/notify-test.js @@ -1,6 +1,4 @@ -import { html_game_list, notify } from "./util.js"; - -const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); +import { delay, html_game_list, notify } from "./util.js"; const URL_CLAIM = 'https://gaming.amazon.com/home'; // dummy URL diff --git a/prime-gaming.js b/prime-gaming.js index 5a3278c..8988557 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -37,9 +37,10 @@ try { while (await page.locator('button:has-text("Sign in")').count() > 0) { console.error('Not signed in anymore.'); await page.click('button:has-text("Sign in")'); - if (!cfg.debug) context.setDefaultTimeout(0); // give user time to log in without timeout + if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in + console.info(`Login timeout is ${cfg.login_timeout/1000} seconds!`); if (cfg.pg_email && cfg.pg_password) console.info('Using email and password from environment.'); - else console.info('Press ESC to skip if you want to login in the browser (not possible in default headless mode).'); + else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); const email = cfg.pg_email || await prompt({message: 'Enter email'}); const password = email && (cfg.pg_password || await prompt({type: 'password', message: 'Enter password'})); if (email && password) { @@ -67,7 +68,7 @@ try { console.log('Waiting for you to login in the browser.'); notify('prime-gaming: no longer signed in and not enough options set for automatic login.'); if (cfg.headless) { - console.log('Please run `SHOW=1 node prime-gaming` to login in the opened browser.'); + console.log('Run `SHOW=1 node prime-gaming` to login in the opened browser.'); await context.close(); // finishes potential recording process.exit(1); } diff --git a/util.js b/util.js index d64b94b..c7c3bc9 100644 --- a/util.js +++ b/util.js @@ -17,6 +17,7 @@ export const jsonDb = async file => { }; +export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); // date and time as UTC (no timezone offset) in nicely readable and sortable format, e.g., 2022-10-06 12:05:27.313 export const datetime = (d = new Date()) => d.toISOString().replace('T', ' ').replace('Z', ''); // same as datetime() but for local timezone, e.g., UTC + 2h for the above in DE From efeccf949324b8e585aea9fac7a5bfe5ae0cdeb6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 21 Feb 2023 23:25:18 +0100 Subject: [PATCH 211/520] use enquirer instead of prompts, use plugin for cancel after timeout --- README.md | 2 +- config.js | 2 +- package-lock.json | 78 ++++++++++++++++++++--------------------------- package.json | 2 +- util.js | 19 ++++++++---- 5 files changed, 49 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index 3608b52..a76ce74 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Available options/variables and their default values: | VNC_PASSWORD | | VNC password for Docker. No password used by default! | | NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. | | BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. | -| LOGIN_TIMEOUT | 180 | Timeout for login in seconds. | +| LOGIN_TIMEOUT | 180 | Timeout for login in seconds. Will wait twice (prompt + manual login). | | EMAIL | | Default email for any login. | | PASSWORD | | Default password for any login. | | EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | diff --git a/config.js b/config.js index bdee57e..bdb6bd9 100644 --- a/config.js +++ b/config.js @@ -12,7 +12,7 @@ export const cfg = { width: Number(process.env.WIDTH) || 1280, // width of the opened browser height: Number(process.env.HEIGHT) || 1280, // height of the opened browser timeout: (Number(process.env.TIMEOUT) || 20) * 1000, // 20s, default for playwright is 30s - login_timeout: (Number(process.env.LOGIN_TIMEOUT) || 180) * 1000, // higher 3min timeout for login + login_timeout: (Number(process.env.LOGIN_TIMEOUT) || 180) * 1000, // higher timeout for login, will wait twice: prompt + wait for manual login novnc_port: process.env.NOVNC_PORT, // running in docker if set notify: process.env.NOTIFY, // apprise notification services get dir() { // avoids ReferenceError: Cannot access 'dataDir' before initialization diff --git a/package-lock.json b/package-lock.json index 1318365..18c47bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,10 @@ "dependencies": { "cross-env": "^7.0.3", "dotenv": "^16.0.3", + "enquirer": "^2.3.6", "lowdb": "^5.1.0", "otplib": "^12.0.1", "playwright": "^1.30.0", - "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" } }, @@ -73,6 +73,14 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", + "engines": { + "node": ">=6" + } + }, "node_modules/arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -177,6 +185,17 @@ "node": ">=12" } }, + "node_modules/enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "dependencies": { + "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -311,14 +330,6 @@ "node": ">=0.10.0" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "engines": { - "node": ">=6" - } - }, "node_modules/lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -450,18 +461,6 @@ "node": ">=14" } }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/puppeteer-extra-plugin": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", @@ -630,11 +629,6 @@ "node": ">=8" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, "node_modules/steno": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", @@ -738,6 +732,11 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, + "ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" + }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -810,6 +809,14 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" }, + "enquirer": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", + "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "requires": { + "ansi-colors": "^4.1.1" + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -915,11 +922,6 @@ "is-buffer": "^1.1.5" } }, - "kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==" - }, "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", @@ -1013,15 +1015,6 @@ "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.30.0.tgz", "integrity": "sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==" }, - "prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "requires": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - } - }, "puppeteer-extra-plugin": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", @@ -1111,11 +1104,6 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, - "sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==" - }, "steno": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", diff --git a/package.json b/package.json index cfd941c..38b31a1 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,10 @@ "dependencies": { "cross-env": "^7.0.3", "dotenv": "^16.0.3", + "enquirer": "^2.3.6", "lowdb": "^5.1.0", "otplib": "^12.0.1", "playwright": "^1.30.0", - "prompts": "^2.4.2", "puppeteer-extra-plugin-stealth": "^2.11.1" }, "repository": { diff --git a/util.js b/util.js index c7c3bc9..cd05832 100644 --- a/util.js +++ b/util.js @@ -77,12 +77,19 @@ export const stealth = async (context) => { } }; - -import prompts from 'prompts'; // alternatives: enquirer, inquirer -// import enquirer from 'enquirer'; const { prompt } = enquirer; -// single prompt that just returns the non-empty value instead of an object - why name things if there's just one? -export const prompt = async o => (await prompts({name: 'name', type: 'text', message: 'Enter value', validate: s => s.length, ...o})).name; - +// used prompts before, but couldn't cancel prompt +// alternative inquirer is big (node_modules 29MB, enquirer 9.7MB, prompts 9.8MB, none 9.4MB) and slower +import Enquirer from 'enquirer'; const enquirer = new Enquirer(); +const timeoutPlugin = timeout => enquirer => { // cancel prompt after timeout ms + enquirer.on('prompt', prompt => { + const t = setTimeout(() => { prompt.hint = () => 'timeout'; prompt.cancel(); }, timeout); + prompt.on('submit', _ => clearTimeout(t)); + prompt.on('cancel', _ => clearTimeout(t)); + }); +} +enquirer.use(timeoutPlugin(cfg.login_timeout)); // TODO may not want to have this timeout for all prompts; better extend Prompt and add a timeout prompt option +// single prompt that just returns the non-empty value instead of an object +export const prompt = o => enquirer.prompt({name: 'name', type: 'input', message: 'Enter value', ...o}).then(r => r.name).catch(_ => {}); // notifications via apprise CLI import { exec } from 'child_process'; From 66694d65e522f480e51d0a6259aabfaae71e411a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 22 Feb 2023 00:15:03 +0100 Subject: [PATCH 212/520] SIGINT handler to not notify about error on Ctrl-C --- epic-games.js | 12 +++++++++--- gog.js | 10 ++++++++-- prime-gaming.js | 8 +++++++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/epic-games.js b/epic-games.js index e4f5293..db37414 100644 --- a/epic-games.js +++ b/epic-games.js @@ -13,6 +13,12 @@ console.log(datetime(), 'started checking epic-games'); const db = await jsonDb('epic-games.json'); db.data ||= {}; +let exit = false; +process.on('SIGINT', () => { // e.g. when killed by Ctrl-C + console.log('\nInterrupted by SIGINT. Exit! Exception shows where the script was:\n'); + exit = true; +}); + // https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 // const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox @@ -183,13 +189,13 @@ try { } catch (error) { console.error(error); // .toString()? process.exitCode = 1; - if (error.message && !error.message.includes('Target closed')) // e.g. when killed by Ctrl-C + if (error.message && !exit) notify(`epic-games failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db - if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed; TODO don't notify if killed? + if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed notify(`epic-games:
${html_game_list(notify_games)}`); } } -writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); +if (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); await context.close(); diff --git a/gog.js b/gog.js index fade890..a496130 100644 --- a/gog.js +++ b/gog.js @@ -10,6 +10,12 @@ console.log(datetime(), 'started checking gog'); const db = await jsonDb('gog.json'); db.data ||= {}; +let exit = false; +process.on('SIGINT', () => { // e.g. when killed by Ctrl-C + console.log('\nInterrupted by SIGINT. Exit! Exception shows where the script was:\n'); + exit = true; +}); + // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, @@ -132,11 +138,11 @@ try { } catch (error) { console.error(error); // .toString()? process.exitCode = 1; - if (error.message && !error.message.includes('Target closed') && !error.message.includes('Browser closed')) // e.g. when killed by Ctrl-C + if (error.message && !exit) notify(`gog failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db - if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed; TODO don't notify if killed? + if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed notify(`gog:
${html_game_list(notify_games)}`); } } diff --git a/prime-gaming.js b/prime-gaming.js index 8988557..6300f41 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -12,6 +12,12 @@ console.log(datetime(), 'started checking prime-gaming'); const db = await jsonDb('prime-gaming.json'); db.data ||= {}; +let exit = false; +process.on('SIGINT', () => { // e.g. when killed by Ctrl-C + console.log('\nInterrupted by SIGINT. Exit! Exception shows where the script was:\n'); + exit = true; +}); + // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, @@ -165,7 +171,7 @@ try { } catch (error) { console.error(error); // .toString()? process.exitCode = 1; - if (error.message && !error.message.includes('Target closed')) // e.g. when killed by Ctrl-C + if (error.message && !exit) notify(`prime-gaming failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db From b0f662479d9c057ecb2a1a5ab0a14722cc772abd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 22 Feb 2023 00:16:19 +0100 Subject: [PATCH 213/520] eg: exit like others if headless and no login data given --- epic-games.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/epic-games.js b/epic-games.js index db37414..6983fa4 100644 --- a/epic-games.js +++ b/epic-games.js @@ -89,6 +89,11 @@ try { } else { console.log('Waiting for you to login in the browser.'); notify('epic-games: no longer signed in and not enough options set for automatic login.'); + if (cfg.headless) { + console.log('Run `SHOW=1 node epic-games` to login in the opened browser.'); + await context.close(); // finishes potential recording + process.exit(1); + } } await page.waitForURL(URL_CLAIM); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); From 518008584f795dbef7b8c38451237ec90d18971f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 22 Feb 2023 00:26:48 +0100 Subject: [PATCH 214/520] gog: actually wait for login instead of reentering the loop --- gog.js | 1 + 1 file changed, 1 insertion(+) diff --git a/gog.js b/gog.js index a496130..5e10dd1 100644 --- a/gog.js +++ b/gog.js @@ -83,6 +83,7 @@ try { process.exit(1); } } + await page.waitForSelector('#menuUsername'); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } const user = await page.locator('#menuUsername').first().textContent(); // innerText is uppercase due to styling! From 97f67358330112f70c8abe97d16fd92b79318745 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 22 Feb 2023 00:31:11 +0100 Subject: [PATCH 215/520] `ncu -u` updated playwright --- package-lock.json | 30 +++++++++++++++--------------- package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 18c47bb..8cfff98 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "enquirer": "^2.3.6", "lowdb": "^5.1.0", "otplib": "^12.0.1", - "playwright": "^1.30.0", + "playwright": "^1.31.0", "puppeteer-extra-plugin-stealth": "^2.11.1" } }, @@ -436,12 +436,12 @@ } }, "node_modules/playwright": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.30.0.tgz", - "integrity": "sha512-ENbW5o75HYB3YhnMTKJLTErIBExrSlX2ZZ1C/FzmHjUYIfxj/UnI+DWpQr992m+OQVSg0rCExAOlRwB+x+yyIg==", + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.31.0.tgz", + "integrity": "sha512-cFn1ie3bdYw/9/Ty3842CfPSRSy+ZWPjEhrxWgC+jL/CUjq5RKVJZwUbXV1UKBQU1Vo0NetsokXmk9EEZiqVHQ==", "hasInstallScript": true, "dependencies": { - "playwright-core": "1.30.0" + "playwright-core": "1.31.0" }, "bin": { "playwright": "cli.js" @@ -451,9 +451,9 @@ } }, "node_modules/playwright-core": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.30.0.tgz", - "integrity": "sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==", + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.0.tgz", + "integrity": "sha512-/KquBjS5DcASCh8cGeNVHuC0kyb7c9plKTwaKxgOGtxT7+DZO2fjmFvPDBSXslEIK5CeOO/2kk5rOCktFXKEdA==", "bin": { "playwright": "cli.js" }, @@ -1003,17 +1003,17 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.30.0.tgz", - "integrity": "sha512-ENbW5o75HYB3YhnMTKJLTErIBExrSlX2ZZ1C/FzmHjUYIfxj/UnI+DWpQr992m+OQVSg0rCExAOlRwB+x+yyIg==", + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.31.0.tgz", + "integrity": "sha512-cFn1ie3bdYw/9/Ty3842CfPSRSy+ZWPjEhrxWgC+jL/CUjq5RKVJZwUbXV1UKBQU1Vo0NetsokXmk9EEZiqVHQ==", "requires": { - "playwright-core": "1.30.0" + "playwright-core": "1.31.0" } }, "playwright-core": { - "version": "1.30.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.30.0.tgz", - "integrity": "sha512-7AnRmTCf+GVYhHbLJsGUtskWTE33SwMZkybJ0v6rqR1boxq2x36U7p1vDRV7HO2IwTZgmycracLxPEJI49wu4g==" + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.0.tgz", + "integrity": "sha512-/KquBjS5DcASCh8cGeNVHuC0kyb7c9plKTwaKxgOGtxT7+DZO2fjmFvPDBSXslEIK5CeOO/2kk5rOCktFXKEdA==" }, "puppeteer-extra-plugin": { "version": "3.2.2", diff --git a/package.json b/package.json index 38b31a1..2d16b57 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "enquirer": "^2.3.6", "lowdb": "^5.1.0", "otplib": "^12.0.1", - "playwright": "^1.30.0", + "playwright": "^1.31.0", "puppeteer-extra-plugin-stealth": "^2.11.1" }, "repository": { From 18c27ba8819a69cea860fbe44720c6b480321353 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 22 Feb 2023 00:49:58 +0100 Subject: [PATCH 216/520] use playwright-firefox such that `npm install` does not download other browsers --- Dockerfile | 11 ++++++----- README.md | 4 ++-- epic-games.js | 2 +- gog.js | 2 +- package-lock.json | 38 +++++++++++++++++++------------------- package.json | 2 +- prime-gaming.js | 2 +- 7 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9ad9f40..ebad9eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,10 +7,8 @@ FROM ubuntu:jammy # https://github.com/hadolint/hadolint/wiki/DL4006 SHELL ["/bin/bash", "-o", "pipefail", "-c"] ARG DEBIAN_FRONTEND=noninteractive -ENV PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD true -# Install up-to-date node & npm, deps for virtual screen & noVNC, browser, pip for apprise. -# Playwright needs --with-deps for firefox. +# Install up-to-date node & npm, deps for virtual screen & noVNC, firefox, pip for apprise. RUN apt-get update \ && apt-get install --no-install-recommends -y curl ca-certificates \ && curl -fsSL https://deb.nodesource.com/setup_19.x | bash - \ @@ -50,8 +48,11 @@ RUN pip install apprise WORKDIR /fgc COPY package*.json ./ -# If firefox is installed (~/.cache/ms-playwright/firefox-*) before `npm install` it may be a newer version than in package.json and playwright will not find it; system deps are installed sep. via apt above to avoid having to pin the version there. -RUN npm install && npx playwright install firefox +# Playwright installs patched firefox to ~/.cache/ms-playwright/firefox-* +# Requires some system deps to run (see install-deps above). +RUN npm install +# Old: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD + install firefox (had to be done after `npm install` to get the correct version). Now: playwright-firefox as npm dep and `npm install` will only install that. +# RUN npx playwright install firefox COPY . . diff --git a/README.md b/README.md index a76ce74..e002939 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,10 @@ 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 && npx playwright install firefox` +3. Run `npm install` 4. Run `pip install apprise` to install [apprise](https://github.com/caronc/apprise) if you want notifications -This downloads Firefox to a cache in home ([doc](https://playwright.dev/docs/browsers#managing-browser-binaries)). +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`. 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 6983fa4..d9bfb27 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,4 +1,4 @@ -import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra +import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; import path from 'path'; import { existsSync, writeFileSync } from 'fs'; diff --git a/gog.js b/gog.js index 5e10dd1..b0a582c 100644 --- a/gog.js +++ b/gog.js @@ -1,4 +1,4 @@ -import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra +import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import path from 'path'; import { jsonDb, datetime, filenamify, prompt, notify, html_game_list } from './util.js'; import { cfg } from './config.js'; diff --git a/package-lock.json b/package-lock.json index 8cfff98..8fe8c71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "enquirer": "^2.3.6", "lowdb": "^5.1.0", "otplib": "^12.0.1", - "playwright": "^1.31.0", + "playwright-firefox": "^1.31.0", "puppeteer-extra-plugin-stealth": "^2.11.1" } }, @@ -435,14 +435,10 @@ "node": ">=8" } }, - "node_modules/playwright": { + "node_modules/playwright-core": { "version": "1.31.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.31.0.tgz", - "integrity": "sha512-cFn1ie3bdYw/9/Ty3842CfPSRSy+ZWPjEhrxWgC+jL/CUjq5RKVJZwUbXV1UKBQU1Vo0NetsokXmk9EEZiqVHQ==", - "hasInstallScript": true, - "dependencies": { - "playwright-core": "1.31.0" - }, + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.0.tgz", + "integrity": "sha512-/KquBjS5DcASCh8cGeNVHuC0kyb7c9plKTwaKxgOGtxT7+DZO2fjmFvPDBSXslEIK5CeOO/2kk5rOCktFXKEdA==", "bin": { "playwright": "cli.js" }, @@ -450,10 +446,14 @@ "node": ">=14" } }, - "node_modules/playwright-core": { + "node_modules/playwright-firefox": { "version": "1.31.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.0.tgz", - "integrity": "sha512-/KquBjS5DcASCh8cGeNVHuC0kyb7c9plKTwaKxgOGtxT7+DZO2fjmFvPDBSXslEIK5CeOO/2kk5rOCktFXKEdA==", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.31.0.tgz", + "integrity": "sha512-E+v16LzBt6SaSRCLH0ZV8NuikTmbmbh9Ky1JgD5sCoF8OHJ3jEjtuoHAJCkO57PJhMX9q/oZ+x133seUMIsKzA==", + "hasInstallScript": true, + "dependencies": { + "playwright-core": "1.31.0" + }, "bin": { "playwright": "cli.js" }, @@ -1002,19 +1002,19 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, - "playwright": { - "version": "1.31.0", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.31.0.tgz", - "integrity": "sha512-cFn1ie3bdYw/9/Ty3842CfPSRSy+ZWPjEhrxWgC+jL/CUjq5RKVJZwUbXV1UKBQU1Vo0NetsokXmk9EEZiqVHQ==", - "requires": { - "playwright-core": "1.31.0" - } - }, "playwright-core": { "version": "1.31.0", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.0.tgz", "integrity": "sha512-/KquBjS5DcASCh8cGeNVHuC0kyb7c9plKTwaKxgOGtxT7+DZO2fjmFvPDBSXslEIK5CeOO/2kk5rOCktFXKEdA==" }, + "playwright-firefox": { + "version": "1.31.0", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.31.0.tgz", + "integrity": "sha512-E+v16LzBt6SaSRCLH0ZV8NuikTmbmbh9Ky1JgD5sCoF8OHJ3jEjtuoHAJCkO57PJhMX9q/oZ+x133seUMIsKzA==", + "requires": { + "playwright-core": "1.31.0" + } + }, "puppeteer-extra-plugin": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", diff --git a/package.json b/package.json index 2d16b57..f3a0f3e 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "enquirer": "^2.3.6", "lowdb": "^5.1.0", "otplib": "^12.0.1", - "playwright": "^1.31.0", + "playwright-firefox": "^1.31.0", "puppeteer-extra-plugin-stealth": "^2.11.1" }, "repository": { diff --git a/prime-gaming.js b/prime-gaming.js index 6300f41..4075062 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,4 +1,4 @@ -import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra +import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; import path from 'path'; import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list } from './util.js'; From 9df483622197646fe5e19643f3aff85b69a9816e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 22 Feb 2023 00:52:25 +0100 Subject: [PATCH 217/520] gitignore *.env for safety :) --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 902b281..7983ad4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ node_modules/ data/ +*.env From a3892eaafbac4d68b2b213d74f037513fef03040 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 22 Feb 2023 00:59:22 +0100 Subject: [PATCH 218/520] update description & version in package.json --- package-lock.json | 4 ++-- package.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8fe8c71..8dee708 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "free-games-claimer", - "version": "1.0.0", + "version": "1.4.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "free-games-claimer", - "version": "1.0.0", + "version": "1.4.0", "license": "MIT", "dependencies": { "cross-env": "^7.0.3", diff --git a/package.json b/package.json index f3a0f3e..04cf02b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "free-games-claimer", - "version": "1.0.0", - "description": "Claims free games on the Epic Games Store and Amazon Prime Gaming.", + "version": "1.4.0", + "description": "Automatically claims free games on the Epic Games Store, Amazon Prime Gaming and GOG.", "homepage": "https://github.com/vogler/free-games-claimer", "main": "index.js", "scripts": { From 21ed2794f498d1b503351d62ebfada09d24d4e19 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 22 Feb 2023 01:10:34 +0100 Subject: [PATCH 219/520] change default `TIMEOUT` from 20s to 60s, #62 --- README.md | 1 + config.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e002939..1388704 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,7 @@ Available options/variables and their default values: | VNC_PASSWORD | | VNC password for Docker. No password used by default! | | NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. | | BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. | +| TIMEOUT | 60 | Timeout for any page action. Should be fine even on slow machines. | | LOGIN_TIMEOUT | 180 | Timeout for login in seconds. Will wait twice (prompt + manual login). | | EMAIL | | Default email for any login. | | PASSWORD | | Default password for any login. | diff --git a/config.js b/config.js index bdb6bd9..d75ee98 100644 --- a/config.js +++ b/config.js @@ -11,7 +11,7 @@ export const cfg = { get headless() { return !this.debug && !this.show }, width: Number(process.env.WIDTH) || 1280, // width of the opened browser height: Number(process.env.HEIGHT) || 1280, // height of the opened browser - timeout: (Number(process.env.TIMEOUT) || 20) * 1000, // 20s, default for playwright is 30s + timeout: (Number(process.env.TIMEOUT) || 60) * 1000, // default timeout for playwright is 30s login_timeout: (Number(process.env.LOGIN_TIMEOUT) || 180) * 1000, // higher timeout for login, will wait twice: prompt + wait for manual login novnc_port: process.env.NOVNC_PORT, // running in docker if set notify: process.env.NOTIFY, // apprise notification services From 38c5402df040c81f330b706db118a45c3652d6f3 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 22 Feb 2023 10:26:32 +0100 Subject: [PATCH 220/520] exitCode should be 130 instead of 1 on SIGINT https://unix.stackexchange.com/questions/386836/why-is-doing-an-exit-130-is-not-the-same-as-dying-of-sigint --- epic-games.js | 12 ++++-------- gog.js | 12 ++++-------- prime-gaming.js | 12 ++++-------- util.js | 4 ++++ 4 files changed, 16 insertions(+), 24 deletions(-) diff --git a/epic-games.js b/epic-games.js index d9bfb27..812af6b 100644 --- a/epic-games.js +++ b/epic-games.js @@ -2,7 +2,7 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdate import { authenticator } from 'otplib'; import path from 'path'; import { existsSync, writeFileSync } from 'fs'; -import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list } from './util.js'; +import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; @@ -13,11 +13,7 @@ console.log(datetime(), 'started checking epic-games'); const db = await jsonDb('epic-games.json'); db.data ||= {}; -let exit = false; -process.on('SIGINT', () => { // e.g. when killed by Ctrl-C - console.log('\nInterrupted by SIGINT. Exit! Exception shows where the script was:\n'); - exit = true; -}); +handleSIGINT(); // https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 // const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox @@ -193,8 +189,8 @@ try { } } catch (error) { console.error(error); // .toString()? - process.exitCode = 1; - if (error.message && !exit) + process.exitCode ||= 1; + if (error.message && process.exitCode != 130) notify(`epic-games failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db diff --git a/gog.js b/gog.js index b0a582c..4128e89 100644 --- a/gog.js +++ b/gog.js @@ -1,6 +1,6 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import path from 'path'; -import { jsonDb, datetime, filenamify, prompt, notify, html_game_list } from './util.js'; +import { jsonDb, datetime, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; const URL_CLAIM = 'https://www.gog.com/en'; @@ -10,11 +10,7 @@ console.log(datetime(), 'started checking gog'); const db = await jsonDb('gog.json'); db.data ||= {}; -let exit = false; -process.on('SIGINT', () => { // e.g. when killed by Ctrl-C - console.log('\nInterrupted by SIGINT. Exit! Exception shows where the script was:\n'); - exit = true; -}); +handleSIGINT(); // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(cfg.dir.browser, { @@ -138,8 +134,8 @@ try { } } catch (error) { console.error(error); // .toString()? - process.exitCode = 1; - if (error.message && !exit) + process.exitCode ||= 1; + if (error.message && process.exitCode != 130) notify(`gog failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db diff --git a/prime-gaming.js b/prime-gaming.js index 4075062..7405f91 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,7 +1,7 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; import path from 'path'; -import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list } from './util.js'; +import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; // const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? @@ -12,11 +12,7 @@ console.log(datetime(), 'started checking prime-gaming'); const db = await jsonDb('prime-gaming.json'); db.data ||= {}; -let exit = false; -process.on('SIGINT', () => { // e.g. when killed by Ctrl-C - console.log('\nInterrupted by SIGINT. Exit! Exception shows where the script was:\n'); - exit = true; -}); +handleSIGINT(); // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(cfg.dir.browser, { @@ -170,8 +166,8 @@ try { await page.locator(games_sel).screenshot({ path: p }); } catch (error) { console.error(error); // .toString()? - process.exitCode = 1; - if (error.message && !exit) + process.exitCode ||= 1; + if (error.message && process.exitCode != 130) notify(`prime-gaming failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db diff --git a/util.js b/util.js index cd05832..1666a58 100644 --- a/util.js +++ b/util.js @@ -24,6 +24,10 @@ export const datetime = (d = new Date()) => d.toISOString().replace('T', ' ').re export const datetimeLocal = (d = new Date()) => datetime(new Date(d.getTime() - new Date().getTimezoneOffset() * 60000)); export const filenamify = s => s.replaceAll(':', '.').replace(/[^a-z0-9 _\-.]/gi, '_'); // alternative: https://www.npmjs.com/package/filenamify - On Unix-like systems, / is reserved. On Windows, <>:"/\|?* along with trailing periods are reserved. +export const handleSIGINT = () => process.on('SIGINT', () => { // e.g. when killed by Ctrl-C + console.error('\nInterrupted by SIGINT. Exit!'); // Exception shows where the script was:\n'); // killed before catch in docker... + process.exitCode = 130; // 128+SIGINT to indicate to parent that process was killed +}); // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 // gets userAgent and then removes "Headless" from it From fc3f0a63336964c4ac1d8eccc9dbbc0917e1b0fd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 23 Feb 2023 12:38:26 +0100 Subject: [PATCH 221/520] pg: PG_REDEEM for external stores, post your response in #5 Known responses (missing unused key): - GOG: `Invalid or no captcha`, `code_used`, `code_not_found` - microsoft games: `NotFound` --- README.md | 5 ++-- config.js | 3 +++ prime-gaming.js | 61 +++++++++++++++++++++++++++++++++++++++++++++++-- 3 files changed, 65 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1388704..3cbd219 100644 --- a/README.md +++ b/README.md @@ -78,6 +78,7 @@ Available options/variables and their default values: | PG_EMAIL | | Prime Gaming email for login. Overrides EMAIL. | | PG_PASSWORD | | Prime Gaming password for login. Overrides PASSWORD. | | PG_OTPKEY | | Prime Gaming MFA OTP key. | +| PG_REDEEM | 0 | Prime Gaming: try to redeem keys on external stores (experimental). | | GOG_EMAIL | | GOG email for login. Overrides EMAIL. | | GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | @@ -118,8 +119,8 @@ Run `node prime-gaming` (locally or in Docker). Claiming the Amazon Games works out-of-the-box, however, for games on external stores you need to either link your account or redeem a key. -- Stores that require account linking: Epic Games, Battle.net. -- Stores that require redeeming a key: Origin, GOG.com, Microsoft Games, Legacy Games. +- Stores that require account linking: Epic Games, Battle.net, Origin. +- Stores that require redeeming a key: GOG.com, Microsoft Games, Legacy Games. Keys and URLs are printed to the console, included in notifications and saved in `data/prime-gaming.json`. A screenshot of the page with the key is also saved to `data/screenshots`. [TODO](https://github.com/vogler/free-games-claimer/issues/5): ~~redeem keys on external stores.~~ diff --git a/config.js b/config.js index d75ee98..f1e0b10 100644 --- a/config.js +++ b/config.js @@ -33,4 +33,7 @@ export const cfg = { gog_email: process.env.GOG_EMAIL || process.env.EMAIL, gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD, // OTP only via GOG_EMAIL, can't add app... + + // experimmental - likely to change + pg_redeem: process.env.PG_REDEEM, // prime-gaming: redeem keys on external stores }; diff --git a/prime-gaming.js b/prime-gaming.js index 7405f91..344f5f8 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -137,8 +137,8 @@ try { const redeem = { // 'origin': 'https://www.origin.com/redeem', // TODO still needed or now only via account linking? 'gog.com': 'https://www.gog.com/redeem', - 'legacy games': 'https://www.legacygames.com/primedeal', 'microsoft games': 'https://redeem.microsoft.com', + 'legacy games': 'https://www.legacygames.com/primedeal', }; if (store in redeem) { // did not work for linked origin: && !await page.locator('div:has-text("Successfully Claimed")').count() const code = await page.inputValue('input[type="text"]'); @@ -148,7 +148,64 @@ try { } console.log(' URL to redeem game:', redeem[store]); db.data[user][title].code = code; - notify_game.status = `redeem ${code} on ${store}`; + let redeem_action = 'redeem'; + if (cfg.pg_redeem) { // try to redeem keys on external stores + console.log(` Trying to redeem ${code} on ${store} (need to be logged in)!`); + const page2 = await context.newPage(); + await page2.goto(redeem[store], { waitUntil: 'domcontentloaded' }); + if (store == 'gog.com') { + // await page.goto(`https://redeem.gog.com/v1/bonusCodes/${code}`); // {"reason":"Invalid or no captcha"} + await page2.fill('#codeInput', code); + const r = page2.waitForResponse(r => r.url().startsWith('https://redeem.gog.com/')); + await page2.click('[type="submit"]'); + // console.log(await page2.locator('.warning-message').innerText()); + const rt = await (await r).text(); + console.debug(` Response: ${rt}`); + // {"reason":"Invalid or no captcha"} + // {"reason":"code_used"} + // {"reason":"code_not_found"} + const reason = JSON.parse(rt).reason; + if (reason.includes('captcha')) { + redeem_action = 'redeem (got captcha)'; + console.error(' Got captcha; could not redeem!'); + } else if (reason == 'code_used') { + redeem_action = 'already redeemed'; + console.log(' Code was already used!'); + } else if (reason == 'code_not_found') { + redeem_action = 'redeem (not found)'; + console.error(' Code was not found!'); + } else { // TODO not logged in? need valid unused code to test. + redeem_action = 'redeemed?'; + console.log(' Redeemed successfully? Please report your Response from above (if it is new) in https://github.com/vogler/free-games-claimer/issues/5'); + } + await page2.pause(); + await page2.close(); + } else if (store == 'microsoft games') { + console.error(` Redeem on ${store} not yet implemented!`); + if (page2.url().startsWith('https://login.')) { + console.error(' Not logged in! Use the browser to login manually.'); + redeem_action = 'redeem (login)'; + } else { + const r = page2.waitForResponse(r => r.url().startsWith('https://purchase.mp.microsoft.com/')); + await page2.fill('[name=tokenString]', code); + // console.log(await page2.locator('.redeem_code_error').innerText()); + const rt = await (await r).text(); + console.debug(` Response: ${rt}`); + // {"code":"NotFound","data":[],"details":[],"innererror":{"code":"TokenNotFound",... + const reason = JSON.parse(rt).code; + if (reason == 'NotFound') { + redeem_action = 'redeem (not found)'; + console.error(' Code was not found!'); + } else { // TODO find out other responses + redeem_action = 'redeemed?'; + console.log(' Redeemed successfully? Please report your Response from above (if it is new) in https://github.com/vogler/free-games-claimer/issues/5'); + } + } + } else if (store == 'legacy games') { + console.error(` Redeem on ${store} not yet implemented!`); + } + } + notify_game.status = `${redeem_action} ${code} on ${store}`; } else { notify_game.status = `claimed on ${store}`; } From 08b9df7cc46194aeb94161560e3cbd3f8fdb90ab Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 23 Feb 2023 12:51:36 +0100 Subject: [PATCH 222/520] pg: microsoft games: click next, #5 --- prime-gaming.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 344f5f8..c39901f 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -178,8 +178,6 @@ try { redeem_action = 'redeemed?'; console.log(' Redeemed successfully? Please report your Response from above (if it is new) in https://github.com/vogler/free-games-claimer/issues/5'); } - await page2.pause(); - await page2.close(); } else if (store == 'microsoft games') { console.error(` Redeem on ${store} not yet implemented!`); if (page2.url().startsWith('https://login.')) { @@ -197,6 +195,7 @@ try { redeem_action = 'redeem (not found)'; console.error(' Code was not found!'); } else { // TODO find out other responses + await page2.click('#nextButton'); redeem_action = 'redeemed?'; console.log(' Redeemed successfully? Please report your Response from above (if it is new) in https://github.com/vogler/free-games-claimer/issues/5'); } @@ -204,6 +203,8 @@ try { } else if (store == 'legacy games') { console.error(` Redeem on ${store} not yet implemented!`); } + await page2.pause(); + await page2.close(); } notify_game.status = `${redeem_action} ${code} on ${store}`; } else { From 792d2859b3e150b4af6d74c8b1a0ecb088eb3f5f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 23 Feb 2023 19:33:08 +0100 Subject: [PATCH 223/520] mention how to run several scripts in docker via `bash -c`, #73 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 3cbd219..5f88f56 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Easy option: [install Docker](https://docs.docker.com/get-docker/) (or [podman]( ``` docker run --rm -it -p 6080:6080 -v fgc:/fgc/data --pull=always ghcr.io/vogler/free-games-claimer ``` -This will run `node epic-games; node prime-gaming; node gog` - if you only want to claim games for one of the stores, you can override the default command by appending e.g. `node epic-games` at the end of the `docker run` command. +This will run `node epic-games; node prime-gaming; node gog` - if you only want to claim games for one of the stores, you can override the default command by appending e.g. `node epic-games` at the end of the `docker run` command, or if you want several `bash -c "node epic-games.js; node gog.js"`. Data (including json files with claimed games, codes to redeem, screenshots) is stored in the Docker volume `fgc`.
From e73d3d47d77bfd95adc0ee0ee153d9bdbc67f524 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 23 Feb 2023 23:01:57 +0100 Subject: [PATCH 224/520] pg: PG_REDEEM == '1' like for other boolean options --- config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.js b/config.js index f1e0b10..6ae802f 100644 --- a/config.js +++ b/config.js @@ -35,5 +35,5 @@ export const cfg = { // OTP only via GOG_EMAIL, can't add app... // experimmental - likely to change - pg_redeem: process.env.PG_REDEEM, // prime-gaming: redeem keys on external stores + pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores }; From 114631da4d2cc13d44caf8ea41a29745c9bf167d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 23 Feb 2023 23:34:27 +0100 Subject: [PATCH 225/520] add NOTIFY_TITLE - Optional title for notifications, e.g. Pushover, #69 --- README.md | 1 + config.js | 1 + util.js | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 5f88f56..9b101cb 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,7 @@ Available options/variables and their default values: | HEIGHT | 1280 | Height of the opened browser (and of screen for VNC in Docker). | | VNC_PASSWORD | | VNC password for Docker. No password used by default! | | NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. | +| NOTIFY_TITLE | | Optional title for notifications, e.g. for Pushover. | | BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. | | TIMEOUT | 60 | Timeout for any page action. Should be fine even on slow machines. | | LOGIN_TIMEOUT | 180 | Timeout for login in seconds. Will wait twice (prompt + manual login). | diff --git a/config.js b/config.js index 6ae802f..e9cf1c1 100644 --- a/config.js +++ b/config.js @@ -15,6 +15,7 @@ export const cfg = { login_timeout: (Number(process.env.LOGIN_TIMEOUT) || 180) * 1000, // higher timeout for login, will wait twice: prompt + wait for manual login novnc_port: process.env.NOVNC_PORT, // running in docker if set notify: process.env.NOTIFY, // apprise notification services + notify_title: process.env.NOTIFY_TITLE, // apprise notification title get dir() { // avoids ReferenceError: Cannot access 'dataDir' before initialization return { browser: process.env.BROWSER_DIR || dataDir('browser'), // for multiple accounts or testing diff --git a/util.js b/util.js index 1666a58..a1d6d40 100644 --- a/util.js +++ b/util.js @@ -101,7 +101,8 @@ import { cfg } from './config.js'; export const notify = (html) => { if (!cfg.notify) return; - exec(`apprise ${cfg.notify} -i html -b '${html}'`, (error, stdout, stderr) => { + const title = cfg.notify_title ? `-t ${cfg.notify_title}` : ''; + exec(`apprise ${cfg.notify} -i html ${title} -b '${html}'`, (error, stdout, stderr) => { if (error) { console.log(`error: ${error.message}`); if (error.message.includes('command not found')) { From 73a7cffd475b4b82986f35d0a01aa1c8a2150702 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 24 Feb 2023 00:08:35 +0100 Subject: [PATCH 226/520] await notify before process.exit, #69 --- epic-games.js | 2 +- gog.js | 2 +- prime-gaming.js | 4 ++-- util.js | 9 +++++---- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/epic-games.js b/epic-games.js index 812af6b..a0a15dd 100644 --- a/epic-games.js +++ b/epic-games.js @@ -84,7 +84,7 @@ try { }).catch(_ => { }); } else { console.log('Waiting for you to login in the browser.'); - notify('epic-games: no longer signed in and not enough options set for automatic login.'); + await notify('epic-games: no longer signed in and not enough options set for automatic login.'); if (cfg.headless) { console.log('Run `SHOW=1 node epic-games` to login in the opened browser.'); await context.close(); // finishes potential recording diff --git a/gog.js b/gog.js index 4128e89..8fff1ce 100644 --- a/gog.js +++ b/gog.js @@ -72,7 +72,7 @@ try { await page.waitForSelector('#menuUsername') } else { console.log('Waiting for you to login in the browser.'); - notify('gog: no longer signed in and not enough options set for automatic login.'); + await notify('gog: no longer signed in and not enough options set for automatic login.'); if (cfg.headless) { console.log('Run `SHOW=1 node gog` to login in the opened browser.'); await context.close(); diff --git a/prime-gaming.js b/prime-gaming.js index c39901f..0ef98c8 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -54,7 +54,7 @@ try { const error = await page.locator('.a-alert-content').first().innerText(); if (!error.trim.length) return; console.error('Login error:', error); - notify(`prime-gaming: login: ${error}`); + await notify(`prime-gaming: login: ${error}`); await context.close(); // finishes potential recording process.exit(1); }); @@ -68,7 +68,7 @@ try { }).catch(_ => { }); } else { console.log('Waiting for you to login in the browser.'); - notify('prime-gaming: no longer signed in and not enough options set for automatic login.'); + await notify('prime-gaming: no longer signed in and not enough options set for automatic login.'); if (cfg.headless) { console.log('Run `SHOW=1 node prime-gaming` to login in the opened browser.'); await context.close(); // finishes potential recording diff --git a/util.js b/util.js index a1d6d40..08b55d1 100644 --- a/util.js +++ b/util.js @@ -99,8 +99,8 @@ export const prompt = o => enquirer.prompt({name: 'name', type: 'input', message import { exec } from 'child_process'; import { cfg } from './config.js'; -export const notify = (html) => { - if (!cfg.notify) return; +export const notify = (html) => new Promise((resolve, reject) => { + if (!cfg.notify) return resolve(); const title = cfg.notify_title ? `-t ${cfg.notify_title}` : ''; exec(`apprise ${cfg.notify} -i html ${title} -b '${html}'`, (error, stdout, stderr) => { if (error) { @@ -108,12 +108,13 @@ export const notify = (html) => { if (error.message.includes('command not found')) { console.info('Run `pip install apprise`. See https://github.com/vogler/free-games-claimer#notifications'); } - return; + return resolve(); } if (stderr) console.error(`stderr: ${stderr}`); if (stdout) console.log(`stdout: ${stdout}`); + resolve(); }); -} +}); export const escapeHtml = (unsafe) => unsafe.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); From dc28c30e7b73f31fd6d504b77730eda8bf4a9827 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 24 Feb 2023 12:28:56 +0100 Subject: [PATCH 227/520] pg: make `DRYRUN=1` terminate for external games, TODO only shows first game --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 0ef98c8..053fd9b 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -118,7 +118,7 @@ try { if (!card) break; const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); - if (cfg.dryrun) continue; + if (cfg.dryrun) break; // TODO change back to continue, but need different iteration scheme await (await card.$('text=Claim')).click(); // goes to URL of game, no need to wait await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); From cc39b4b3d1b7e6a05edc7fc64c07e4601b71e611 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 24 Feb 2023 12:31:42 +0100 Subject: [PATCH 228/520] pg: skip if user has no Prime membership, closes #76? --- prime-gaming.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/prime-gaming.js b/prime-gaming.js index 053fd9b..733b6ba 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -85,6 +85,12 @@ try { // console.log(`Twitch user name is ${twitch}`); db.data[user] ||= {}; + if (await page.getByRole('button', { name: 'Try Prime' }).count()) { + console.error('User is currently not an Amazon Prime member, so no games to claim. Exit!'); + await context.close(); + process.exit(1); + } + await page.click('button[data-type="Game"]'); const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; await page.waitForSelector(games_sel); From 862fd20d5bf375d7f9f7313dba04ba58430ea288 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 24 Feb 2023 13:21:55 +0100 Subject: [PATCH 229/520] mention open issue with Enquirer: must cancel prompt with Escape instead of Ctrl-C --- util.js | 1 + 1 file changed, 1 insertion(+) diff --git a/util.js b/util.js index 08b55d1..b45fdb7 100644 --- a/util.js +++ b/util.js @@ -83,6 +83,7 @@ export const stealth = async (context) => { // used prompts before, but couldn't cancel prompt // alternative inquirer is big (node_modules 29MB, enquirer 9.7MB, prompts 9.8MB, none 9.4MB) and slower +// open issue: prevents handleSIGINT() to work if prompt is cancelled with Ctrl-C instead of Escape: https://github.com/enquirer/enquirer/issues/372 import Enquirer from 'enquirer'; const enquirer = new Enquirer(); const timeoutPlugin = timeout => enquirer => { // cancel prompt after timeout ms enquirer.on('prompt', prompt => { From 0d2ff0c8a990c9b471cf9a1fbd8eeb1197681d21 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 24 Feb 2023 19:57:57 +0100 Subject: [PATCH 230/520] pg: add status to db for external games: claimed, claimed and redeemed, failed --- prime-gaming.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 733b6ba..0233ebb 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -134,10 +134,12 @@ try { console.log(' External store:', store); const url = page.url().split('?')[0]; db.data[user][title] ||= { title, time: datetime(), url, store }; - const notify_game = { title, url, status: `failed - link ${store}` }; + const notify_game = { title, url }; notify_games.push(notify_game); // status is updated below if (await page.locator('div:has-text("Link game account")').count()) { console.error(' Account linking is required to claim this offer!'); + notify_game.status = `failed: need account linking for ${store}`; + db.data[user][title].status = 'failed: need account linking'; } else { // print code if there is one const redeem = { @@ -183,6 +185,7 @@ try { } else { // TODO not logged in? need valid unused code to test. redeem_action = 'redeemed?'; console.log(' Redeemed successfully? Please report your Response from above (if it is new) in https://github.com/vogler/free-games-claimer/issues/5'); + // db.data[user][title].status = 'claimed and redeemed'; } } else if (store == 'microsoft games') { console.error(` Redeem on ${store} not yet implemented!`); @@ -204,6 +207,7 @@ try { await page2.click('#nextButton'); redeem_action = 'redeemed?'; console.log(' Redeemed successfully? Please report your Response from above (if it is new) in https://github.com/vogler/free-games-claimer/issues/5'); + // db.data[user][title].status = 'claimed and redeemed'; } } } else if (store == 'legacy games') { @@ -215,6 +219,7 @@ try { notify_game.status = `${redeem_action} ${code} on ${store}`; } else { notify_game.status = `claimed on ${store}`; + db.data[user][title].status = 'claimed'; } // save screenshot of potential code just in case const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', 'external', `${filenamify(title)}.png`); From 944cca671564bcd859d6a07ac421c03f6628b741 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 24 Feb 2023 20:56:25 +0100 Subject: [PATCH 231/520] eg: add click delay, fixes #75 Playwright triggered the click such that the purchase frame opened, but did not resolve the promise. Had to move mouse into the browser for it to continue. Adding a click delay of 1ms also worked (default is no delay between mouse down and up). Went for 11ms. A typical click is probably 100-200ms. --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index a0a15dd..c25f88a 100644 --- a/epic-games.js +++ b/epic-games.js @@ -133,7 +133,7 @@ try { if (db.data[user][game_id].status == 'failed') db.data[user][game_id].status = 'manual'; // was failed but now it's claimed } else { // GET console.log(' Not in library yet! Click GET.'); - await page.click('[data-testid="purchase-cta-button"]'); + await page.click('[data-testid="purchase-cta-button"]', { delay: 11 }); // got stuck here without delay (or mouse move), see #75, 1ms was also enough // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoided by Windows userAgent? page.click('button:has-text("Continue")').catch(_ => { }); // needed since change from Chromium to Firefox? From a8fa80cd0554f80f483a23a2621dd8d509c6ebff Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 6 Mar 2023 21:08:24 +0100 Subject: [PATCH 232/520] eg: fix #84 like #75 with click delay >0ms --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index c25f88a..828d85b 100644 --- a/epic-games.js +++ b/epic-games.js @@ -150,7 +150,7 @@ try { db.data[user][game_id].status = notify_game.status = 'unavailable-in-region'; continue; } - await iframe.locator('button:has-text("Place Order")').click(); + await iframe.locator('button:has-text("Place Order")').click({ delay: 11 }); // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 const btnAgree = iframe.locator('button:has-text("I Agree")'); From e45c3a5dca31f24473549efc7e7d7939fccfe0b9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 6 Mar 2023 21:08:57 +0100 Subject: [PATCH 233/520] eg: no captcha -> no special timeout for solving one --- epic-games.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index 828d85b..58fb1d6 100644 --- a/epic-games.js +++ b/epic-games.js @@ -155,7 +155,7 @@ try { // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 const btnAgree = iframe.locator('button:has-text("I Agree")'); try { - context.setDefaultTimeout(100 * 1000); // give time to solve captcha, iframe goes blank after 60s? + // context.setDefaultTimeout(100 * 1000); // give time to solve captcha, iframe goes blank after 60s? await Promise.any([btnAgree.click(), page.waitForSelector('text=Thank you for buying').then(_ => { })]); // EU: wait for agree button, non-EU: potentially done const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); @@ -172,7 +172,7 @@ try { db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time console.log(' Claimed successfully!'); - context.setDefaultTimeout(cfg.timeout); + // context.setDefaultTimeout(cfg.timeout); } catch (e) { console.log(e); // console.error(' Failed to claim! Try again if NopeCHA timed out. Click the extension to see if you ran out of credits (refill after 24h). To avoid captchas try to get a new IP or set a cookie from https://www.hcaptcha.com/accessibility'); From cbdea1b5d0d207a2d61539ef74b522df3cb2142e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 6 Mar 2023 21:15:42 +0100 Subject: [PATCH 234/520] add username to notification of claimed games, closes #88 --- epic-games.js | 5 +++-- gog.js | 5 +++-- prime-gaming.js | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/epic-games.js b/epic-games.js index 58fb1d6..e6ea28d 100644 --- a/epic-games.js +++ b/epic-games.js @@ -47,6 +47,7 @@ const page = context.pages().length ? context.pages()[0] : await context.newPage // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); const notify_games = []; +let user; try { await context.addCookies([{name: 'OptanonAlertBoxClosed', value: new Date(Date.now() - 5*24*60*60*1000).toISOString(), domain: '.epicgames.com', path: '/'}]); // Accept cookies to get rid of banner to save space on screen. Set accept time to 5 days ago. @@ -94,7 +95,7 @@ try { await page.waitForURL(URL_CLAIM); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } - const user = await page.locator('#user span').first().innerHTML(); + user = await page.locator('#user span').first().innerHTML(); console.log(`Signed in as ${user}`); db.data[user] ||= {}; @@ -195,7 +196,7 @@ try { } finally { await db.write(); // write out json db if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed - notify(`epic-games:
${html_game_list(notify_games)}`); + notify(`epic-games (${user}):
${html_game_list(notify_games)}`); } } if (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); diff --git a/gog.js b/gog.js index 8fff1ce..a879028 100644 --- a/gog.js +++ b/gog.js @@ -27,6 +27,7 @@ const page = context.pages().length ? context.pages()[0] : await context.newPage // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); const notify_games = []; +let user; try { await context.addCookies([{name: 'CookieConsent', value: '{stamp:%274oR8MJL+bxVlG6g+kl2we5+suMJ+Tv7I4C5d4k+YY4vrnhCD+P23RQ==%27%2Cnecessary:true%2Cpreferences:true%2Cstatistics:true%2Cmarketing:true%2Cmethod:%27explicit%27%2Cver:1%2Cutc:1672331618201%2Cregion:%27de%27}', domain: 'www.gog.com', path: '/'}]); // to not waste screen space when non-headless @@ -82,7 +83,7 @@ try { await page.waitForSelector('#menuUsername'); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } - const user = await page.locator('#menuUsername').first().textContent(); // innerText is uppercase due to styling! + user = await page.locator('#menuUsername').first().textContent(); // innerText is uppercase due to styling! console.log(`Signed in as '${user}'`); db.data[user] ||= {}; @@ -140,7 +141,7 @@ try { } finally { await db.write(); // write out json db if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed - notify(`gog:
${html_game_list(notify_games)}`); + notify(`gog (${user}):
${html_game_list(notify_games)}`); } } await context.close(); diff --git a/prime-gaming.js b/prime-gaming.js index 0233ebb..dddf912 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -30,6 +30,7 @@ const page = context.pages().length ? context.pages()[0] : await context.newPage // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); const notify_games = []; +let user; try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever @@ -78,7 +79,7 @@ try { await page.waitForURL('https://gaming.amazon.com/home?signedIn=true'); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } - const user = await page.locator('[data-a-target="user-dropdown-first-name-text"]').first().innerText(); + user = await page.locator('[data-a-target="user-dropdown-first-name-text"]').first().innerText(); console.log(`Signed in as ${user}`); // await page.click('button[aria-label="User dropdown and more options"]'); // const twitch = await page.locator('[data-a-target="TwitchDisplayName"]').first().innerText(); @@ -241,7 +242,7 @@ try { } finally { await db.write(); // write out json db if (notify_games.length) { // list should only include claimed games - notify(`prime-gaming:
${html_game_list(notify_games)}`); + notify(`prime-gaming (${user}):
${html_game_list(notify_games)}`); } } await context.close(); From a5f42a960918b313aa972a971555aa06641b8dfe Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 6 Mar 2023 22:13:58 +0100 Subject: [PATCH 235/520] sample `command:` and `environment:` in docker-compose.yml, #85 --- docker-compose.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docker-compose.yml b/docker-compose.yml index 7c16500..dbcc679 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,3 +9,7 @@ services: - "6080:6080" # noVNC (browser-based VNC client) volumes: - fgc:/fgc/data + # command: bash -c "node epic-games; node gog" + environment: + # - EMAIL=foo@bar.org + # - NOTIFY='tgram://...' From b51547a376c0862fb19de59b1d78c50a3f250995 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 6 Mar 2023 22:32:08 +0100 Subject: [PATCH 236/520] pg: experimental `PG_CLAIMDLC`, WIP #55 --- config.js | 1 + prime-gaming.js | 50 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index e9cf1c1..3cb8f4e 100644 --- a/config.js +++ b/config.js @@ -37,4 +37,5 @@ export const cfg = { // experimmental - likely to change pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores + pg_claimdlc: process.env.PG_CLAIMDLC == '1', // prime-gaming: claim in-game content }; diff --git a/prime-gaming.js b/prime-gaming.js index dddf912..b2edb53 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -233,7 +233,55 @@ try { } while (n); const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); // await page.screenshot({ path: p, fullPage: true }); - await page.locator(games_sel).screenshot({ path: p }); + if (!cfg.dryrun) await page.locator(games_sel).screenshot({ path: p }); + + if (cfg.pg_claimdlc) { + console.log('Trying to claim in-game content...'); + await page.click('button[data-type="InGameLoot"]'); + const loot_sel = 'div[data-a-target="offer-list-IN_GAME_LOOT"]'; + await page.waitForSelector(loot_sel); + console.log('Number of already claimed DLC:', await page.locator(`${loot_sel} p:has-text("Collected")`).count()); + + const cards = await page.locator(`${loot_sel} [data-a-target="item-card"]:has(p:text-is("Claim"))`).all(); + console.log('Number of unclaimed DLC:', cards.length); + const dlcs = await Promise.all(cards.map(async card => ({ + game: await card.locator('.item-card-details__body p').innerText(), + title: await card.locator('.item-card-details__body__primary').innerText(), + url: 'https://gaming.amazon.com' + await card.locator('a').getAttribute('href'), + }))); + // console.log(dlcs); + + for (const dlc of dlcs) { + const title = `${dlc.game} - ${dlc.title}`; + const url = dlc.url; + console.log('Current DLC:', title); + // if (cfg.dryrun) continue; + db.data[user][title] ||= { title, time: datetime(), store: 'DLC', status: 'failed: need account linking' }; + const notify_game = { title, url }; + notify_games.push(notify_game); // status is updated below + await page.goto(url, { waitUntil: 'domcontentloaded' }); + // most games have a button 'Get in-game content' + // epic-games: Fall Guys: Claim now -> Continue -> Go to Epic Games (despite account linked and logged into epic-games) -> not tied to account but via some cookie? + await Promise.any([page.click('button:has-text("Get in-game content")'), page.click('button:has-text("Claim your gift")'), page.click('button:has-text("Claim now")').then(() => page.click('button:has-text("Continue")'))]); + page.click('button:has-text("Continue")').catch(_ => { }); + const linkAccountModal = page.locator('[data-a-target="LinkAccountModal"]'); + const linkAccountButton = linkAccountModal.locator('[data-a-target="LinkAccountButton"]'); + if (await linkAccountButton.count()) { + console.error(' Missing account linking:', await linkAccountButton.innerText()); + } else if(await page.locator('text=Link game account').count()) { // epic-games only? + console.error(' Missing account linking:', await page.locator('button[data-a-target="gms-cta"]').innerText()); + } else { + const code = await page.inputValue('input[type="text"]'); + console.log(' Code to redeem game:', code); + db.data[user][title].code = code; + db.data[user][title].status = 'claimed'; + // notify_game.status = `${redeem_action} ${code} on ${store}`; + } + // await page.pause(); + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); + await page.click('button[data-type="InGameLoot"]'); + } + } } catch (error) { console.error(error); // .toString()? process.exitCode ||= 1; From 07ce17f8e3d42869a7798cde81bb0e8f443f329c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 6 Mar 2023 22:35:37 +0100 Subject: [PATCH 237/520] link issues for experimental options `PG_REDEEM`, `PG_CLAIMDLC` --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b101cb..06ca07e 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,8 @@ Available options/variables and their default values: | PG_EMAIL | | Prime Gaming email for login. Overrides EMAIL. | | PG_PASSWORD | | Prime Gaming password for login. Overrides PASSWORD. | | PG_OTPKEY | | Prime Gaming MFA OTP key. | -| PG_REDEEM | 0 | Prime Gaming: try to redeem keys on external stores (experimental). | +| PG_REDEEM | 0 | Prime Gaming: try to redeem keys on external stores ([experimental](https://github.com/vogler/free-games-claimer/issues/5)). | +| PG_CLAIMDLC | 0 | Prime Gaming: try to claim DLCs ([experimental](https://github.com/vogler/free-games-claimer/issues/55)). | | GOG_EMAIL | | GOG email for login. Overrides EMAIL. | | GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | From 9ab44bd7aa29208f33d3a86e4b2371b625000d9e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 8 Mar 2023 16:41:06 +0100 Subject: [PATCH 238/520] gog: fix indent --- gog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gog.js b/gog.js index a879028..91d812a 100644 --- a/gog.js +++ b/gog.js @@ -114,7 +114,7 @@ try { if (response == '{}') { status = 'claimed'; console.log(' Claimed successfully!'); - } else { + } else { const message = JSON.parse(response).message; if (message == 'Already claimed') { status = 'existed'; // same status text as for epic-games From 74bd2f538acd711860eff8184e3522216aeb8b33 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 8 Mar 2023 16:41:31 +0100 Subject: [PATCH 239/520] gog: newsletter is not subscribed again if game already existed --- gog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gog.js b/gog.js index 91d812a..7a33b09 100644 --- a/gog.js +++ b/gog.js @@ -127,7 +127,7 @@ try { db.data[user][title].status ||= status; notify_games.push({ title, url, status }); - if (status == 'claimed') { // TODO check if this is enough or if newsleter is enabled if 'existed' + if (status == 'claimed') { console.log("Unsubscribe from 'Promotions and hot deals' newsletter"); await page.goto('https://www.gog.com/en/account/settings/subscriptions'); await page.locator('li:has-text("Promotions and hot deals") label').uncheck(); From 7520bf197696be2fff3c3a853d400d558e1a2860 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 8 Mar 2023 16:54:10 +0100 Subject: [PATCH 240/520] gog: screenshot: wait for not is-loading --- gog.js | 1 + 1 file changed, 1 insertion(+) diff --git a/gog.js b/gog.js index 7a33b09..feffa70 100644 --- a/gog.js +++ b/gog.js @@ -98,6 +98,7 @@ try { console.log(`Current free game: ${title} - ${url}`); db.data[user][title] ||= { title, time: datetime(), url }; if (cfg.dryrun) process.exit(1); + await page.locator('#giveaway:not(.is-loading)').waitFor(); // otherwise screenshot is sometimes with loading indicator instead of game title const p = path.resolve(cfg.dir.screenshots, 'gog', `${filenamify(title)}.png`); await banner.screenshot({ path: p }); // overwrites every time - only keep first? From c48c80717af2b161f6916735052d7765cd22c914 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 8 Mar 2023 17:46:03 +0100 Subject: [PATCH 241/520] pg: only screenshot if claimed, incr. height to fit all games, fixes #82 --- prime-gaming.js | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index b2edb53..2fc1c72 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -106,14 +106,13 @@ try { const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); if (cfg.dryrun) continue; + await (await card.$('button:has-text("Claim game")')).click(); + db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; + notify_games.push({ title, status: 'claimed', url: URL_CLAIM }); // const img = await (await card.$('img.tw-image')).getAttribute('src'); // console.log('Image:', img); const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); await card.screenshot({ path: p }); - await (await card.$('button:has-text("Claim game")')).click(); - db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; - notify_games.push({ title, status: 'claimed', url: URL_CLAIM }); - // await page.pause(); } // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft let n; @@ -232,8 +231,12 @@ try { await page.click('button[data-type="Game"]'); } while (n); const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); - // await page.screenshot({ path: p, fullPage: true }); - if (!cfg.dryrun) await page.locator(games_sel).screenshot({ path: p }); + // await page.screenshot({ path: p, fullPage: true }); // fullPage does not make a difference since scroll not on body but on some element + await page.keyboard.press('End'); // scroll to bottom to show all games + await page.waitForTimeout(1000); // wait for fade in animation + const viewportSize = page.viewportSize(); // current viewport size + await page.setViewportSize({...viewportSize, height: 3000}); // increase height, otherwise element screenshot is cut off at the top and bottom + if (notify_games.length) await page.locator(games_sel).screenshot({ path: p }); // screenshot of all claimed games if (cfg.pg_claimdlc) { console.log('Trying to claim in-game content...'); From 0cfb9d29aa686d6dcc9b8179258ff4647af0e782 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 9 Mar 2023 16:21:19 +0100 Subject: [PATCH 242/520] eg: Accept End User License Agreement (only needed once per account) --- epic-games.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index e6ea28d..f6b9d45 100644 --- a/epic-games.js +++ b/epic-games.js @@ -139,8 +139,12 @@ try { // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoided by Windows userAgent? page.click('button:has-text("Continue")').catch(_ => { }); // needed since change from Chromium to Firefox? - if (cfg.dryrun) continue; - if (cfg.debug) await page.pause(); + // Accept End User License Agreement (only needed once) + page.locator('input#agree').waitFor().then(async () => { + console.log('Accept End User License Agreement (only needed once)'); + await page.locator('input#agree').check(); + await page.locator('button:has-text("Accept")').click(); + }).catch(_ => { }); // it then creates an iframe for the purchase await page.waitForSelector('#webPurchaseContainer iframe'); // TODO needed? @@ -151,6 +155,10 @@ try { db.data[user][game_id].status = notify_game.status = 'unavailable-in-region'; continue; } + + if (cfg.dryrun) continue; + if (cfg.debug) await page.pause(); + await iframe.locator('button:has-text("Place Order")').click({ delay: 11 }); // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 From 9df80fa6d8ef49aa84aac2f900d5d8c115d1abcf Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 10 Mar 2023 09:16:54 +0100 Subject: [PATCH 243/520] eg: wait 2s before 'Place Order', fix #84 for everyone? --- epic-games.js | 1 + 1 file changed, 1 insertion(+) diff --git a/epic-games.js b/epic-games.js index f6b9d45..05cae05 100644 --- a/epic-games.js +++ b/epic-games.js @@ -159,6 +159,7 @@ try { if (cfg.dryrun) continue; if (cfg.debug) await page.pause(); + await page.waitForTimeout(2000); await iframe.locator('button:has-text("Place Order")').click({ delay: 11 }); // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 From 4f1ca53d1b8936b62e720ae44ef5071724dbd482 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 16 Mar 2023 15:54:25 +0100 Subject: [PATCH 244/520] eg: fix waiting for captcha for non-EU accounts https://github.com/vogler/free-games-claimer/issues/84#issuecomment-1462993079 --- epic-games.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index 05cae05..4aef571 100644 --- a/epic-games.js +++ b/epic-games.js @@ -164,10 +164,9 @@ try { // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 const btnAgree = iframe.locator('button:has-text("I Agree")'); + btnAgree.waitFor().then(() => btnAgree.click()).catch(_ => { }); // EU: wait for and click 'I Agree' try { // context.setDefaultTimeout(100 * 1000); // give time to solve captcha, iframe goes blank after 60s? - await Promise.any([btnAgree.click(), page.waitForSelector('text=Thank you for buying').then(_ => { })]); // EU: wait for agree button, non-EU: potentially done - const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); captcha.waitFor().then(async () => { // don't await, since element may not be shown // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') @@ -178,7 +177,7 @@ try { // console.info(' Saved a screenshot of hcaptcha challenge to', p); // console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? }).catch(_ => { }); // may time out if not shown - await page.waitForSelector('text=Thank you for buying'); // EU: wait, non-EU: wait again = no-op + await page.waitForSelector('text=Thank you for buying'); db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time console.log(' Claimed successfully!'); From 9435ff6edbe51ce60ba9bc2452f01bbdf0de4e47 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 17 Mar 2023 10:19:34 +0100 Subject: [PATCH 245/520] fix #97: NOTIFY_TITLE in quotes, otherwise fails on spaces --- util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/util.js b/util.js index b45fdb7..839e5c8 100644 --- a/util.js +++ b/util.js @@ -103,7 +103,7 @@ import { cfg } from './config.js'; export const notify = (html) => new Promise((resolve, reject) => { if (!cfg.notify) return resolve(); const title = cfg.notify_title ? `-t ${cfg.notify_title}` : ''; - exec(`apprise ${cfg.notify} -i html ${title} -b '${html}'`, (error, stdout, stderr) => { + exec(`apprise ${cfg.notify} -i html '${title}' -b '${html}'`, (error, stdout, stderr) => { if (error) { console.log(`error: ${error.message}`); if (error.message.includes('command not found')) { From b75f538d8a358e9ee04aef7317712363e8c12935 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 17 Mar 2023 11:19:38 +0100 Subject: [PATCH 246/520] eg: add `EG_PARENTALPIN` to enter Parental Controls PIN, #98 --- README.md | 1 + config.js | 1 + epic-games.js | 11 ++++++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 06ca07e..e845c62 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Available options/variables and their default values: | EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | | EG_PASSWORD | | Epic Games password for login. Overrides PASSWORD. | | EG_OTPKEY | | Epic Games MFA OTP key. | +| EG_PARENTALPIN | | Epic Games Parental Controls PIN. | | PG_EMAIL | | Prime Gaming email for login. Overrides EMAIL. | | PG_PASSWORD | | Prime Gaming password for login. Overrides PASSWORD. | | PG_OTPKEY | | Prime Gaming MFA OTP key. | diff --git a/config.js b/config.js index 3cb8f4e..c667040 100644 --- a/config.js +++ b/config.js @@ -26,6 +26,7 @@ export const cfg = { eg_email: process.env.EG_EMAIL || process.env.EMAIL, eg_password: process.env.EG_PASSWORD || process.env.PASSWORD, eg_otpkey: process.env.EG_OTPKEY, + eg_parentalpin: process.env.EG_PARENTALPIN, // auth prime-gaming pg_email: process.env.PG_EMAIL || process.env.EMAIL, pg_password: process.env.PG_PASSWORD || process.env.PASSWORD, diff --git a/epic-games.js b/epic-games.js index 4aef571..cccb083 100644 --- a/epic-games.js +++ b/epic-games.js @@ -156,8 +156,17 @@ try { continue; } - if (cfg.dryrun) continue; + iframe.locator('.payment-pin-code').waitFor().then(async () => { + if (!cfg.eg_parentalpin) { + console.error(' EG_PARENTALPIN not set. Need to enter Parental Control PIN manually.'); + notify('epic-games: EG_PARENTALPIN not set. Need to enter Parental Control PIN manually.'); + } + await iframe.locator('input.payment-pin-code__input').first().type(cfg.eg_parentalpin); + await iframe.locator('button:has-text("Continue")').click({ delay: 11 }); + }).catch(_ => { }); + if (cfg.debug) await page.pause(); + if (cfg.dryrun) continue; await page.waitForTimeout(2000); await iframe.locator('button:has-text("Place Order")').click({ delay: 11 }); From b7d5d43078cb8b4122d235d56348a92302774e72 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 17 Mar 2023 20:59:55 +0100 Subject: [PATCH 247/520] eg: wait for "Place Order" button to not be loading, fixes #84 --- epic-games.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index cccb083..366bc67 100644 --- a/epic-games.js +++ b/epic-games.js @@ -168,8 +168,8 @@ try { if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; - await page.waitForTimeout(2000); - await iframe.locator('button:has-text("Place Order")').click({ delay: 11 }); + // Playwright clicked before button was ready to handle event, https://github.com/vogler/free-games-claimer/issues/84#issuecomment-1474346591 + await iframe.locator('button:has-text("Place Order"):not(:has(.payment-loading--loading))').click({ delay: 11 }); // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 const btnAgree = iframe.locator('button:has-text("I Agree")'); From 352ee753c9523277697985ef8c1b88850044dd91 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 23 Mar 2023 19:53:10 +0100 Subject: [PATCH 248/520] eg: skip if 'requires base game', fix #106 --- epic-games.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index 366bc67..c8ceef1 100644 --- a/epic-games.js +++ b/epic-games.js @@ -131,7 +131,11 @@ try { console.log(' Already in library! Nothing to claim.'); notify_game.status = 'existed'; db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed - if (db.data[user][game_id].status == 'failed') db.data[user][game_id].status = 'manual'; // was failed but now it's claimed + if (db.data[user][game_id].status.startsWith('failed')) db.data[user][game_id].status = 'manual'; // was failed but now it's claimed + } else if (btnText.toLowerCase() == 'requires base game') { + console.log(' Requires base game! Nothing to claim.'); + notify_game.status = 'requires base game'; + db.data[user][game_id].status ||= 'failed:requires-base-game'; } else { // GET console.log(' Not in library yet! Click GET.'); await page.click('[data-testid="purchase-cta-button"]', { delay: 11 }); // got stuck here without delay (or mouse move), see #75, 1ms was also enough @@ -212,7 +216,7 @@ try { notify(`epic-games failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db - if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed + if (notify_games.filter(g => g.status != 'existed' && g.status != 'failed:requires-base-game').length) { // don't notify if all were already claimed notify(`epic-games (${user}):
${html_game_list(notify_games)}`); } } From 9285af254490ad5486261127ca283e2abb1f04f7 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 30 Mar 2023 14:06:57 +0200 Subject: [PATCH 249/520] eg: notify_games filter status 'requires base game', fixes #112 fix 352ee753c9523277697985ef8c1b88850044dd91 --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index c8ceef1..0a6cad6 100644 --- a/epic-games.js +++ b/epic-games.js @@ -216,7 +216,7 @@ try { notify(`epic-games failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db - if (notify_games.filter(g => g.status != 'existed' && g.status != 'failed:requires-base-game').length) { // don't notify if all were already claimed + if (notify_games.filter(g => g.status != 'existed' && g.status != 'requires base game').length) { // don't notify if all were already claimed notify(`epic-games (${user}):
${html_game_list(notify_games)}`); } } From db5a98eb8832ee52f02500472784638378acb006 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 30 Mar 2023 14:17:26 +0200 Subject: [PATCH 250/520] gog: set GOG_NEWSLETTER=1 to not unsubscribe newsletter, closes #109 --- README.md | 1 + config.js | 1 + gog.js | 2 +- 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e845c62..af2a19b 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,7 @@ Available options/variables and their default values: | PG_CLAIMDLC | 0 | Prime Gaming: try to claim DLCs ([experimental](https://github.com/vogler/free-games-claimer/issues/55)). | | GOG_EMAIL | | GOG email for login. Overrides EMAIL. | | GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | +| GOG_NEWSLETTER | 0 | Do not unsubscribe from newsletter after claiming a game if 1. | See `config.js` for all options. diff --git a/config.js b/config.js index c667040..33016ca 100644 --- a/config.js +++ b/config.js @@ -34,6 +34,7 @@ export const cfg = { // auth gog gog_email: process.env.GOG_EMAIL || process.env.EMAIL, gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD, + gog_newsletter: process.env.GOG_NEWSLETTER == '1', // do not unsubscribe from newsletter after claiming a game // OTP only via GOG_EMAIL, can't add app... // experimmental - likely to change diff --git a/gog.js b/gog.js index feffa70..4e27cc2 100644 --- a/gog.js +++ b/gog.js @@ -128,7 +128,7 @@ try { db.data[user][title].status ||= status; notify_games.push({ title, url, status }); - if (status == 'claimed') { + if (status == 'claimed' && !cfg.gog_newsletter) { console.log("Unsubscribe from 'Promotions and hot deals' newsletter"); await page.goto('https://www.gog.com/en/account/settings/subscriptions'); await page.locator('li:has-text("Promotions and hot deals") label').uncheck(); From 04f503fea7738982a4d49b35a7a7b1479d6e1d87 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 30 Mar 2023 16:53:43 +0200 Subject: [PATCH 251/520] eg: mention required base game for free Add-Ons, #106 --- epic-games.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/epic-games.js b/epic-games.js index 0a6cad6..668f117 100644 --- a/epic-games.js +++ b/epic-games.js @@ -136,6 +136,10 @@ try { console.log(' Requires base game! Nothing to claim.'); notify_game.status = 'requires base game'; db.data[user][game_id].status ||= 'failed:requires-base-game'; + // TODO claim base game if it is free + const baseUrl = 'https://store.epicgames.com' + await page.locator('a:has-text("Overview")').getAttribute('href'); + console.log(' Base game:', baseUrl); + // await page.click('a:has-text("Overview")'); } else { // GET console.log(' Not in library yet! Click GET.'); await page.click('[data-testid="purchase-cta-button"]', { delay: 11 }); // got stuck here without delay (or mouse move), see #75, 1ms was also enough From 6cbd9fe5e3245b511d4424df82ea9b143b7da4cf Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 30 Mar 2023 17:59:34 +0200 Subject: [PATCH 252/520] link RPi 64-bit OS issue --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index af2a19b..a18063e 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Pull requests welcome :) _Works on Windows/macOS/Linux._ -Raspberry Pi (3, 4, Zero 2): Raspbian won't work since it's 32-bit, but Raspberry Pi OS (64-bit) or Ubuntu will. +Raspberry Pi (3, 4, Zero 2): [requires 64-bit OS](https://github.com/vogler/free-games-claimer/issues/3) like Raspberry Pi OS or Ubuntu (Raspbian won't work since it's 32-bit). ## How to run Easy option: [install Docker](https://docs.docker.com/get-docker/) (or [podman](https://podman-desktop.io/)) and run this command in a terminal (Windows: `cmd`, `.bat` file): From e22bb22a535f31fda3d10170a0570c4921492465 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 7 Apr 2023 09:50:40 +0200 Subject: [PATCH 253/520] eg: try click delay for #120 --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 668f117..67eba3c 100644 --- a/epic-games.js +++ b/epic-games.js @@ -117,7 +117,7 @@ try { // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { console.log(' This game contains mature content recommended only for ages 18+'); - await page.click('button:has-text("Continue")'); + await page.click('button:has-text("Continue")', { delay: 11 }); } const title = await page.locator('h1').first().innerText(); From 28146c7d69c2c6f11616b6a5755d90bbbb0600f5 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 7 Apr 2023 10:23:19 +0200 Subject: [PATCH 254/520] eg: 2s delay after click Continue, #120 --- epic-games.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 67eba3c..2c98a31 100644 --- a/epic-games.js +++ b/epic-games.js @@ -117,7 +117,8 @@ try { // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { console.log(' This game contains mature content recommended only for ages 18+'); - await page.click('button:has-text("Continue")', { delay: 11 }); + await page.click('button:has-text("Continue")', { delay: 111 }); + await page.waitForTimeout(2000); } const title = await page.locator('h1').first().innerText(); From b2d69b4d263a4861864490ff8be951133e797152 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 14 Apr 2023 11:53:22 +0200 Subject: [PATCH 255/520] pg: redeem legacy games, #5 --- prime-gaming.js | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/prime-gaming.js b/prime-gaming.js index 2fc1c72..e975c75 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -211,6 +211,15 @@ try { } } } else if (store == 'legacy games') { + console.error(` Redeem on ${store} not yet tested!`); + await page2.fill('[name=coupon_code]', code); + await page2.fill('[name=email]', cfg.pg_email); // TODO option for sep. email? + await page2.fill('[name=email_validate]', cfg.pg_email); + await page2.uncheck('[name=newsletter_sub]'); + await page2.click('[type="submit"]'); + redeem_action = 'redeemed?'; + console.log(' Redeemed successfully? Please report problems in https://github.com/vogler/free-games-claimer/issues/5'); + } else { console.error(` Redeem on ${store} not yet implemented!`); } await page2.pause(); From 351bf00c7b78a94b72d2a5fb1d9085b385ec41bb Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 14 Apr 2023 11:59:33 +0200 Subject: [PATCH 256/520] pg: redeem gog: fix for undefined reason in response, #5 --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index e975c75..b7fa620 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -173,7 +173,7 @@ try { // {"reason":"code_used"} // {"reason":"code_not_found"} const reason = JSON.parse(rt).reason; - if (reason.includes('captcha')) { + if (reason && reason.includes('captcha')) { redeem_action = 'redeem (got captcha)'; console.error(' Got captcha; could not redeem!'); } else if (reason == 'code_used') { From 670262c118928d090fcd555812d8e4dc69472118 Mon Sep 17 00:00:00 2001 From: l-skywalker Date: Thu, 20 Apr 2023 20:12:49 +0200 Subject: [PATCH 257/520] fix: update epic confirm text --- epic-games.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index 2c98a31..ce60d49 100644 --- a/epic-games.js +++ b/epic-games.js @@ -195,7 +195,7 @@ try { // console.info(' Saved a screenshot of hcaptcha challenge to', p); // console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? }).catch(_ => { }); // may time out if not shown - await page.waitForSelector('text=Thank you for buying'); + await page.waitForSelector('text=Thanks for your order!'); db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time console.log(' Claimed successfully!'); @@ -226,4 +226,4 @@ try { } } if (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); -await context.close(); +await context.close(); \ No newline at end of file From a235ce7915964d98e5313610890ea50ecb47b4bd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 25 Apr 2023 09:48:20 +0200 Subject: [PATCH 258/520] add star-history --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index a18063e..c10d726 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,8 @@ Added OTP generation via otplib for automatic login, even with 2FA. Added notifications via [apprise](https://github.com/caronc/apprise).
+[![Star History Chart](https://api.star-history.com/svg?repos=vogler/free-games-claimer&type=Date)](https://star-history.com/#vogler/free-games-claimer&Date) + --- Logo with smaller aspect ratio (for Telegram bot etc.): 👾 - [emojipedia](https://emojipedia.org/alien-monster/) From bc8a89f365c116a2bcbaff806194c0ae1dee4c07 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Apr 2023 19:14:48 +0200 Subject: [PATCH 259/520] log 'DRYRUN=1 -> Skip order!' --- epic-games.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index ce60d49..3819083 100644 --- a/epic-games.js +++ b/epic-games.js @@ -175,7 +175,10 @@ try { }).catch(_ => { }); if (cfg.debug) await page.pause(); - if (cfg.dryrun) continue; + if (cfg.dryrun) { + console.log(' DRYRUN=1 -> Skip order!'); + continue; + } // Playwright clicked before button was ready to handle event, https://github.com/vogler/free-games-claimer/issues/84#issuecomment-1474346591 await iframe.locator('button:has-text("Place Order"):not(:has(.payment-loading--loading))').click({ delay: 11 }); From bb51fd80657fafcf974256024c9000ce501d2815 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 28 Apr 2023 00:11:29 +0200 Subject: [PATCH 260/520] use local time instead of UTC, migrate.js, closes #131 Run `node migrate.js localtime data/*.json` to convert existing `time` entries from UTC to your local timezone. --- migrate.js | 34 ++++++++++++++++++++++++++++++++++ util.js | 6 +++--- 2 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 migrate.js diff --git a/migrate.js b/migrate.js new file mode 100644 index 0000000..41bbe13 --- /dev/null +++ b/migrate.js @@ -0,0 +1,34 @@ +import { existsSync } from 'fs'; +import { Low } from 'lowdb'; +import { JSONFile } from 'lowdb/node'; +import { datetime } from './util.js'; + +const datetime_UTCtoLocalTimezone = async file => { + if (!existsSync(file)) + return console.error('File does not exist:', file); + const db = new Low(new JSONFile(file)); + await db.read(); + db.data ||= {}; + console.log('Migrating', file); + for (const user in db.data) { + for (const game in db.data[user]) { + const time1 = db.data[user][game].time; + const time1s = time1.endsWith('Z') ? time1 : time1 + ' UTC'; + const time2 = datetime(new Date(time1s)); + console.log([game, time1, time2]); + db.data[user][game].time = time2; + } + } + // console.log(db.data); + await db.write(); // write out json db +}; + +const args = process.argv.slice(2); +if (args[0] == 'localtime') { + const files = args.slice(1); + console.log('Will convert UTC datetime to local timezone for', files); + files.forEach(datetime_UTCtoLocalTimezone); +} else { + console.log('Usage: node migrate.js '); + console.log(' node migrate.js localtime data/*.json'); +} diff --git a/util.js b/util.js index 839e5c8..2725745 100644 --- a/util.js +++ b/util.js @@ -19,9 +19,9 @@ export const jsonDb = async file => { export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); // date and time as UTC (no timezone offset) in nicely readable and sortable format, e.g., 2022-10-06 12:05:27.313 -export const datetime = (d = new Date()) => d.toISOString().replace('T', ' ').replace('Z', ''); -// same as datetime() but for local timezone, e.g., UTC + 2h for the above in DE -export const datetimeLocal = (d = new Date()) => datetime(new Date(d.getTime() - new Date().getTimezoneOffset() * 60000)); +export const datetimeUTC = (d = new Date()) => d.toISOString().replace('T', ' ').replace('Z', ''); +// same as datetimeUTC() but for local timezone, e.g., UTC + 2h for the above in DE +export const datetime = (d = new Date()) => datetimeUTC(new Date(d.getTime() - d.getTimezoneOffset() * 60000)); export const filenamify = s => s.replaceAll(':', '.').replace(/[^a-z0-9 _\-.]/gi, '_'); // alternative: https://www.npmjs.com/package/filenamify - On Unix-like systems, / is reserved. On Windows, <>:"/\|?* along with trailing periods are reserved. export const handleSIGINT = () => process.on('SIGINT', () => { // e.g. when killed by Ctrl-C From 5214bea488ae86824713d28e25c57d769fa8fdab Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 28 Apr 2023 00:26:09 +0200 Subject: [PATCH 261/520] cp epic-games.js unrealengine.js --- unrealengine.js | 232 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 unrealengine.js diff --git a/unrealengine.js b/unrealengine.js new file mode 100644 index 0000000..3819083 --- /dev/null +++ b/unrealengine.js @@ -0,0 +1,232 @@ +import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra +import { authenticator } from 'otplib'; +import path from 'path'; +import { existsSync, writeFileSync } from 'fs'; +import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; +import { cfg } from './config.js'; + +const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; +const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; + +console.log(datetime(), 'started checking epic-games'); + +const db = await jsonDb('epic-games.json'); +db.data ||= {}; + +handleSIGINT(); + +// https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 +// const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox + +// https://playwright.dev/docs/auth#multi-factor-authentication +const context = await firefox.launchPersistentContext(cfg.dir.browser, { + // chrome will not work in linux arm64, only chromium + // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge + headless: cfg.headless, + viewport: { width: cfg.width, height: cfg.height }, + userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? + // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 + locale: "en-US", // ignore OS locale to be sure to have english text for locators + // recordVideo: { dir: 'data/videos/' }, // will record a .webm video for each page navigated + args: [ // https://peter.sh/experiments/chromium-command-line-switches + // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' + // '--restore-last-session', // does not apply for crash/killed + '--hide-crash-restore-bubble', + // `--disable-extensions-except=${ext}`, + // `--load-extension=${ext}`, + ], + // ignoreDefaultArgs: ['--enable-automation'], // remove default arg that shows the info bar with 'Chrome is being controlled by automated test software.'. Since Chromeium 106 this leads to show another info bar with 'You are using an unsupported command-line flag: --no-sandbox. Stability and security will suffer.'. +}); + +// 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); + +if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); + +const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist +// console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); + +const notify_games = []; +let user; + +try { + await context.addCookies([{name: 'OptanonAlertBoxClosed', value: new Date(Date.now() - 5*24*60*60*1000).toISOString(), domain: '.epicgames.com', path: '/'}]); // Accept cookies to get rid of banner to save space on screen. Set accept time to 5 days ago. + + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto + + // page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // Not needed anymore since we set the cookie above. Clicking this did not always work since the message was animated in too slowly. + + while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { + console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); + if (cfg.novnc_port) console.info(`Open http://localhost:${cfg.novnc_port} to login inside the docker container.`); + if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in + console.info(`Login timeout is ${cfg.login_timeout/1000} seconds!`); + await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); + if (cfg.eg_email && cfg.eg_password) console.info('Using email and password from environment.'); + else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); + const email = cfg.eg_email || await prompt({message: 'Enter email'}); + const password = email && (cfg.eg_password || await prompt({type: 'password', message: 'Enter password'})); + if (email && password) { + await page.click('text=Sign in with Epic Games'); + await page.fill('#email', email); + await page.fill('#password', password); + await page.click('button[type="submit"]'); + page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(() => { + console.error('Got a captcha during login (likely due to too many attempts)! You may solve it in the browser, get a new IP or try again in a few hours.'); + notify('epic-games: got captcha during login. Please check.'); + }).catch(_ => { }); + // handle MFA, but don't await it + page.waitForURL('**/id/login/mfa**').then(async () => { + console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); + // TODO locator for text (email or app?) + const otp = cfg.eg_otpkey && authenticator.generate(cfg.eg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + await page.type('input[name="code-input-0"]', otp.toString()); + await page.click('button[type="submit"]'); + }).catch(_ => { }); + } else { + console.log('Waiting for you to login in the browser.'); + await notify('epic-games: no longer signed in and not enough options set for automatic login.'); + if (cfg.headless) { + console.log('Run `SHOW=1 node epic-games` to login in the opened browser.'); + await context.close(); // finishes potential recording + process.exit(1); + } + } + await page.waitForURL(URL_CLAIM); + if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); + } + user = await page.locator('#user span').first().innerHTML(); + console.log(`Signed in as ${user}`); + db.data[user] ||= {}; + + // Detect free games + const game_loc = page.locator('a:has(span:text-is("Free Now"))'); + await game_loc.last().waitFor(); + // clicking on `game_sel` sometimes led to a 404, see https://github.com/vogler/free-games-claimer/issues/25 + // 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 + // 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 urls = urlSlugs.map(s => 'https://store.epicgames.com' + s); + console.log('Free games:', urls); + + for (const url of urls) { + await page.goto(url); // , { waitUntil: 'domcontentloaded' }); + const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); // barrier to block until page is loaded + + // click Continue if 'This game contains mature content recommended only for ages 18+' + if (await page.locator('button:has-text("Continue")').count() > 0) { + console.log(' This game contains mature content recommended only for ages 18+'); + await page.click('button:has-text("Continue")', { delay: 111 }); + await page.waitForTimeout(2000); + } + + const title = await page.locator('h1').first().innerText(); + const game_id = page.url().split('/').pop(); + db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! + console.log('Current free game:', title); + const notify_game = { title, url, status: 'failed' }; + notify_games.push(notify_game); // status is updated below + + if (btnText.toLowerCase() == 'in library') { + console.log(' Already in library! Nothing to claim.'); + notify_game.status = 'existed'; + db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed + if (db.data[user][game_id].status.startsWith('failed')) db.data[user][game_id].status = 'manual'; // was failed but now it's claimed + } else if (btnText.toLowerCase() == 'requires base game') { + console.log(' Requires base game! Nothing to claim.'); + notify_game.status = 'requires base game'; + db.data[user][game_id].status ||= 'failed:requires-base-game'; + // TODO claim base game if it is free + const baseUrl = 'https://store.epicgames.com' + await page.locator('a:has-text("Overview")').getAttribute('href'); + console.log(' Base game:', baseUrl); + // await page.click('a:has-text("Overview")'); + } else { // GET + console.log(' Not in library yet! Click GET.'); + await page.click('[data-testid="purchase-cta-button"]', { delay: 11 }); // got stuck here without delay (or mouse move), see #75, 1ms was also enough + + // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoided by Windows userAgent? + page.click('button:has-text("Continue")').catch(_ => { }); // needed since change from Chromium to Firefox? + + // Accept End User License Agreement (only needed once) + page.locator('input#agree').waitFor().then(async () => { + console.log('Accept End User License Agreement (only needed once)'); + await page.locator('input#agree').check(); + await page.locator('button:has-text("Accept")').click(); + }).catch(_ => { }); + + // it then creates an iframe for the purchase + await page.waitForSelector('#webPurchaseContainer iframe'); // TODO needed? + const iframe = page.frameLocator('#webPurchaseContainer iframe'); + // skip game if unavailable in region, https://github.com/vogler/free-games-claimer/issues/46 TODO check games for account's region + if (await iframe.locator(':has-text("unavailable in your region")').count() > 0) { + console.error(' This product is unavailable in your region!'); + db.data[user][game_id].status = notify_game.status = 'unavailable-in-region'; + continue; + } + + iframe.locator('.payment-pin-code').waitFor().then(async () => { + if (!cfg.eg_parentalpin) { + console.error(' EG_PARENTALPIN not set. Need to enter Parental Control PIN manually.'); + notify('epic-games: EG_PARENTALPIN not set. Need to enter Parental Control PIN manually.'); + } + await iframe.locator('input.payment-pin-code__input').first().type(cfg.eg_parentalpin); + await iframe.locator('button:has-text("Continue")').click({ delay: 11 }); + }).catch(_ => { }); + + if (cfg.debug) await page.pause(); + if (cfg.dryrun) { + console.log(' DRYRUN=1 -> Skip order!'); + continue; + } + + // Playwright clicked before button was ready to handle event, https://github.com/vogler/free-games-claimer/issues/84#issuecomment-1474346591 + await iframe.locator('button:has-text("Place Order"):not(:has(.payment-loading--loading))').click({ delay: 11 }); + + // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 + const btnAgree = iframe.locator('button:has-text("I Agree")'); + btnAgree.waitFor().then(() => btnAgree.click()).catch(_ => { }); // EU: wait for and click 'I Agree' + try { + // context.setDefaultTimeout(100 * 1000); // give time to solve captcha, iframe goes blank after 60s? + const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); + captcha.waitFor().then(async () => { // don't await, since element may not be shown + // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') + console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.') + // await page.waitForTimeout(2000); + // const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); + // await captcha.screenshot({ path: p }); + // console.info(' Saved a screenshot of hcaptcha challenge to', p); + // console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? + }).catch(_ => { }); // may time out if not shown + await page.waitForSelector('text=Thanks for your order!'); + db.data[user][game_id].status = 'claimed'; + db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time + console.log(' Claimed successfully!'); + // context.setDefaultTimeout(cfg.timeout); + } catch (e) { + console.log(e); + // console.error(' Failed to claim! Try again if NopeCHA timed out. Click the extension to see if you ran out of credits (refill after 24h). To avoid captchas try to get a new IP or set a cookie from https://www.hcaptcha.com/accessibility'); + console.error(' Failed to claim! To avoid captchas try to get a new IP address.'); + const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'failed', `${game_id}_${filenamify(datetime())}.png`); + await page.screenshot({ path: p, fullPage: true }); + db.data[user][game_id].status = 'failed'; + } + notify_game.status = db.data[user][game_id].status; // claimed or failed + + const p = path.resolve(cfg.dir.screenshots, 'epic-games', `${game_id}.png`); + if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... + } + } +} catch (error) { + console.error(error); // .toString()? + process.exitCode ||= 1; + if (error.message && process.exitCode != 130) + notify(`epic-games failed: ${error.message.split('\n')[0]}`); +} finally { + await db.write(); // write out json db + if (notify_games.filter(g => g.status != 'existed' && g.status != 'requires base game').length) { // don't notify if all were already claimed + notify(`epic-games (${user}):
${html_game_list(notify_games)}`); + } +} +if (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); +await context.close(); \ No newline at end of file From 5809b0963aad0ce45aed04a2fe3377b3717c64d1 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 28 Apr 2023 00:59:27 +0200 Subject: [PATCH 262/520] ue: unrealengine: add assets to cart & checkout, rest same as for epic-games, #44 --- unrealengine.js | 224 ++++++++++++++++++------------------------------ 1 file changed, 82 insertions(+), 142 deletions(-) diff --git a/unrealengine.js b/unrealengine.js index 3819083..b7b7c63 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -1,3 +1,5 @@ +// TODO This is mostly a copy of epic-games.js + import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; import path from 'path'; @@ -5,40 +7,25 @@ import { existsSync, writeFileSync } from 'fs'; import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; -const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; +const URL_CLAIM = 'https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910'; const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; -console.log(datetime(), 'started checking epic-games'); +console.log(datetime(), 'started checking unrealengine'); -const db = await jsonDb('epic-games.json'); +const db = await jsonDb('unrealengine.json'); db.data ||= {}; handleSIGINT(); -// https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 -// const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox - // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(cfg.dir.browser, { - // chrome will not work in linux arm64, only chromium - // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 locale: "en-US", // ignore OS locale to be sure to have english text for locators - // recordVideo: { dir: 'data/videos/' }, // will record a .webm video for each page navigated - args: [ // https://peter.sh/experiments/chromium-command-line-switches - // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' - // '--restore-last-session', // does not apply for crash/killed - '--hide-crash-restore-bubble', - // `--disable-extensions-except=${ext}`, - // `--load-extension=${ext}`, - ], - // ignoreDefaultArgs: ['--enable-automation'], // remove default arg that shows the info bar with 'Chrome is being controlled by automated test software.'. Since Chromeium 106 this leads to show another info bar with 'You are using an unsupported command-line flag: --no-sandbox. Stability and security will suffer.'. }); -// 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); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); @@ -54,8 +41,6 @@ try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto - // page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // Not needed anymore since we set the cookie above. Clicking this did not always work since the message was animated in too slowly. - while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); if (cfg.novnc_port) console.info(`Open http://localhost:${cfg.novnc_port} to login inside the docker container.`); @@ -73,7 +58,7 @@ try { await page.click('button[type="submit"]'); page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(() => { console.error('Got a captcha during login (likely due to too many attempts)! You may solve it in the browser, get a new IP or try again in a few hours.'); - notify('epic-games: got captcha during login. Please check.'); + notify('unrealengine: got captcha during login. Please check.'); }).catch(_ => { }); // handle MFA, but don't await it page.waitForURL('**/id/login/mfa**').then(async () => { @@ -85,9 +70,9 @@ try { }).catch(_ => { }); } else { console.log('Waiting for you to login in the browser.'); - await notify('epic-games: no longer signed in and not enough options set for automatic login.'); + await notify('unrealengine: no longer signed in and not enough options set for automatic login.'); if (cfg.headless) { - console.log('Run `SHOW=1 node epic-games` to login in the opened browser.'); + console.log('Run `SHOW=1 node unrealengine` to login in the opened browser.'); await context.close(); // finishes potential recording process.exit(1); } @@ -95,138 +80,93 @@ try { await page.waitForURL(URL_CLAIM); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } - user = await page.locator('#user span').first().innerHTML(); + await page.waitForTimeout(1000); + user = await page.locator('.user-label').first().innerHTML(); console.log(`Signed in as ${user}`); db.data[user] ||= {}; - // Detect free games - const game_loc = page.locator('a:has(span:text-is("Free Now"))'); - await game_loc.last().waitFor(); - // clicking on `game_sel` sometimes led to a 404, see https://github.com/vogler/free-games-claimer/issues/25 - // 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 - // 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 urls = urlSlugs.map(s => 'https://store.epicgames.com' + s); - console.log('Free games:', urls); - - for (const url of urls) { - await page.goto(url); // , { waitUntil: 'domcontentloaded' }); - const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); // barrier to block until page is loaded - - // click Continue if 'This game contains mature content recommended only for ages 18+' - if (await page.locator('button:has-text("Continue")').count() > 0) { - console.log(' This game contains mature content recommended only for ages 18+'); - await page.click('button:has-text("Continue")', { delay: 111 }); - await page.waitForTimeout(2000); - } - - const title = await page.locator('h1').first().innerText(); - const game_id = page.url().split('/').pop(); - db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! - console.log('Current free game:', title); + page.locator('button:has-text("Accept All Cookies")').click().catch(_ => { }); + for (const p of await page.locator('article.asset').all()) { + const link = p.locator('h3 a'); + const title = await link.innerText(); + const url = 'https://www.unrealengine.com' + await link.getAttribute('href'); + console.log(title, url); + const id = page.url().split('/').pop(); + db.data[user][id] ||= { title, time: datetime(), url }; // this will be set on the initial run only! const notify_game = { title, url, status: 'failed' }; notify_games.push(notify_game); // status is updated below - - if (btnText.toLowerCase() == 'in library') { - console.log(' Already in library! Nothing to claim.'); - notify_game.status = 'existed'; - db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed - if (db.data[user][game_id].status.startsWith('failed')) db.data[user][game_id].status = 'manual'; // was failed but now it's claimed - } else if (btnText.toLowerCase() == 'requires base game') { - console.log(' Requires base game! Nothing to claim.'); - notify_game.status = 'requires base game'; - db.data[user][game_id].status ||= 'failed:requires-base-game'; - // TODO claim base game if it is free - const baseUrl = 'https://store.epicgames.com' + await page.locator('a:has-text("Overview")').getAttribute('href'); - console.log(' Base game:', baseUrl); - // await page.click('a:has-text("Overview")'); - } else { // GET - console.log(' Not in library yet! Click GET.'); - await page.click('[data-testid="purchase-cta-button"]', { delay: 11 }); // got stuck here without delay (or mouse move), see #75, 1ms was also enough - - // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoided by Windows userAgent? - page.click('button:has-text("Continue")').catch(_ => { }); // needed since change from Chromium to Firefox? - - // Accept End User License Agreement (only needed once) - page.locator('input#agree').waitFor().then(async () => { - console.log('Accept End User License Agreement (only needed once)'); - await page.locator('input#agree').check(); - await page.locator('button:has-text("Accept")').click(); - }).catch(_ => { }); - - // it then creates an iframe for the purchase - await page.waitForSelector('#webPurchaseContainer iframe'); // TODO needed? - const iframe = page.frameLocator('#webPurchaseContainer iframe'); - // skip game if unavailable in region, https://github.com/vogler/free-games-claimer/issues/46 TODO check games for account's region - if (await iframe.locator(':has-text("unavailable in your region")').count() > 0) { - console.error(' This product is unavailable in your region!'); - db.data[user][game_id].status = notify_game.status = 'unavailable-in-region'; - continue; - } - - iframe.locator('.payment-pin-code').waitFor().then(async () => { - if (!cfg.eg_parentalpin) { - console.error(' EG_PARENTALPIN not set. Need to enter Parental Control PIN manually.'); - notify('epic-games: EG_PARENTALPIN not set. Need to enter Parental Control PIN manually.'); - } - await iframe.locator('input.payment-pin-code__input').first().type(cfg.eg_parentalpin); - await iframe.locator('button:has-text("Continue")').click({ delay: 11 }); - }).catch(_ => { }); - - if (cfg.debug) await page.pause(); - if (cfg.dryrun) { - console.log(' DRYRUN=1 -> Skip order!'); - continue; - } - - // Playwright clicked before button was ready to handle event, https://github.com/vogler/free-games-claimer/issues/84#issuecomment-1474346591 - await iframe.locator('button:has-text("Place Order"):not(:has(.payment-loading--loading))').click({ delay: 11 }); - - // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 - const btnAgree = iframe.locator('button:has-text("I Agree")'); - btnAgree.waitFor().then(() => btnAgree.click()).catch(_ => { }); // EU: wait for and click 'I Agree' - try { - // context.setDefaultTimeout(100 * 1000); // give time to solve captcha, iframe goes blank after 60s? - const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); - captcha.waitFor().then(async () => { // don't await, since element may not be shown - // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') - console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.') - // await page.waitForTimeout(2000); - // const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); - // await captcha.screenshot({ path: p }); - // console.info(' Saved a screenshot of hcaptcha challenge to', p); - // console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? - }).catch(_ => { }); // may time out if not shown - await page.waitForSelector('text=Thanks for your order!'); - db.data[user][game_id].status = 'claimed'; - db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time - console.log(' Claimed successfully!'); - // context.setDefaultTimeout(cfg.timeout); - } catch (e) { - console.log(e); - // console.error(' Failed to claim! Try again if NopeCHA timed out. Click the extension to see if you ran out of credits (refill after 24h). To avoid captchas try to get a new IP or set a cookie from https://www.hcaptcha.com/accessibility'); - console.error(' Failed to claim! To avoid captchas try to get a new IP address.'); - const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'failed', `${game_id}_${filenamify(datetime())}.png`); - await page.screenshot({ path: p, fullPage: true }); - db.data[user][game_id].status = 'failed'; - } - notify_game.status = db.data[user][game_id].status; // claimed or failed - - const p = path.resolve(cfg.dir.screenshots, 'epic-games', `${game_id}.png`); - if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... + if (await p.locator('.btn .in-cart').count()){ + console.log(' already in cart'); + continue; } + await p.locator('.btn .add').click(); + console.log(' added to cart'); } + const price = (await page.locator('.shopping-cart .total .price').innerText()).split(' '); + console.log('price: ', price[1], 'instead of', price[0]); + if (price[1] != '0') { + console.error('Price is not 0! Exit!'); + process.exit(1); + } + // await page.pause(); + console.log('Click shopping cart'); + await page.locator('.shopping-cart').click(); + // await page.waitForTimeout(2000); + await page.locator('button.checkout').click(); + console.log('Click checkout'); + // maybe: Accept End User License Agreement + page.locator('[name=accept-label]').check().then(() => { + console.log('Accept End User License Agreement'); + page.locator('span:text-is("Accept")').click() // otherwise matches 'Accept All Cookies' + }).catch(_ => { }); + // await page.waitForSelector('#webPurchaseContainer iframe'); // TODO needed? + const iframe = page.frameLocator('#webPurchaseContainer iframe'); + + if (cfg.debug) await page.pause(); + if (cfg.dryrun) { + console.log(' DRYRUN=1 -> Skip order!'); + process.exit(); + } + + await iframe.locator('button:has-text("Place Order")').click(); + // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 + const btnAgree = iframe.locator('button:has-text("I Agree")'); + try { + context.setDefaultTimeout(100 * 1000); // give time to solve captcha, iframe goes blank after 60s? + await Promise.any([btnAgree.click(), page.waitForSelector('text=Thank you').then(_ => { })]); // EU: wait for agree button, non-EU: potentially done + + const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); + captcha.waitFor().then(async () => { // don't await, since element may not be shown + console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.') + }).catch(_ => { }); // may time out if not shown + await page.waitForSelector('text=Thank you'); // EU: wait, non-EU: wait again = no-op + // db.data[user][id].status = 'claimed'; + // db.data[user][id].time = datetime(); // claimed time overwrites failed/dryrun time + notify_games.forEach(g => g.status = 'claimed'); + console.log(' Claimed successfully!'); + context.setDefaultTimeout(cfg.timeout); + } catch (e) { + console.log(e); + console.error(' Failed to claim! To avoid captchas try to get a new IP address.'); + // const p = path.resolve(cfg.dir.screenshots, 'unrealengine', 'failed', `${id}_${filenamify(datetime())}.png`); + // await page.screenshot({ path: p, fullPage: true }); + // db.data[user][id].status = 'failed'; + notify_games.forEach(g => g.status = 'failed'); + } + + const p = path.resolve(cfg.dir.screenshots, 'unrealengine', `${filenamify(datetime())}.png`); + if (notify_games.length) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... + console.log('Done'); } catch (error) { console.error(error); // .toString()? process.exitCode ||= 1; if (error.message && process.exitCode != 130) - notify(`epic-games failed: ${error.message.split('\n')[0]}`); + notify(`unrealengine failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db - if (notify_games.filter(g => g.status != 'existed' && g.status != 'requires base game').length) { // don't notify if all were already claimed - notify(`epic-games (${user}):
${html_game_list(notify_games)}`); + if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed + notify(`unrealengine (${user}):
${html_game_list(notify_games)}`); } } if (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); -await context.close(); \ No newline at end of file +await context.close(); From 631197371f5bdfcfa7e1262e99bc667a1e53cf14 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 29 Apr 2023 11:01:16 +0200 Subject: [PATCH 263/520] ue: claimed successfully; set status, detect owned, better log --- unrealengine.js | 143 ++++++++++++++++++++++++++++-------------------- 1 file changed, 83 insertions(+), 60 deletions(-) diff --git a/unrealengine.js b/unrealengine.js index b7b7c63..25be32e 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -86,77 +86,100 @@ try { db.data[user] ||= {}; page.locator('button:has-text("Accept All Cookies")').click().catch(_ => { }); + + const ids = []; for (const p of await page.locator('article.asset').all()) { const link = p.locator('h3 a'); const title = await link.innerText(); const url = 'https://www.unrealengine.com' + await link.getAttribute('href'); - console.log(title, url); - const id = page.url().split('/').pop(); - db.data[user][id] ||= { title, time: datetime(), url }; // this will be set on the initial run only! + console.log([title, url]); + const id = url.split('/').pop(); + db.data[user][id] ||= { title, time: datetime(), url, status: 'failed' }; // this will be set on the initial run only! const notify_game = { title, url, status: 'failed' }; notify_games.push(notify_game); // status is updated below - if (await p.locator('.btn .in-cart').count()){ - console.log(' already in cart'); + // if (await p.locator('.btn .add-review-btn').count()) { // did not work + if((await p.getAttribute('class')).includes('asset--owned')) { + console.log(' ↳ Already claimed'); + if (db.data[user][id].status != 'claimed') { + db.data[user][id].status = 'existed'; + notify_game.status = 'existed'; + } + continue; + } + if (await p.locator('.btn .in-cart').count()) { + console.log(' ↳ Already in cart'); continue; } await p.locator('.btn .add').click(); - console.log(' added to cart'); + console.log(' ↳ Added to cart'); + ids.push(id); } - const price = (await page.locator('.shopping-cart .total .price').innerText()).split(' '); - console.log('price: ', price[1], 'instead of', price[0]); - if (price[1] != '0') { - console.error('Price is not 0! Exit!'); - process.exit(1); + if (!ids.length) { + console.log('Nothing to claim'); + } else { + const price = (await page.locator('.shopping-cart .total .price').innerText()).split(' '); + console.log('Price: ', price[1], 'instead of', price[0]); + if (price[1] != '0') { + console.error('Price is not 0! Exit!'); + process.exit(1); + } + // await page.pause(); + console.log('Click shopping cart'); + await page.locator('.shopping-cart').click(); + // await page.waitForTimeout(2000); + await page.locator('button.checkout').click(); + console.log('Click checkout'); + // maybe: Accept End User License Agreement + page.locator('[name=accept-label]').check().then(() => { + console.log('Accept End User License Agreement'); + page.locator('span:text-is("Accept")').click() // otherwise matches 'Accept All Cookies' + }).catch(_ => { }); + await page.waitForSelector('#webPurchaseContainer iframe'); // TODO needed? + const iframe = page.frameLocator('#webPurchaseContainer iframe'); + + if (cfg.debug) await page.pause(); + if (cfg.dryrun) { + console.log('DRYRUN=1 -> Skip order!'); + throw new Error('DRYRUN=1'); + } + + console.log('Click Place Order'); + // Playwright clicked before button was ready to handle event, https://github.com/vogler/free-games-claimer/issues/84#issuecomment-1474346591 + await iframe.locator('button:has-text("Place Order"):not(:has(.payment-loading--loading))').click({ delay: 11 }); + + // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 + const btnAgree = iframe.locator('button:has-text("I Agree")'); + btnAgree.waitFor().then(() => btnAgree.click()).catch(_ => { }); // EU: wait for and click 'I Agree' + try { + // context.setDefaultTimeout(100 * 1000); // give time to solve captcha, iframe goes blank after 60s? + const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); + captcha.waitFor().then(async () => { // don't await, since element may not be shown + // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') + console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.') + }).catch(_ => { }); // may time out if not shown + await page.waitForSelector('text=Thank you'); + for (const id of ids) { + db.data[user][id].status = 'claimed'; + db.data[user][id].time = datetime(); // claimed time overwrites failed/dryrun time + } + notify_games.forEach(g => g.status == 'failed' && (g.status = 'claimed')); + console.log('Claimed successfully!'); + // context.setDefaultTimeout(cfg.timeout); + } catch (e) { + console.log(e); + // console.error(' Failed to claim! Try again if NopeCHA timed out. Click the extension to see if you ran out of credits (refill after 24h). To avoid captchas try to get a new IP or set a cookie from https://www.hcaptcha.com/accessibility'); + console.error(' Failed to claim! To avoid captchas try to get a new IP address.'); + const p = path.resolve(cfg.dir.screenshots, 'unrealengine', 'failed', `${filenamify(datetime())}.png`); + await page.screenshot({ path: p, fullPage: true }); + // db.data[user][id].status = 'failed'; + notify_games.forEach(g => g.status = 'failed'); + } + // notify_game.status = db.data[user][game_id].status; // claimed or failed + + const p = path.resolve(cfg.dir.screenshots, 'unrealengine', `${filenamify(datetime())}.png`); + if (notify_games.length) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... + console.log('Done'); } - // await page.pause(); - console.log('Click shopping cart'); - await page.locator('.shopping-cart').click(); - // await page.waitForTimeout(2000); - await page.locator('button.checkout').click(); - console.log('Click checkout'); - // maybe: Accept End User License Agreement - page.locator('[name=accept-label]').check().then(() => { - console.log('Accept End User License Agreement'); - page.locator('span:text-is("Accept")').click() // otherwise matches 'Accept All Cookies' - }).catch(_ => { }); - // await page.waitForSelector('#webPurchaseContainer iframe'); // TODO needed? - const iframe = page.frameLocator('#webPurchaseContainer iframe'); - - if (cfg.debug) await page.pause(); - if (cfg.dryrun) { - console.log(' DRYRUN=1 -> Skip order!'); - process.exit(); - } - - await iframe.locator('button:has-text("Place Order")').click(); - // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 - const btnAgree = iframe.locator('button:has-text("I Agree")'); - try { - context.setDefaultTimeout(100 * 1000); // give time to solve captcha, iframe goes blank after 60s? - await Promise.any([btnAgree.click(), page.waitForSelector('text=Thank you').then(_ => { })]); // EU: wait for agree button, non-EU: potentially done - - const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); - captcha.waitFor().then(async () => { // don't await, since element may not be shown - console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.') - }).catch(_ => { }); // may time out if not shown - await page.waitForSelector('text=Thank you'); // EU: wait, non-EU: wait again = no-op - // db.data[user][id].status = 'claimed'; - // db.data[user][id].time = datetime(); // claimed time overwrites failed/dryrun time - notify_games.forEach(g => g.status = 'claimed'); - console.log(' Claimed successfully!'); - context.setDefaultTimeout(cfg.timeout); - } catch (e) { - console.log(e); - console.error(' Failed to claim! To avoid captchas try to get a new IP address.'); - // const p = path.resolve(cfg.dir.screenshots, 'unrealengine', 'failed', `${id}_${filenamify(datetime())}.png`); - // await page.screenshot({ path: p, fullPage: true }); - // db.data[user][id].status = 'failed'; - notify_games.forEach(g => g.status = 'failed'); - } - - const p = path.resolve(cfg.dir.screenshots, 'unrealengine', `${filenamify(datetime())}.png`); - if (notify_games.length) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... - console.log('Done'); } catch (error) { console.error(error); // .toString()? process.exitCode ||= 1; From ce51c269f8a1cfc24a18051a4a92cbc417f2fe70 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 5 May 2023 09:30:37 +0200 Subject: [PATCH 264/520] eg: only notify for status 'claimed' or 'failed'; DRYRUN -> 'skipped' --- epic-games.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index 3819083..c38bdfc 100644 --- a/epic-games.js +++ b/epic-games.js @@ -177,6 +177,7 @@ try { if (cfg.debug) await page.pause(); if (cfg.dryrun) { console.log(' DRYRUN=1 -> Skip order!'); + notify_game.status = 'skipped'; continue; } @@ -224,9 +225,9 @@ try { notify(`epic-games failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db - if (notify_games.filter(g => g.status != 'existed' && g.status != 'requires base game').length) { // don't notify if all were already claimed + if (notify_games.filter(g => g.status == 'claimed' || g.status == 'failed').length) { // don't notify if all have status 'existed', 'manual', 'requires base game', 'unavailable-in-region', 'skipped' notify(`epic-games (${user}):
${html_game_list(notify_games)}`); } } if (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); -await context.close(); \ No newline at end of file +await context.close(); From 066a99c77c9cc78baa71c2abe467385830269280 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 5 May 2023 09:54:21 +0200 Subject: [PATCH 265/520] eg: RECORD=1 to recordVideo & recordHar --- config.js | 1 + epic-games.js | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index 33016ca..32974e1 100644 --- a/config.js +++ b/config.js @@ -6,6 +6,7 @@ dotenv.config({ path: 'data/config.env' }); // loads env vars from file - will n // Options - also see table in README.md export const cfg = { debug: process.env.PWDEBUG == '1', // runs non-headless and opens https://playwright.dev/docs/inspector + record: process.env.RECORD == '1', // `recordHar` (network) + `recordVideo` dryrun: process.env.DRYRUN == '1', // don't claim anything show: process.env.SHOW == '1', // run non-headless get headless() { return !this.debug && !this.show }, diff --git a/epic-games.js b/epic-games.js index c38bdfc..a107a1f 100644 --- a/epic-games.js +++ b/epic-games.js @@ -27,7 +27,8 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 locale: "en-US", // ignore OS locale to be sure to have english text for locators - // recordVideo: { dir: 'data/videos/' }, // will record a .webm video for each page navigated + recordVideo: cfg.record && { dir: 'data/record/', size: { width: cfg.width, height: cfg.height } }, // 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-${datetime()}.har` }, // will record a HAR file with network requests and responses; can be imported in Chrome devtools args: [ // https://peter.sh/experiments/chromium-command-line-switches // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' // '--restore-last-session', // does not apply for crash/killed @@ -45,6 +46,12 @@ if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); +if (cfg.record && cfg.debug) { + // const filter = _ => true; + const filter = r => r.url().includes('store.epicgames.com'); + page.on('request', request => filter(request) && console.log('>>', request.method(), request.url())); + page.on('response', response => filter(response) && console.log('<<', response.status(), response.url())); +} const notify_games = []; let user; From 7fc0fbc69c969d8de2fae7cc8cf6dd21711804d8 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 5 May 2023 13:36:51 +0200 Subject: [PATCH 266/520] Revert "eg: RECORD=1 to recordVideo & recordHar" This reverts commit 066a99c77c9cc78baa71c2abe467385830269280. --- config.js | 1 - epic-games.js | 9 +-------- 2 files changed, 1 insertion(+), 9 deletions(-) diff --git a/config.js b/config.js index 32974e1..33016ca 100644 --- a/config.js +++ b/config.js @@ -6,7 +6,6 @@ dotenv.config({ path: 'data/config.env' }); // loads env vars from file - will n // Options - also see table in README.md export const cfg = { debug: process.env.PWDEBUG == '1', // runs non-headless and opens https://playwright.dev/docs/inspector - record: process.env.RECORD == '1', // `recordHar` (network) + `recordVideo` dryrun: process.env.DRYRUN == '1', // don't claim anything show: process.env.SHOW == '1', // run non-headless get headless() { return !this.debug && !this.show }, diff --git a/epic-games.js b/epic-games.js index a107a1f..c38bdfc 100644 --- a/epic-games.js +++ b/epic-games.js @@ -27,8 +27,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 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 } }, // 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-${datetime()}.har` }, // will record a HAR file with network requests and responses; can be imported in Chrome devtools + // recordVideo: { dir: 'data/videos/' }, // will record a .webm video for each page navigated args: [ // https://peter.sh/experiments/chromium-command-line-switches // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' // '--restore-last-session', // does not apply for crash/killed @@ -46,12 +45,6 @@ if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); -if (cfg.record && cfg.debug) { - // const filter = _ => true; - const filter = r => r.url().includes('store.epicgames.com'); - page.on('request', request => filter(request) && console.log('>>', request.method(), request.url())); - page.on('response', response => filter(response) && console.log('<<', response.status(), response.url())); -} const notify_games = []; let user; From 8f174c4bf095f604829407d5415dde7324f334f1 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 8 May 2023 17:26:38 +0200 Subject: [PATCH 267/520] eg: RECORD=1 to recordVideo & recordHar; fixed: `recordVideo` can't be false Strangely `recordHar` can be false instead of undefined, but made it symmetric. --- config.js | 1 + epic-games.js | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index 33016ca..32974e1 100644 --- a/config.js +++ b/config.js @@ -6,6 +6,7 @@ dotenv.config({ path: 'data/config.env' }); // loads env vars from file - will n // Options - also see table in README.md export const cfg = { debug: process.env.PWDEBUG == '1', // runs non-headless and opens https://playwright.dev/docs/inspector + record: process.env.RECORD == '1', // `recordHar` (network) + `recordVideo` dryrun: process.env.DRYRUN == '1', // don't claim anything show: process.env.SHOW == '1', // run non-headless get headless() { return !this.debug && !this.show }, diff --git a/epic-games.js b/epic-games.js index c38bdfc..7e1bf62 100644 --- a/epic-games.js +++ b/epic-games.js @@ -27,7 +27,8 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 locale: "en-US", // ignore OS locale to be sure to have english text for locators - // recordVideo: { dir: 'data/videos/' }, // will record a .webm video for each page navigated + 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-${datetime()}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools args: [ // https://peter.sh/experiments/chromium-command-line-switches // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' // '--restore-last-session', // does not apply for crash/killed @@ -45,6 +46,12 @@ if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); +if (cfg.record && cfg.debug) { + // const filter = _ => true; + const filter = r => r.url().includes('store.epicgames.com'); + page.on('request', request => filter(request) && console.log('>>', request.method(), request.url())); + page.on('response', response => filter(response) && console.log('<<', response.status(), response.url())); +} const notify_games = []; let user; From 6b13287b61ff684f1d8c6e157c079c8e1f6fc4fd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 11 May 2023 18:38:44 +0200 Subject: [PATCH 268/520] eg: click 'Yes, buy now' if 'This edition contains something you already have. Still interested?' Happened for add-on https://store.epicgames.com/en-US/p/the-sims-4--the-daring-lifestyle-bundle --- epic-games.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/epic-games.js b/epic-games.js index 7e1bf62..37310ea 100644 --- a/epic-games.js +++ b/epic-games.js @@ -155,6 +155,9 @@ try { // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoided by Windows userAgent? page.click('button:has-text("Continue")').catch(_ => { }); // needed since change from Chromium to Firefox? + // click 'Yes, buy now' if 'This edition contains something you already have. Still interested?' + page.click('button:has-text("Yes, buy now")').catch(_ => { }); + // Accept End User License Agreement (only needed once) page.locator('input#agree').waitFor().then(async () => { console.log('Accept End User License Agreement (only needed once)'); From af935d48898d4df8490483c4611a591c3d36f951 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 11 May 2023 18:50:54 +0200 Subject: [PATCH 269/520] gog: no quotes around user, as for {eg, pg} --- gog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gog.js b/gog.js index 4e27cc2..1785663 100644 --- a/gog.js +++ b/gog.js @@ -84,7 +84,7 @@ try { if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } user = await page.locator('#menuUsername').first().textContent(); // innerText is uppercase due to styling! - console.log(`Signed in as '${user}'`); + console.log(`Signed in as ${user}`); db.data[user] ||= {}; const banner = page.locator('#giveaway'); From 491ee899a5d7d7e517eadceee4875e457e8b6b0c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 11 May 2023 18:57:48 +0200 Subject: [PATCH 270/520] pg: #126 change selector to handle potential button text 'Claim' (instead of 'Claim game') for internal games --- prime-gaming.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index b7fa620..4330cd9 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -96,7 +96,7 @@ try { const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; await page.waitForSelector(games_sel); console.log('Number of already claimed games (total):', await page.locator(`${games_sel} p:has-text("Collected")`).count()); - const game_sel = `${games_sel} [data-a-target="item-card"]:has-text("Claim game")`; + const game_sel = `${games_sel} [data-a-target="claim-prime-offer-card"]:has-text("Claim")`; console.log('Number of free unclaimed games (Prime Gaming):', await page.locator(game_sel).count()); const games = await page.$$(game_sel); // for (let i=1; i<=n; i++) { @@ -106,7 +106,7 @@ try { const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); if (cfg.dryrun) continue; - await (await card.$('button:has-text("Claim game")')).click(); + await (await card.$('button:has-text("Claim")')).click(); db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; notify_games.push({ title, status: 'claimed', url: URL_CLAIM }); // const img = await (await card.$('img.tw-image')).getAttribute('src'); @@ -116,7 +116,7 @@ try { } // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft let n; - const game_sel_ext = `${games_sel} [data-a-target="item-card"]:has(p:text-is("Claim"))`; + const game_sel_ext = `${games_sel} [data-a-target="learn-more-card"]:has(p:text-is("Claim"))`; do { n = await page.locator(game_sel_ext).count(); console.log('Number of free unclaimed games (external stores):', n); From 290fe289d4dac79e595f5bddf9ed54a72dd75a2a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 16 May 2023 13:31:53 +0200 Subject: [PATCH 271/520] pg: fix #142: `PG_CLAIMDLC` locator 'a' resolved to 2 elements --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 4330cd9..8390fd5 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -259,7 +259,7 @@ try { const dlcs = await Promise.all(cards.map(async card => ({ game: await card.locator('.item-card-details__body p').innerText(), title: await card.locator('.item-card-details__body__primary').innerText(), - url: 'https://gaming.amazon.com' + await card.locator('a').getAttribute('href'), + url: 'https://gaming.amazon.com' + await card.locator('a').first().getAttribute('href'), }))); // console.log(dlcs); From 590b01aba2d94b97a5bb4e35411e18b9ef4ed6f5 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 17 May 2023 00:11:35 +0200 Subject: [PATCH 272/520] pg: refactor: use more locators & all() instead of $ --- prime-gaming.js | 51 ++++++++++++++++++++++--------------------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 8390fd5..6f95c23 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -93,20 +93,18 @@ try { } await page.click('button[data-type="Game"]'); - const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; - await page.waitForSelector(games_sel); - console.log('Number of already claimed games (total):', await page.locator(`${games_sel} p:has-text("Collected")`).count()); - const game_sel = `${games_sel} [data-a-target="claim-prime-offer-card"]:has-text("Claim")`; - console.log('Number of free unclaimed games (Prime Gaming):', await page.locator(game_sel).count()); - const games = await page.$$(game_sel); - // for (let i=1; i<=n; i++) { - for (const card of games) { - // const card = page.locator(`:nth-match(${game_sel}, ${i})`); // this will reevaluate after games are claimed and index will be wrong - // const title = await card.locator('h3').first().innerText(); - const title = await (await card.$('.item-card-details__body__primary')).innerText(); + const games = page.locator('div[data-a-target="offer-list-FGWP_FULL"]'); + await games.waitFor(); + console.log('Number of already claimed games (total):', await games.locator('p:has-text("Collected")').count()); + const internal = await games.locator('[data-a-target="claim-prime-offer-card"]:has-text("Claim")').all(); + const external = await games.locator('[data-a-target="learn-more-card"]:has(p:text-is("Claim"))').all(); + console.log('Number of free unclaimed games (Prime Gaming):', internal.length); + // claim games in internal store + for (const card of internal) { + const title = await card.locator('.item-card-details__body__primary').innerText(); console.log('Current free game:', title); if (cfg.dryrun) continue; - await (await card.$('button:has-text("Claim")')).click(); + await card.locator('button:has-text("Claim")').click(); db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; notify_games.push({ title, status: 'claimed', url: URL_CLAIM }); // const img = await (await card.$('img.tw-image')).getAttribute('src'); @@ -114,18 +112,15 @@ try { const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); await card.screenshot({ path: p }); } + console.log('Number of free unclaimed games (external stores):', external.length); // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft - let n; - const game_sel_ext = `${games_sel} [data-a-target="learn-more-card"]:has(p:text-is("Claim"))`; - do { - n = await page.locator(game_sel_ext).count(); - console.log('Number of free unclaimed games (external stores):', n); - const card = await page.$(game_sel_ext); + for (const card of external) { if (!card) break; - const title = await (await card.$('.item-card-details__body__primary')).innerText(); + const title = await card.locator('.item-card-details__body__primary').innerText(); console.log('Current free game:', title); - if (cfg.dryrun) break; // TODO change back to continue, but need different iteration scheme - await (await card.$('text=Claim')).click(); // goes to URL of game, no need to wait + if (cfg.debug) await page.pause(); + if (cfg.dryrun) continue; + await card.locator('text=Claim').click(); // goes to URL of game, no need to wait await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); // Full game for PC [and MAC] on: gog.com, Origin, Legacy Games, EPIC GAMES, Battle.net @@ -238,23 +233,23 @@ try { // await page.pause(); await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); await page.click('button[data-type="Game"]'); - } while (n); + } const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); // await page.screenshot({ path: p, fullPage: true }); // fullPage does not make a difference since scroll not on body but on some element await page.keyboard.press('End'); // scroll to bottom to show all games await page.waitForTimeout(1000); // wait for fade in animation const viewportSize = page.viewportSize(); // current viewport size await page.setViewportSize({...viewportSize, height: 3000}); // increase height, otherwise element screenshot is cut off at the top and bottom - if (notify_games.length) await page.locator(games_sel).screenshot({ path: p }); // screenshot of all claimed games + if (notify_games.length) await games.screenshot({ path: p }); // screenshot of all claimed games if (cfg.pg_claimdlc) { console.log('Trying to claim in-game content...'); await page.click('button[data-type="InGameLoot"]'); - const loot_sel = 'div[data-a-target="offer-list-IN_GAME_LOOT"]'; - await page.waitForSelector(loot_sel); - console.log('Number of already claimed DLC:', await page.locator(`${loot_sel} p:has-text("Collected")`).count()); + const loot = page.locator('div[data-a-target="offer-list-IN_GAME_LOOT"]'); + await loot.waitFor(); + console.log('Number of already claimed DLC:', await loot.locator('p:has-text("Collected")').count()); - const cards = await page.locator(`${loot_sel} [data-a-target="item-card"]:has(p:text-is("Claim"))`).all(); + const cards = await loot.locator('[data-a-target="item-card"]:has(p:text-is("Claim"))').all(); console.log('Number of unclaimed DLC:', cards.length); const dlcs = await Promise.all(cards.map(async card => ({ game: await card.locator('.item-card-details__body p').innerText(), @@ -267,7 +262,7 @@ try { const title = `${dlc.game} - ${dlc.title}`; const url = dlc.url; console.log('Current DLC:', title); - // if (cfg.dryrun) continue; + if (cfg.dryrun) continue; db.data[user][title] ||= { title, time: datetime(), store: 'DLC', status: 'failed: need account linking' }; const notify_game = { title, url }; notify_games.push(notify_game); // status is updated below From c4eb1e03ce4c8e8ab4c14c3b1b74fd0a52d601b2 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 17 May 2023 00:41:37 +0200 Subject: [PATCH 273/520] pg: need elementHandles() instead of all() for internal since it changes --- prime-gaming.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 6f95c23..012aab6 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -96,15 +96,15 @@ try { const games = page.locator('div[data-a-target="offer-list-FGWP_FULL"]'); await games.waitFor(); console.log('Number of already claimed games (total):', await games.locator('p:has-text("Collected")').count()); - const internal = await games.locator('[data-a-target="claim-prime-offer-card"]:has-text("Claim")').all(); + const internal = await games.locator('[data-a-target="claim-prime-offer-card"]:has-text("Claim")').elementHandles(); // can't use .all() here since the list of elements will change after click while we iterate over it const external = await games.locator('[data-a-target="learn-more-card"]:has(p:text-is("Claim"))').all(); console.log('Number of free unclaimed games (Prime Gaming):', internal.length); // claim games in internal store for (const card of internal) { - const title = await card.locator('.item-card-details__body__primary').innerText(); + const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); if (cfg.dryrun) continue; - await card.locator('button:has-text("Claim")').click(); + await (await card.$('button:has-text("Claim")')).click(); db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; notify_games.push({ title, status: 'claimed', url: URL_CLAIM }); // const img = await (await card.$('img.tw-image')).getAttribute('src'); From 60acf747d04f74a9412e93daad4890b9ea04456c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 17 May 2023 00:55:58 +0200 Subject: [PATCH 274/520] pg: scroll to bottom to load all games There may be so many unclaimed games that not all of them are loaded initially. Also relevant to show the correct number of collected games. --- prime-gaming.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prime-gaming.js b/prime-gaming.js index 8390fd5..d5d9453 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -93,6 +93,8 @@ try { } await page.click('button[data-type="Game"]'); + await page.keyboard.press('End'); // scroll to bottom to show all games + await page.waitForLoadState('networkidle'); // wait for all games to be loaded const games_sel = 'div[data-a-target="offer-list-FGWP_FULL"]'; await page.waitForSelector(games_sel); console.log('Number of already claimed games (total):', await page.locator(`${games_sel} p:has-text("Collected")`).count()); @@ -102,6 +104,7 @@ try { // for (let i=1; i<=n; i++) { for (const card of games) { // const card = page.locator(`:nth-match(${game_sel}, ${i})`); // this will reevaluate after games are claimed and index will be wrong + await card.scrollIntoViewIfNeeded(); // const title = await card.locator('h3').first().innerText(); const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); From 240e64ae4cd03a908b4e54ede049c7fdd8e8f048 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 17 May 2023 10:20:17 +0200 Subject: [PATCH 275/520] pg: also use elementHandles() for external --- prime-gaming.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 702d996..3513b50 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -98,8 +98,9 @@ try { const games = page.locator('div[data-a-target="offer-list-FGWP_FULL"]'); await games.waitFor(); console.log('Number of already claimed games (total):', await games.locator('p:has-text("Collected")').count()); - const internal = await games.locator('[data-a-target="claim-prime-offer-card"]:has-text("Claim")').elementHandles(); // can't use .all() here since the list of elements will change after click while we iterate over it - const external = await games.locator('[data-a-target="learn-more-card"]:has(p:text-is("Claim"))').all(); + // can't use .all() since the list of elements via locator will change after click while we iterate over it + const internal = await games.locator('[data-a-target="claim-prime-offer-card"]:has-text("Claim")').elementHandles(); + const external = await games.locator('[data-a-target="learn-more-card"]:has(p:text-is("Claim"))').elementHandles(); console.log('Number of free unclaimed games (Prime Gaming):', internal.length); // claim games in internal store for (const card of internal) { @@ -118,12 +119,12 @@ try { console.log('Number of free unclaimed games (external stores):', external.length); // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft for (const card of external) { - if (!card) break; - const title = await card.locator('.item-card-details__body__primary').innerText(); + // if (!card) continue; + const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; - await card.locator('text=Claim').click(); // goes to URL of game, no need to wait + await (await card.$('text=Claim')).click(); // goes to URL of game, no need to wait await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); // Full game for PC [and MAC] on: gog.com, Origin, Legacy Games, EPIC GAMES, Battle.net From e4ebae3744593e3268b61f520b47bef2febd1a11 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 17 May 2023 11:15:07 +0200 Subject: [PATCH 276/520] pg: `PG_REDEEM`, fixes for gog, #5 --- prime-gaming.js | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 3513b50..bcea0f0 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -163,15 +163,16 @@ try { if (store == 'gog.com') { // await page.goto(`https://redeem.gog.com/v1/bonusCodes/${code}`); // {"reason":"Invalid or no captcha"} await page2.fill('#codeInput', code); - const r = page2.waitForResponse(r => r.url().startsWith('https://redeem.gog.com/')); - await page2.click('[type="submit"]'); - // console.log(await page2.locator('.warning-message').innerText()); - const rt = await (await r).text(); - console.debug(` Response: ${rt}`); + // wait for responses before clicking on Continue and then Redeem + // first there are requests with OPTIONS and GET to https://redeem.gog.com/v1/bonusCodes/XYZ?language=de-DE + const r1 = page2.waitForResponse(r => r.request().method() == 'GET' && r.url().startsWith('https://redeem.gog.com/')); + await page2.click('[type="submit"]'); // click Continue + // console.log(await page2.locator('.warning-message').innerText()); // does not exist if there is no warning + const r1t = await (await r1).text(); + const reason = JSON.parse(r1t).reason; // {"reason":"Invalid or no captcha"} // {"reason":"code_used"} // {"reason":"code_not_found"} - const reason = JSON.parse(rt).reason; if (reason && reason.includes('captcha')) { redeem_action = 'redeem (got captcha)'; console.error(' Got captcha; could not redeem!'); @@ -183,7 +184,20 @@ try { console.error(' Code was not found!'); } else { // TODO not logged in? need valid unused code to test. redeem_action = 'redeemed?'; - console.log(' Redeemed successfully? Please report your Response from above (if it is new) in https://github.com/vogler/free-games-claimer/issues/5'); + console.log(' Redeemed successfully? Please report your Responses (if new) in https://github.com/vogler/free-games-claimer/issues/5'); + console.debug(` Response 1: ${r1t}`); + // then after the click on Redeem there is a POST request which should return {} if claimed successfully + const r2 = page2.waitForResponse(r => r.request().method() == 'POST' && r.url().startsWith('https://redeem.gog.com/')); + await page2.click('[type="submit"]'); // click Redeem + const r2t = await (await r2).text(); + console.debug(` Response 2: ${r2t}`); + if (r2t == '{}') { + redeem_action = 'redeemed'; + console.log(' Redeemed successfully.'); + } else { + redeem_action = 'redeemed?'; + console.log(' Unknown Response 2 - please report in https://github.com/vogler/free-games-claimer/issues/5'); + } // db.data[user][title].status = 'claimed and redeemed'; } } else if (store == 'microsoft games') { From 819e4cc57e8645588c70ca786f4dc64e90ac606d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 17 May 2023 11:28:31 +0200 Subject: [PATCH 277/520] pg: `PG_REDEEM` tested legacy games successfully, #5 --- prime-gaming.js | 1 - 1 file changed, 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index bcea0f0..926bbdd 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -224,7 +224,6 @@ try { } } } else if (store == 'legacy games') { - console.error(` Redeem on ${store} not yet tested!`); await page2.fill('[name=coupon_code]', code); await page2.fill('[name=email]', cfg.pg_email); // TODO option for sep. email? await page2.fill('[name=email_validate]', cfg.pg_email); From 8ee63c7a35bb3ed43ab7f59dfb121c89bb7c9368 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 17 May 2023 11:29:45 +0200 Subject: [PATCH 278/520] pg: support `RECORD=1` --- prime-gaming.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/prime-gaming.js b/prime-gaming.js index 926bbdd..2e75d0c 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -19,6 +19,8 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, 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/pg-${datetime()}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools }); // TODO test if needed From 368229be096ed360c4afde153048c61669fc5dd0 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 17 May 2023 11:35:10 +0200 Subject: [PATCH 279/520] pg: screenshot prep only if needed --- prime-gaming.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 2e75d0c..83c1592 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -253,13 +253,16 @@ try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); await page.click('button[data-type="Game"]'); } - const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); - // await page.screenshot({ path: p, fullPage: true }); // fullPage does not make a difference since scroll not on body but on some element - await page.keyboard.press('End'); // scroll to bottom to show all games - await page.waitForTimeout(1000); // wait for fade in animation - const viewportSize = page.viewportSize(); // current viewport size - await page.setViewportSize({...viewportSize, height: 3000}); // increase height, otherwise element screenshot is cut off at the top and bottom - if (notify_games.length) await games.screenshot({ path: p }); // screenshot of all claimed games + + if (notify_games.length) { + const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); + // await page.screenshot({ path: p, fullPage: true }); // fullPage does not make a difference since scroll not on body but on some element + await page.keyboard.press('End'); // scroll to bottom to show all games + await page.waitForTimeout(1000); // wait for fade in animation + const viewportSize = page.viewportSize(); // current viewport size + await page.setViewportSize({...viewportSize, height: 3000}); // increase height, otherwise element screenshot is cut off at the top and bottom + await games.screenshot({ path: p }); // screenshot of all claimed games + } if (cfg.pg_claimdlc) { console.log('Trying to claim in-game content...'); From 0d074d0397e3349e1f3bfaa5713f559df6976ac5 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 18 May 2023 16:03:24 +0200 Subject: [PATCH 280/520] pg: dlc: scroll to end of page until all are loaded, #55 --- prime-gaming.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 83c1592..3160a96 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -269,7 +269,21 @@ try { await page.click('button[data-type="InGameLoot"]'); const loot = page.locator('div[data-a-target="offer-list-IN_GAME_LOOT"]'); await loot.waitFor(); - console.log('Number of already claimed DLC:', await loot.locator('p:has-text("Collected")').count()); + + process.stdout.write('Loading all DLCs on page...'); + let n1 = 0; + let n2 = 0; + do { + n1 = n2; + n2 = await loot.locator('[data-a-target="item-card"]').count(); + // console.log(n2); + process.stdout.write(` ${n2}`); + await page.keyboard.press('End'); // scroll to bottom to show all dlcs + await page.waitForLoadState('networkidle'); // did not wait for dlcs to be loaded + await page.waitForTimeout(1000); + } while (n2 > n1); + + console.log('\nNumber of already claimed DLC:', await loot.locator('p:has-text("Collected")').count()); const cards = await loot.locator('[data-a-target="item-card"]:has(p:text-is("Claim"))').all(); console.log('Number of unclaimed DLC:', cards.length); @@ -284,6 +298,7 @@ try { const title = `${dlc.game} - ${dlc.title}`; const url = dlc.url; console.log('Current DLC:', title); + if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; db.data[user][title] ||= { title, time: datetime(), store: 'DLC', status: 'failed: need account linking' }; const notify_game = { title, url }; From 7fb872608d6ba015653d1944e9c1086532675786 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 18 May 2023 17:32:02 +0200 Subject: [PATCH 281/520] pg: dlc: try/catch for each dlc, #55 --- prime-gaming.js | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 3160a96..372d072 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -264,6 +264,7 @@ try { await games.screenshot({ path: p }); // screenshot of all claimed games } + // https://github.com/vogler/free-games-claimer/issues/55 if (cfg.pg_claimdlc) { console.log('Trying to claim in-game content...'); await page.click('button[data-type="InGameLoot"]'); @@ -303,27 +304,32 @@ try { db.data[user][title] ||= { title, time: datetime(), store: 'DLC', status: 'failed: need account linking' }; const notify_game = { title, url }; notify_games.push(notify_game); // status is updated below - await page.goto(url, { waitUntil: 'domcontentloaded' }); - // most games have a button 'Get in-game content' - // epic-games: Fall Guys: Claim now -> Continue -> Go to Epic Games (despite account linked and logged into epic-games) -> not tied to account but via some cookie? - await Promise.any([page.click('button:has-text("Get in-game content")'), page.click('button:has-text("Claim your gift")'), page.click('button:has-text("Claim now")').then(() => page.click('button:has-text("Continue")'))]); - page.click('button:has-text("Continue")').catch(_ => { }); - const linkAccountModal = page.locator('[data-a-target="LinkAccountModal"]'); - const linkAccountButton = linkAccountModal.locator('[data-a-target="LinkAccountButton"]'); - if (await linkAccountButton.count()) { - console.error(' Missing account linking:', await linkAccountButton.innerText()); - } else if(await page.locator('text=Link game account').count()) { // epic-games only? - console.error(' Missing account linking:', await page.locator('button[data-a-target="gms-cta"]').innerText()); - } else { - const code = await page.inputValue('input[type="text"]'); - console.log(' Code to redeem game:', code); - db.data[user][title].code = code; - db.data[user][title].status = 'claimed'; - // notify_game.status = `${redeem_action} ${code} on ${store}`; + try { + await page.goto(url, { waitUntil: 'domcontentloaded' }); + // most games have a button 'Get in-game content' + // epic-games: Fall Guys: Claim now -> Continue -> Go to Epic Games (despite account linked and logged into epic-games) -> not tied to account but via some cookie? + await Promise.any([page.click('button:has-text("Get in-game content")'), page.click('button:has-text("Claim your gift")'), page.click('button:has-text("Claim now")').then(() => page.click('button:has-text("Continue")'))]); + page.click('button:has-text("Continue")').catch(_ => { }); + const linkAccountModal = page.locator('[data-a-target="LinkAccountModal"]'); + const linkAccountButton = linkAccountModal.locator('[data-a-target="LinkAccountButton"]'); + if (await linkAccountButton.count()) { + console.error(' Missing account linking:', await linkAccountButton.innerText()); + } else if(await page.locator('text=Link game account').count()) { // epic-games only? + console.error(' Missing account linking:', await page.locator('button[data-a-target="gms-cta"]').innerText()); + } else { + const code = await page.inputValue('input[type="text"]'); + console.log(' Code to redeem game:', code); + db.data[user][title].code = code; + db.data[user][title].status = 'claimed'; + // notify_game.status = `${redeem_action} ${code} on ${store}`; + } + // await page.pause(); + } catch (error) { + console.error(error); + } finally { + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); + await page.click('button[data-type="InGameLoot"]'); } - // await page.pause(); - await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); - await page.click('button[data-type="InGameLoot"]'); } } } catch (error) { From feadfc5acf6dfe1fc05493c8c2eaa1d84e7696d0 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 18 May 2023 18:02:11 +0200 Subject: [PATCH 282/520] pg: redeem: gog: status = 'claimed and redeemed' --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 372d072..a772e3e 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -196,11 +196,11 @@ try { if (r2t == '{}') { redeem_action = 'redeemed'; console.log(' Redeemed successfully.'); + db.data[user][title].status = 'claimed and redeemed'; } else { redeem_action = 'redeemed?'; console.log(' Unknown Response 2 - please report in https://github.com/vogler/free-games-claimer/issues/5'); } - // db.data[user][title].status = 'claimed and redeemed'; } } else if (store == 'microsoft games') { console.error(` Redeem on ${store} not yet implemented!`); From 00e213cf5050bc01f5d1989ce0c45feed7ea883d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 2 Jun 2023 00:11:10 +0200 Subject: [PATCH 283/520] ue: support RECORD=1 --- unrealengine.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/unrealengine.js b/unrealengine.js index 25be32e..87651d5 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -24,6 +24,8 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 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/ue-${datetime()}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools }); await stealth(context); From f906bef2a82ba87b9748e8659c01fd9759360d91 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 2 Jun 2023 00:14:21 +0200 Subject: [PATCH 284/520] ue: fix 'Sign In'/user detection, #44 --- unrealengine.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/unrealengine.js b/unrealengine.js index 87651d5..af3fa51 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -3,7 +3,7 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; import path from 'path'; -import { existsSync, writeFileSync } from 'fs'; +import { writeFileSync } from 'fs'; import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; @@ -43,7 +43,9 @@ try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto - while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { + await page.waitForResponse(r => r.request().method() == 'POST' && r.url().startsWith('https://graphql.unrealengine.com/ue/graphql')); + + while (await page.locator('.display-name').count() == 0) { console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); if (cfg.novnc_port) console.info(`Open http://localhost:${cfg.novnc_port} to login inside the docker container.`); if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in @@ -79,11 +81,11 @@ try { process.exit(1); } } - await page.waitForURL(URL_CLAIM); + await page.waitForURL('**unrealengine.com/marketplace/**'); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } await page.waitForTimeout(1000); - user = await page.locator('.user-label').first().innerHTML(); + user = await page.locator('.display-name').first().innerHTML(); console.log(`Signed in as ${user}`); db.data[user] ||= {}; @@ -110,10 +112,10 @@ try { } if (await p.locator('.btn .in-cart').count()) { console.log(' ↳ Already in cart'); - continue; + } else { + await p.locator('.btn .add').click(); + console.log(' ↳ Added to cart'); } - await p.locator('.btn .add').click(); - console.log(' ↳ Added to cart'); ids.push(id); } if (!ids.length) { From 2ceccdae6baa8fa990421fcbd5976d3b0b6640bc Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 2 Jun 2023 00:30:31 +0200 Subject: [PATCH 285/520] ue: add as experimental to README.md, #44 --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index c10d726..047d0ce 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ Claims free games periodically on - [Amazon Prime Gaming](https://gaming.amazon.com) - [GOG](https://www.gog.com) - [Xbox Live Games with Gold](https://www.xbox.com/en-US/live/gold#gameswithgold) - planned +- [Unreal Engine (Assets)](https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910) ([experimental](https://github.com/vogler/free-games-claimer/issues/44), same login as Epic Games) Pull requests welcome :) From d3d22b1582a1ec9f6df32f0438d3828eab03bebb Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 2 Jun 2023 00:47:48 +0200 Subject: [PATCH 286/520] ue: wait 2s before checking cart, #44 --- unrealengine.js | 1 + 1 file changed, 1 insertion(+) diff --git a/unrealengine.js b/unrealengine.js index af3fa51..871c82e 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -121,6 +121,7 @@ try { if (!ids.length) { console.log('Nothing to claim'); } else { + await page.waitForTimeout(2000); const price = (await page.locator('.shopping-cart .total .price').innerText()).split(' '); console.log('Price: ', price[1], 'instead of', price[0]); if (price[1] != '0') { From 7e4770b846135f7d0c36ac101914d35506f79474 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 2 Jun 2023 00:52:11 +0200 Subject: [PATCH 287/520] ue: notify on 'Price is not 0!' + ask to report, #44 --- unrealengine.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/unrealengine.js b/unrealengine.js index 871c82e..2993fea 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -125,7 +125,9 @@ try { const price = (await page.locator('.shopping-cart .total .price').innerText()).split(' '); console.log('Price: ', price[1], 'instead of', price[0]); if (price[1] != '0') { - console.error('Price is not 0! Exit!'); + const err = 'Price is not 0! Exit! Please report.' + console.error(err); + notify('unrealengine: ' + err); process.exit(1); } // await page.pause(); From 0b9d5d0b6370552c4f065c7da0fd393f20690146 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 22 Jun 2023 17:16:35 +0200 Subject: [PATCH 288/520] pg: comments --- prime-gaming.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index a772e3e..c731efa 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -153,7 +153,7 @@ try { const code = await page.inputValue('input[type="text"]'); console.log(' Code to redeem game:', code); if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ - redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); + redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); // full text: Click here to enter your redemption code. } console.log(' URL to redeem game:', redeem[store]); db.data[user][title].code = code; @@ -254,7 +254,7 @@ try { await page.click('button[data-type="Game"]'); } - if (notify_games.length) { + if (notify_games.length) { // make screenshot of all games if something was claimed const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); // await page.screenshot({ path: p, fullPage: true }); // fullPage does not make a difference since scroll not on body but on some element await page.keyboard.press('End'); // scroll to bottom to show all games From 85513031f2d03478a9ed833cc4e9fa9a8fd24ed4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 23 Jun 2023 01:21:42 +0200 Subject: [PATCH 289/520] =?UTF-8?q?pg:=20external:=20fix=20lost=20elementH?= =?UTF-8?q?andle=C2=A0error=20due=20to=20navigation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit elementHandle.$: Protocol error (Page.adoptNode) --- prime-gaming.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index c731efa..c99eec0 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -102,7 +102,7 @@ try { console.log('Number of already claimed games (total):', await games.locator('p:has-text("Collected")').count()); // can't use .all() since the list of elements via locator will change after click while we iterate over it const internal = await games.locator('[data-a-target="claim-prime-offer-card"]:has-text("Claim")').elementHandles(); - const external = await games.locator('[data-a-target="learn-more-card"]:has(p:text-is("Claim"))').elementHandles(); + const external = games.locator('[data-a-target="learn-more-card"]:has(p:text-is("Claim"))'); // using .elementHandles() here would lead to error due to page navigation: elementHandle.$: Protocol error (Page.adoptNode) console.log('Number of free unclaimed games (Prime Gaming):', internal.length); // claim games in internal store for (const card of internal) { @@ -118,9 +118,9 @@ try { const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); await card.screenshot({ path: p }); } - console.log('Number of free unclaimed games (external stores):', external.length); + console.log('Number of free unclaimed games (external stores):', await external.count()); // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft - for (const card of external) { + for (const card of await external.elementHandles()) { // TODO refactor (result of external locator changes with each iteration) // if (!card) continue; const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); From f0e15b5c7cc24343192b14e9e344b26166cc2304 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 23 Jun 2023 01:24:06 +0200 Subject: [PATCH 290/520] pg: fix #158 - page for gog (and ?) changed (button, store, code) The claim page for Legacy Games is still unchanged and works with the old code. --- prime-gaming.js | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index c99eec0..7108e58 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -127,12 +127,28 @@ try { if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; await (await card.$('text=Claim')).click(); // goes to URL of game, no need to wait - await Promise.any([page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation - const store_text = await (await page.$('[data-a-target="hero-header-subtitle"]')).innerText(); - // Full game for PC [and MAC] on: gog.com, Origin, Legacy Games, EPIC GAMES, Battle.net - // 3 Full PC Games on Legacy Games - const store = store_text.toLowerCase().replace(/.* on /, ''); + await Promise.any([page.click('button:has-text("Get game")'), page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation + + // TODO would be simpler than the below, but will block for linked stores without code + // const redeem_text = await page.textContent('text=/ code on /'); // FAQ: How do I redeem my code? + // console.log(' ', redeem_text); + // // Before July 29, 2023, redeem your offer code on GOG.com. + // // Before July 1, 2023, redeem your product code on Legacy Games. + // let store = redeem_text.toLowerCase().replace(/.* on /, '').slice(0, -1); + + let store = ''; + const store_text = await page.$('[data-a-target="hero-header-subtitle"]'); // worked fine for every store, but now no longer works for gog.com + if (store_text) { // legacy games, ? + const store_texts = await store_text.innerText(); + // Full game for PC [and MAC] on: Legacy Games, Origin, EPIC GAMES, Battle.net; alt: 3 Full PC Games on Legacy Games + store = store_texts.toLowerCase().replace(/.* on /, ''); + } else { // gog.com, ? + // $('[data-a-target="DescriptionItemDetails"]').innerText is e.g. 'Prey for PC on GOG.com.' but does not work for Legacy Games + const item_text = await page.innerText('[data-a-target="DescriptionItemDetails"]'); + store = item_text.toLowerCase().replace(/.* on /, '').slice(0, -1); + } console.log(' External store:', store); + const url = page.url().split('?')[0]; db.data[user][title] ||= { title, time: datetime(), url, store }; const notify_game = { title, url }; @@ -150,7 +166,7 @@ try { 'legacy games': 'https://www.legacygames.com/primedeal', }; if (store in redeem) { // did not work for linked origin: && !await page.locator('div:has-text("Successfully Claimed")').count() - const code = await page.inputValue('input[type="text"]'); + const code = await Promise.any([page.inputValue('input[type="text"]'), page.textContent('[data-a-target="ClaimStateClaimCodeContent"]').then(s => s.replace('Your code: ', ''))]); // input: Legacy Games; text: gog.com console.log(' Code to redeem game:', code); if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); // full text: Click here to enter your redemption code. From acc337a4f99606dd3a786dc2cb33eb27623bef95 Mon Sep 17 00:00:00 2001 From: Omair Date: Wed, 24 May 2023 20:12:00 +0100 Subject: [PATCH 291/520] xbox: add config values --- config.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config.js b/config.js index 32974e1..b420a5a 100644 --- a/config.js +++ b/config.js @@ -37,6 +37,10 @@ export const cfg = { gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD, gog_newsletter: process.env.GOG_NEWSLETTER == '1', // do not unsubscribe from newsletter after claiming a game // OTP only via GOG_EMAIL, can't add app... + // auth xbox + xbox_email: process.env.XBOX_EMAIL || process.env.EMAIL, + xbox_password: process.env.XBOX_PASSWORD || process.env.PASSWORD, + xbox_otpkey: process.env.XBOX_OTPKEY, // TODO unimplemented // experimmental - likely to change pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores From a7dcfe72ccea6434b4f1b6b04ddbb61a96c011fb Mon Sep 17 00:00:00 2001 From: Omair Date: Wed, 24 May 2023 20:12:33 +0100 Subject: [PATCH 292/520] xbox: add implementation for xbox games with gold --- config.js | 3 +- xbox.js | 256 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 xbox.js diff --git a/config.js b/config.js index b420a5a..b1e31e6 100644 --- a/config.js +++ b/config.js @@ -40,8 +40,7 @@ export const cfg = { // auth xbox xbox_email: process.env.XBOX_EMAIL || process.env.EMAIL, xbox_password: process.env.XBOX_PASSWORD || process.env.PASSWORD, - xbox_otpkey: process.env.XBOX_OTPKEY, // TODO unimplemented - + xbox_otpkey: process.env.XBOX_OTPKEY, // experimmental - likely to change pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores pg_claimdlc: process.env.PG_CLAIMDLC == '1', // prime-gaming: claim in-game content diff --git a/xbox.js b/xbox.js new file mode 100644 index 0000000..501f4f1 --- /dev/null +++ b/xbox.js @@ -0,0 +1,256 @@ +import { firefox } from "playwright-firefox"; // stealth plugin needs no outdated playwright-extra +import { authenticator } from "otplib"; +import { + datetime, + handleSIGINT, + html_game_list, + jsonDb, + notify, + prompt, +} from "./util.js"; +import path from "path"; +import { existsSync, writeFileSync } from "fs"; +import { cfg } from "./config.js"; + +// ### SETUP +const URL_CLAIM = "https://www.xbox.com/en-US/live/gold"; // #gameswithgold"; + +console.log(datetime(), "started checking xbox"); + +const db = await jsonDb("xbox.json"); +db.data ||= {}; + +handleSIGINT(); + +// https://playwright.dev/docs/auth#multi-factor-authentication +const context = await firefox.launchPersistentContext(cfg.dir.browser, { + headless: cfg.headless, + viewport: { width: cfg.width, height: cfg.height }, + locale: "en-US", // ignore OS locale to be sure to have english text for locators -> done via /en in URL +}); + +if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); + +const page = context.pages().length + ? context.pages()[0] + : await context.newPage(); // should always exist + +const notify_games = []; +let user; + +main(); + +async function main() { + try { + await performLogin(); + await getAndSaveUser(); + await redeemFreeGames(); + } catch (error) { + console.error(error); + process.exitCode ||= 1; + if (error.message && process.exitCode != 130) + notify(`xbox failed: ${error.message.split("\n")[0]}`); + } finally { + await db.write(); // write out json db + if (notify_games.filter((g) => g.status != "existed").length) { + // don't notify if all were already claimed + notify(`xbox (${user}):
${html_game_list(notify_games)}`); + } + await context.close(); + } +} + +async function performLogin() { + await page.goto(URL_CLAIM, { waitUntil: "domcontentloaded" }); // default 'load' takes forever + + const signInLocator = page + .getByRole("link", { + name: "Sign in to your account", + }) + .first(); + const usernameLocator = page + .getByRole("button", { + name: "Account manager for", + }) + .first(); + + await Promise.any([signInLocator.waitFor(), usernameLocator.waitFor()]); + + if (await usernameLocator.isVisible()) { + return; // logged in using saved cookie + } else if (await signInLocator.isVisible()) { + console.error("Not signed in anymore."); + await signInLocator.click(); + await signInToXbox(); + } else { + console.error("lost! where am i?"); + } +} + +async function signInToXbox() { + page.waitForLoadState("domcontentloaded"); + if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in + console.info(`Login timeout is ${cfg.login_timeout / 1000} seconds!`); + + // ### FETCH EMAIL/PASS + if (cfg.xbox_email && cfg.xbox_password) + console.info("Using email and password from environment."); + else + console.info( + "Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode)." + ); + const email = cfg.xbox_email || (await prompt({ message: "Enter email" })); + const password = + email && + (cfg.xbox_password || + (await prompt({ + type: "password", + message: "Enter password", + }))); + // ### FILL IN EMAIL/PASS + if (email && password) { + const usernameLocator = page + .getByPlaceholder("Email, phone, or Skype") + .first(); + const passwordLocator = page.getByPlaceholder("Password").first(); + + await Promise.any([ + usernameLocator.waitFor(), + passwordLocator.waitFor(), + ]); + + // username may already be saved from before, if so, skip to filling in password + if (await page.getByPlaceholder("Email, phone, or Skype").isVisible()) { + await usernameLocator.fill(email); + await page.getByRole("button", { name: "Next" }).click(); + } + + await passwordLocator.fill(password); + await page.getByRole("button", { name: "Sign in" }).click(); + + // handle MFA, but don't await it + page.locator('input[name="otc"]') + .waitFor() + .then(async () => { + console.log("Two-Step Verification - Enter security code"); + console.log( + await page + .locator('div[data-bind="text: description"]') + .innerText() + ); + const otp = + (cfg.xbox_otpkey && + authenticator.generate(cfg.xbox_otpkey)) || + (await prompt({ + type: "text", + message: "Enter two-factor sign in code", + validate: (n) => + n.toString().length == 6 || + "The code must be 6 digits!", + })); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + await page.type('input[name="otc"]', otp.toString()); + await page + .getByLabel("Don't ask me again on this device") + .check(); // Trust this Browser + await page.getByRole("button", { name: "Verify" }).click(); + }) + .catch((_) => {}); + + // Trust this browser, but don't await it + page.getByLabel("Don't show this again") + .waitFor() + .then(async () => { + await page.getByLabel("Don't show this again").check(); + await page.getByRole("button", { name: "Yes" }).click(); + }) + .catch((_) => {}); + } else { + console.log("Waiting for you to login in the browser."); + await notify( + "xbox: no longer signed in and not enough options set for automatic login." + ); + if (cfg.headless) { + console.log( + "Run `SHOW=1 node xbox` to login in the opened browser." + ); + await context.close(); + process.exit(1); + } + } + + // ### VERIFY SIGNED IN + await page.waitForURL(`${URL_CLAIM}**`); + + if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); +} + +async function getAndSaveUser() { + user = await page.locator("#mectrl_currentAccount_primary").innerHTML(); + console.log(`Signed in as '${user}'`); + db.data[user] ||= {}; +} + +async function redeemFreeGames() { + const monthlyGamesLocator = await page.locator(".f-size-large").all(); + + const monthlyGamesPageLinks = await Promise.all( + monthlyGamesLocator.map( + async (el) => await el.locator("a").getAttribute("href") + ) + ); + console.log("Free games:", monthlyGamesPageLinks); + + for (const url of monthlyGamesPageLinks) { + await page.goto(url); + + const title = await page.locator("h1").first().innerText(); + const game_id = page.url().split("/").pop(); + db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! + console.log("Current free game:", title); + const notify_game = { title, url, status: "failed" }; + notify_games.push(notify_game); // status is updated below + + // SELECTORS + const getBtnLocator = page.getByText("GET", { exact: true }).first(); + const installToLocator = page + .getByText("INSTALL TO", { exact: true }) + .first(); + + await Promise.any([ + getBtnLocator.waitFor(), + installToLocator.waitFor(), + ]); + + if (await installToLocator.isVisible()) { + console.log(" Already in library! Nothing to claim."); + notify_game.status = "existed"; + db.data[user][game_id].status ||= "existed"; // does not overwrite claimed or failed + } else if (await getBtnLocator.isVisible()) { + console.log(" Not in library yet! Click GET."); + await getBtnLocator.click(); + + // wait for popup + await page + .locator('iframe[name="purchase-sdk-hosted-iframe"]') + .waitFor(); + const popupLocator = page.frameLocator( + "[name=purchase-sdk-hosted-iframe]" + ); + + const finalGetBtnLocator = popupLocator.getByText("GET"); + await finalGetBtnLocator.waitFor(); + await finalGetBtnLocator.click(); + + await page.getByText("Thank you for your purchase.").waitFor(); + notify_game.status = "claimed"; + db.data[user][game_id].status = "claimed"; + db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time + console.log(" Claimed successfully!"); + } + + // notify_game.status = db.data[user][game_id].status; // claimed or failed + + // const p = path.resolve(cfg.dir.screenshots, playstation-plus', `${game_id}.png`); + // if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... + } +} From 8c6ff57054894e99a8f671c271a847bd8ac3394b Mon Sep 17 00:00:00 2001 From: Omair Date: Mon, 26 Jun 2023 15:46:41 -0400 Subject: [PATCH 293/520] xbox: update readme and dockerfile with xbox info/scripts --- Dockerfile | 2 +- README.md | 15 +++++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index ebad9eb..5d8ee90 100644 --- a/Dockerfile +++ b/Dockerfile @@ -77,4 +77,4 @@ ENV SHOW 1 # Script to setup display server & VNC is always executed. ENTRYPOINT ["docker-entrypoint.sh"] # Default command to run. This is replaced by appending own command, e.g. `docker run ... node prime-gaming` to only run this script. -CMD node epic-games; node prime-gaming; node gog +CMD node epic-games; node prime-gaming; node gog; node xbox; diff --git a/README.md b/README.md index 047d0ce..3938546 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Claims free games periodically on - [Epic Games Store](https://www.epicgames.com/store/free-games) - [Amazon Prime Gaming](https://gaming.amazon.com) - [GOG](https://www.gog.com) -- [Xbox Live Games with Gold](https://www.xbox.com/en-US/live/gold#gameswithgold) - planned +- [Xbox Live Games with Gold](https://www.xbox.com/en-US/live/gold#gameswithgold) ([experimental](https://github.com/vogler/free-games-claimer/issues/19)) - [Unreal Engine (Assets)](https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910) ([experimental](https://github.com/vogler/free-games-claimer/issues/44), same login as Epic Games) Pull requests welcome :) @@ -24,7 +24,7 @@ Easy option: [install Docker](https://docs.docker.com/get-docker/) (or [podman]( ``` docker run --rm -it -p 6080:6080 -v fgc:/fgc/data --pull=always ghcr.io/vogler/free-games-claimer ``` -This will run `node epic-games; node prime-gaming; node gog` - if you only want to claim games for one of the stores, you can override the default command by appending e.g. `node epic-games` at the end of the `docker run` command, or if you want several `bash -c "node epic-games.js; node gog.js"`. +This will run `node epic-games; node prime-gaming; node gog; node xbox;` - if you only want to claim games for one of the stores, you can override the default command by appending e.g. `node epic-games` at the end of the `docker run` command, or if you want several `bash -c "node epic-games.js; node gog.js"`. Data (including json files with claimed games, codes to redeem, screenshots) is stored in the Docker volume `fgc`.
@@ -86,6 +86,9 @@ Available options/variables and their default values: | GOG_EMAIL | | GOG email for login. Overrides EMAIL. | | GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | | GOG_NEWSLETTER | 0 | Do not unsubscribe from newsletter after claiming a game if 1. | +| XBOX_EMAIL | | Xbox email for login. Overrides EMAIL. | +| XBOX_PASSWORD | | Xbox password for login. Overrides PASSWORD. | +| XBOX_OTPKEY | | Xbox MFA OTP key. | See `config.js` for all options. @@ -113,6 +116,7 @@ To get the OTP key, it is easiest to follow the store's guide for adding an auth - **Epic Games**: visit [password & security](https://www.epicgames.com/account/password), enable 'third-party authenticator app', copy the 'Manual Entry Key' and use it to set `EG_OTPKEY`. - **Prime Gaming**: visit Amazon 'Your Account › Login & security', 2-step verification › Manage › Add new app › Can't scan the barcode, copy the bold key and use it to set `PG_OTPKEY` - **GOG**: only offers OTP via email +- **Xbox**: visit [additional security](https://account.live.com/proofs/manage/additional) > Add a new way to sign in or verify > Use an app > Set up a different Authenticator app > I can't scan the bar code > copy the bold key and use it to set `XBOX_OTPKEY` Beware that storing passwords and OTP keys as clear text may be a security risk. Use a unique/generated password! TODO: maybe at least offer to base64 encode for storage. @@ -130,13 +134,16 @@ Claiming the Amazon Games works out-of-the-box, however, for games on external s Keys and URLs are printed to the console, included in notifications and saved in `data/prime-gaming.json`. A screenshot of the page with the key is also saved to `data/screenshots`. [TODO](https://github.com/vogler/free-games-claimer/issues/5): ~~redeem keys on external stores.~~ +### Xbox Games With Gold +Run `node xbox` (locally or in docker). + ### Run periodically #### How often? Epic Games usually has two free games *every week*, before Christmas every day. Prime Gaming has new games *every month* or more often during Prime days. -GOG usually has one new game every couples of weeks. +GOG usually has one new game every couples of weeks. Xbox usually has two games *every month*. -It is save to run the scripts every day. +It is safe to run the scripts every day. #### How to schedule? The container/scripts will claim currently available games and then exit. From 04bd56cd77e1d87b99e5dfb79ea684cfc24f201c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 23 Jun 2023 01:32:39 +0200 Subject: [PATCH 294/520] pg: legacy games: status = claimed and redeemed --- prime-gaming.js | 1 + 1 file changed, 1 insertion(+) diff --git a/prime-gaming.js b/prime-gaming.js index 7108e58..878ba50 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -249,6 +249,7 @@ try { await page2.click('[type="submit"]'); redeem_action = 'redeemed?'; console.log(' Redeemed successfully? Please report problems in https://github.com/vogler/free-games-claimer/issues/5'); + db.data[user][title].status = 'claimed and redeemed'; } else { console.error(` Redeem on ${store} not yet implemented!`); } From 2e8a731573f1f6c2b222cc1b21dcd3f1c3954346 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 13 Jul 2023 10:56:12 +0200 Subject: [PATCH 295/520] pg: update selectors for internal/external games, fixes #164 --- prime-gaming.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 878ba50..695951e 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -97,12 +97,13 @@ try { await page.click('button[data-type="Game"]'); await page.keyboard.press('End'); // scroll to bottom to show all games await page.waitForLoadState('networkidle'); // wait for all games to be loaded + await page.waitForTimeout(2000); // TODO networkidle wasn't enough to load all already collected games const games = page.locator('div[data-a-target="offer-list-FGWP_FULL"]'); await games.waitFor(); console.log('Number of already claimed games (total):', await games.locator('p:has-text("Collected")').count()); // can't use .all() since the list of elements via locator will change after click while we iterate over it - const internal = await games.locator('[data-a-target="claim-prime-offer-card"]:has-text("Claim")').elementHandles(); - const external = games.locator('[data-a-target="learn-more-card"]:has(p:text-is("Claim"))'); // using .elementHandles() here would lead to error due to page navigation: elementHandle.$: Protocol error (Page.adoptNode) + const internal = await games.locator('.item-card__action:has([data-a-target="FGWPOffer"])').elementHandles(); + const external = games.locator('.item-card__action:has([data-a-target="ExternalOfferClaim"])'); // using .elementHandles() here would lead to error due to page navigation: elementHandle.$: Protocol error (Page.adoptNode) console.log('Number of free unclaimed games (Prime Gaming):', internal.length); // claim games in internal store for (const card of internal) { From a90f90afc098b3043ee1ef90e57164b748d3b6dc Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 13 Jul 2023 11:28:18 +0200 Subject: [PATCH 296/520] pg: PG_REDEEM: only pause if DEBUG=1 Fixes https://github.com/vogler/free-games-claimer/issues/5#issuecomment-1527063819 --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 695951e..462fe49 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -254,7 +254,7 @@ try { } else { console.error(` Redeem on ${store} not yet implemented!`); } - await page2.pause(); + if (cfg.debug) await page.pause(); await page2.close(); } notify_game.status = `${redeem_action} ${code} on ${store}`; From 22f673282b12d3f047c3c25e4d8650e299d9ae74 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 13 Jul 2023 11:30:48 +0200 Subject: [PATCH 297/520] pg: fix a90f90afc098b3043ee1ef90e57164b748d3b6dc --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 462fe49..d9fcd7e 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -254,7 +254,7 @@ try { } else { console.error(` Redeem on ${store} not yet implemented!`); } - if (cfg.debug) await page.pause(); + if (cfg.debug) await page2.pause(); await page2.close(); } notify_game.status = `${redeem_action} ${code} on ${store}`; From b5ead8ea21129830596f7058775c97e9832813e9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 20 Jul 2023 16:10:50 +0200 Subject: [PATCH 298/520] SCREENSHOTS_DIR=0 to disable screenshots, fixes #172 --- config.js | 2 +- epic-games.js | 8 +++++--- gog.js | 8 ++++---- prime-gaming.js | 13 ++++++------- unrealengine.js | 10 +++++----- util.js | 3 +++ 6 files changed, 24 insertions(+), 20 deletions(-) diff --git a/config.js b/config.js index 32974e1..d863d8d 100644 --- a/config.js +++ b/config.js @@ -20,7 +20,7 @@ export const cfg = { get dir() { // avoids ReferenceError: Cannot access 'dataDir' before initialization return { browser: process.env.BROWSER_DIR || dataDir('browser'), // for multiple accounts or testing - screenshots: process.env.SCREENSHOTS_DIR || dataDir('screenshots'), // if not wanted: /dev/null + screenshots: process.env.SCREENSHOTS_DIR || dataDir('screenshots'), // set to 0 to disable screenshots } }, // auth epic-games diff --git a/epic-games.js b/epic-games.js index 37310ea..314b349 100644 --- a/epic-games.js +++ b/epic-games.js @@ -2,9 +2,11 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdate import { authenticator } from 'otplib'; import path from 'path'; import { existsSync, writeFileSync } from 'fs'; -import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; +import { resolve, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; +const screenshot = (...a) => resolve(cfg.dir.screenshots, 'epic-games', ...a); + const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; @@ -218,13 +220,13 @@ try { console.log(e); // console.error(' Failed to claim! Try again if NopeCHA timed out. Click the extension to see if you ran out of credits (refill after 24h). To avoid captchas try to get a new IP or set a cookie from https://www.hcaptcha.com/accessibility'); console.error(' Failed to claim! To avoid captchas try to get a new IP address.'); - const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'failed', `${game_id}_${filenamify(datetime())}.png`); + const p = screenshot('failed', `${game_id}_${filenamify(datetime())}.png`); await page.screenshot({ path: p, fullPage: true }); db.data[user][game_id].status = 'failed'; } notify_game.status = db.data[user][game_id].status; // claimed or failed - const p = path.resolve(cfg.dir.screenshots, 'epic-games', `${game_id}.png`); + const p = screenshot(`${game_id}.png`); if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... } } diff --git a/gog.js b/gog.js index 1785663..cde7acb 100644 --- a/gog.js +++ b/gog.js @@ -1,8 +1,9 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra -import path from 'path'; -import { jsonDb, datetime, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; +import { resolve, jsonDb, datetime, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; +const screenshot = (...a) => resolve(cfg.dir.screenshots, 'gog', ...a); + const URL_CLAIM = 'https://www.gog.com/en'; console.log(datetime(), 'started checking gog'); @@ -99,8 +100,7 @@ try { db.data[user][title] ||= { title, time: datetime(), url }; if (cfg.dryrun) process.exit(1); await page.locator('#giveaway:not(.is-loading)').waitFor(); // otherwise screenshot is sometimes with loading indicator instead of game title - const p = path.resolve(cfg.dir.screenshots, 'gog', `${filenamify(title)}.png`); - await banner.screenshot({ path: p }); // overwrites every time - only keep first? + await banner.screenshot({ path: screenshot(`${filenamify(title)}.png`) }); // overwrites every time - only keep first? // await banner.getByRole('button', { name: 'Add to library' }).click(); // instead of clicking the button, we visit the auto-claim URL which gives as a JSON response which is easier than checking the state of a button diff --git a/prime-gaming.js b/prime-gaming.js index d9fcd7e..1af1eb8 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,9 +1,10 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; -import path from 'path'; -import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; +import { resolve, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; +const screenshot = (...a) => resolve(cfg.dir.screenshots, 'prime-gaming', ...a); + // const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? const URL_CLAIM = 'https://gaming.amazon.com/home'; @@ -116,8 +117,7 @@ try { notify_games.push({ title, status: 'claimed', url: URL_CLAIM }); // const img = await (await card.$('img.tw-image')).getAttribute('src'); // console.log('Image:', img); - const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', 'internal', `${filenamify(title)}.png`); - await card.screenshot({ path: p }); + await card.screenshot({ path: screenshot('internal', `${filenamify(title)}.png`) }); } console.log('Number of free unclaimed games (external stores):', await external.count()); // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft @@ -263,8 +263,7 @@ try { db.data[user][title].status = 'claimed'; } // save screenshot of potential code just in case - const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', 'external', `${filenamify(title)}.png`); - await page.screenshot({ path: p, fullPage: true }); + await page.screenshot({ path: screenshot('external', `${filenamify(title)}.png`), fullPage: true }); // console.info(' Saved a screenshot of page to', p); } // await page.pause(); @@ -273,7 +272,7 @@ try { } if (notify_games.length) { // make screenshot of all games if something was claimed - const p = path.resolve(cfg.dir.screenshots, 'prime-gaming', `${filenamify(datetime())}.png`); + const p = screenshot(`${filenamify(datetime())}.png`); // await page.screenshot({ path: p, fullPage: true }); // fullPage does not make a difference since scroll not on body but on some element await page.keyboard.press('End'); // scroll to bottom to show all games await page.waitForTimeout(1000); // wait for fade in animation diff --git a/unrealengine.js b/unrealengine.js index 2993fea..830496c 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -4,9 +4,11 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdate import { authenticator } from 'otplib'; import path from 'path'; import { writeFileSync } from 'fs'; -import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; +import { resolve, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; +const screenshot = (...a) => resolve(cfg.dir.screenshots, 'unrealengine', ...a); + const URL_CLAIM = 'https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910'; const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect=true&redirectUrl=' + URL_CLAIM; @@ -176,15 +178,13 @@ try { console.log(e); // console.error(' Failed to claim! Try again if NopeCHA timed out. Click the extension to see if you ran out of credits (refill after 24h). To avoid captchas try to get a new IP or set a cookie from https://www.hcaptcha.com/accessibility'); console.error(' Failed to claim! To avoid captchas try to get a new IP address.'); - const p = path.resolve(cfg.dir.screenshots, 'unrealengine', 'failed', `${filenamify(datetime())}.png`); - await page.screenshot({ path: p, fullPage: true }); + await page.screenshot({ path: screenshot('failed', `${filenamify(datetime())}.png`), fullPage: true }); // db.data[user][id].status = 'failed'; notify_games.forEach(g => g.status = 'failed'); } // notify_game.status = db.data[user][game_id].status; // claimed or failed - const p = path.resolve(cfg.dir.screenshots, 'unrealengine', `${filenamify(datetime())}.png`); - if (notify_games.length) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... + if (notify_games.length) await page.screenshot({ path: screenshot(`${filenamify(datetime())}.png`), fullPage: false }); // fullPage is quite long... console.log('Done'); } } catch (error) { diff --git a/util.js b/util.js index 2725745..e581684 100644 --- a/util.js +++ b/util.js @@ -7,6 +7,9 @@ const __dirname = path.dirname(__filename); // explicit object instead of Object.fromEntries since the built-in type would loose the keys, better type: https://dev.to/svehla/typescript-object-fromentries-389c export const dataDir = s => path.resolve(__dirname, 'data', s); +// modified path.resolve to return null if first argument is '0', used to disable screenshots +export const resolve = (...a) => a.length && a[0] == '0' ? null : path.resolve(...a); + // json database import { Low } from 'lowdb'; import { JSONFile } from 'lowdb/node'; From f89b4a6a3659bfabdfb1987585ae51ef7b7d05af Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 10 Aug 2023 14:37:00 +0200 Subject: [PATCH 299/520] ue: mention new assets to claim every first Tuesday of a month --- README.md | 1 + unrealengine.js | 1 + 2 files changed, 2 insertions(+) diff --git a/README.md b/README.md index 047d0ce..538ea4f 100644 --- a/README.md +++ b/README.md @@ -135,6 +135,7 @@ Claiming the Amazon Games works out-of-the-box, however, for games on external s Epic Games usually has two free games *every week*, before Christmas every day. Prime Gaming has new games *every month* or more often during Prime days. GOG usually has one new game every couples of weeks. +Unreal Engine has new assets to claim *every first Tuesday of a month*. It is save to run the scripts every day. diff --git a/unrealengine.js b/unrealengine.js index 830496c..e44e684 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -1,4 +1,5 @@ // TODO This is mostly a copy of epic-games.js +// New assets to claim every first Tuesday of a month. import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; From 08fc59520c6a937d04a0a761d3608ceb889bfd1e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 17 Aug 2023 16:29:08 +0200 Subject: [PATCH 300/520] update docker/build-push-action v3 -> v4 --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c09e9c7..6c3555d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -38,7 +38,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v3 + uses: docker/build-push-action@v4 with: context: . platforms: linux/amd64,linux/arm64 # ,linux/arm/v7 From 13def8ba18066258f84dbb25afbf00a21c14f03a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 17 Aug 2023 16:30:12 +0200 Subject: [PATCH 301/520] build on every push or PR to main --- .github/workflows/docker.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6c3555d..6da8fd6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,14 +1,16 @@ name: Build and push Docker image (amd64, arm64 to hub.docker.com and ghcr.io) on: - workflow_dispatch: - push: - branches: - - "main" + workflow_dispatch: # allow manual trigger + # https://github.com/orgs/community/discussions/26276 + push: # on every branch, but not for PRs from forks? paths-ignore: - ".github/**" - ".gitignore" - "README.md" + pull_request: # includes PRs from forks but only triggers on creation, not pushes? + branches: + - "main" # only PRs against main jobs: docker: From 490c4c0788ea2db4fa2b6096df6e106df08e993e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 17 Aug 2023 16:31:43 +0200 Subject: [PATCH 302/520] don't ignore .github, only issue templates --- .github/workflows/docker.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6da8fd6..1914326 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -5,9 +5,8 @@ on: # https://github.com/orgs/community/discussions/26276 push: # on every branch, but not for PRs from forks? paths-ignore: - - ".github/**" - - ".gitignore" - "README.md" + - ".github/ISSUE_TEMPLATE/**" pull_request: # includes PRs from forks but only triggers on creation, not pushes? branches: - "main" # only PRs against main From d0761dea2627ea3fc1360a64bce9ad298245ad08 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 17 Aug 2023 16:37:05 +0200 Subject: [PATCH 303/520] pg: dlc: account linking: mention store, close #180 --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 1af1eb8..d22c8e7 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -330,7 +330,7 @@ try { const linkAccountModal = page.locator('[data-a-target="LinkAccountModal"]'); const linkAccountButton = linkAccountModal.locator('[data-a-target="LinkAccountButton"]'); if (await linkAccountButton.count()) { - console.error(' Missing account linking:', await linkAccountButton.innerText()); + console.error(' Missing account linking:', await linkAccountButton.getAttribute('aria-label')); } else if(await page.locator('text=Link game account').count()) { // epic-games only? console.error(' Missing account linking:', await page.locator('button[data-a-target="gms-cta"]').innerText()); } else { From 86832e7fe986cbc52e7531040e63b35fe95a0b05 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 17 Aug 2023 16:43:47 +0200 Subject: [PATCH 304/520] pg: dlc: log url of dlc if account not linked, #180 --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index d22c8e7..90b1a15 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -330,7 +330,7 @@ try { const linkAccountModal = page.locator('[data-a-target="LinkAccountModal"]'); const linkAccountButton = linkAccountModal.locator('[data-a-target="LinkAccountButton"]'); if (await linkAccountButton.count()) { - console.error(' Missing account linking:', await linkAccountButton.getAttribute('aria-label')); + console.error(' Missing account linking:', await linkAccountButton.getAttribute('aria-label'), url); } else if(await page.locator('text=Link game account').count()) { // epic-games only? console.error(' Missing account linking:', await page.locator('button[data-a-target="gms-cta"]').innerText()); } else { From 5c8f8fc3fd3bb533c97b6f704344c5c29876568c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 17 Aug 2023 16:44:59 +0200 Subject: [PATCH 305/520] pg: dlc: log url of dlc if account not linked, #180 --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 90b1a15..d1b69eb 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -332,7 +332,7 @@ try { if (await linkAccountButton.count()) { console.error(' Missing account linking:', await linkAccountButton.getAttribute('aria-label'), url); } else if(await page.locator('text=Link game account').count()) { // epic-games only? - console.error(' Missing account linking:', await page.locator('button[data-a-target="gms-cta"]').innerText()); + console.error(' Missing account linking:', await page.locator('button[data-a-target="gms-cta"]').innerText(), url); } else { const code = await page.inputValue('input[type="text"]'); console.log(' Code to redeem game:', code); From e794a3306fdee58dc1d2837d323eb23ff0023162 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 17 Aug 2023 17:02:47 +0200 Subject: [PATCH 306/520] pg: dlc: grouped list of dlcs per unlinked store, #180 --- prime-gaming.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index d1b69eb..380812a 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -312,6 +312,7 @@ try { }))); // console.log(dlcs); + const dlc_unlinked = {}; for (const dlc of dlcs) { const title = `${dlc.game} - ${dlc.title}`; const url = dlc.url; @@ -329,10 +330,18 @@ try { page.click('button:has-text("Continue")').catch(_ => { }); const linkAccountModal = page.locator('[data-a-target="LinkAccountModal"]'); const linkAccountButton = linkAccountModal.locator('[data-a-target="LinkAccountButton"]'); + let unlinked_store; if (await linkAccountButton.count()) { - console.error(' Missing account linking:', await linkAccountButton.getAttribute('aria-label'), url); + unlinked_store = await linkAccountButton.getAttribute('aria-label'); + unlinked_store = unlinked_store.match(/Link (.*) account/)[1]; } else if(await page.locator('text=Link game account').count()) { // epic-games only? - console.error(' Missing account linking:', await page.locator('button[data-a-target="gms-cta"]').innerText(), url); + console.error(' Missing account linking (epic-games specific button?):', await page.locator('button[data-a-target="gms-cta"]').innerText()); // TODO needed? + unlinked_store = 'epic-games'; + } + if (unlinked_store) { + console.error(' Missing account linking:', unlinked_store, url); + dlc_unlinked[unlinked_store] ??= []; + dlc_unlinked[unlinked_store].push(title); } else { const code = await page.inputValue('input[type="text"]'); console.log(' Code to redeem game:', code); @@ -348,6 +357,7 @@ try { await page.click('button[data-type="InGameLoot"]'); } } + console.log('DLC: Unlinked accounts:', dlc_unlinked); } } catch (error) { console.error(error); // .toString()? From e2e28301e6ab7acb16b525f5580bbd4cb01a4b0a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 19 Aug 2023 16:35:58 +0200 Subject: [PATCH 307/520] pg: fix #185? --- prime-gaming.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 380812a..bdd662d 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -328,12 +328,13 @@ try { // epic-games: Fall Guys: Claim now -> Continue -> Go to Epic Games (despite account linked and logged into epic-games) -> not tied to account but via some cookie? await Promise.any([page.click('button:has-text("Get in-game content")'), page.click('button:has-text("Claim your gift")'), page.click('button:has-text("Claim now")').then(() => page.click('button:has-text("Continue")'))]); page.click('button:has-text("Continue")').catch(_ => { }); - const linkAccountModal = page.locator('[data-a-target="LinkAccountModal"]'); - const linkAccountButton = linkAccountModal.locator('[data-a-target="LinkAccountButton"]'); + const linkAccountButton = page.locator('[data-a-target="LinkAccountButton"]'); let unlinked_store; if (await linkAccountButton.count()) { unlinked_store = await linkAccountButton.getAttribute('aria-label'); - unlinked_store = unlinked_store.match(/Link (.*) account/)[1]; + console.debug(' LinkAccountButton label:', unlinked_store); + const match = unlinked_store.match(/Link (.*) account/); + if (match.length == 2) unlinked_store = match[1]; } else if(await page.locator('text=Link game account').count()) { // epic-games only? console.error(' Missing account linking (epic-games specific button?):', await page.locator('button[data-a-target="gms-cta"]').innerText()); // TODO needed? unlinked_store = 'epic-games'; From 7551a6ca965e53048a4991bf482d7bf9873f6d98 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 19 Aug 2023 17:04:33 +0200 Subject: [PATCH 308/520] pg: fixup e2e28301e6ab7acb16b525f5580bbd4cb01a4b0a? --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index bdd662d..1f165b3 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -334,7 +334,7 @@ try { unlinked_store = await linkAccountButton.getAttribute('aria-label'); console.debug(' LinkAccountButton label:', unlinked_store); const match = unlinked_store.match(/Link (.*) account/); - if (match.length == 2) unlinked_store = match[1]; + if (match && match.length == 2) unlinked_store = match[1]; } else if(await page.locator('text=Link game account').count()) { // epic-games only? console.error(' Missing account linking (epic-games specific button?):', await page.locator('button[data-a-target="gms-cta"]').innerText()); // TODO needed? unlinked_store = 'epic-games'; From 40bcf1c8a2995b93c33f5206746a6c81e2ad9c18 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 23 Aug 2023 00:09:27 +0200 Subject: [PATCH 309/520] pg: status 'claimed' (not just 'claimed and redeemed') for external stores --- prime-gaming.js | 1 + 1 file changed, 1 insertion(+) diff --git a/prime-gaming.js b/prime-gaming.js index 1f165b3..da1c1c9 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -159,6 +159,7 @@ try { notify_game.status = `failed: need account linking for ${store}`; db.data[user][title].status = 'failed: need account linking'; } else { + db.data[user][title].status = 'claimed'; // print code if there is one const redeem = { // 'origin': 'https://www.origin.com/redeem', // TODO still needed or now only via account linking? From 3d1168f653625b9d51f9b40ce953caff0551791a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 23 Aug 2023 00:51:19 +0200 Subject: [PATCH 310/520] pg: external: split loop for URLs to avoid issue with changed elementHandles (Page.adoptNode) --- prime-gaming.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index da1c1c9..63c1d96 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -104,7 +104,7 @@ try { console.log('Number of already claimed games (total):', await games.locator('p:has-text("Collected")').count()); // can't use .all() since the list of elements via locator will change after click while we iterate over it const internal = await games.locator('.item-card__action:has([data-a-target="FGWPOffer"])').elementHandles(); - const external = games.locator('.item-card__action:has([data-a-target="ExternalOfferClaim"])'); // using .elementHandles() here would lead to error due to page navigation: elementHandle.$: Protocol error (Page.adoptNode) + const external = await games.locator('.item-card__action:has([data-a-target="ExternalOfferClaim"])').all(); console.log('Number of free unclaimed games (Prime Gaming):', internal.length); // claim games in internal store for (const card of internal) { @@ -119,15 +119,21 @@ try { // console.log('Image:', img); await card.screenshot({ path: screenshot('internal', `${filenamify(title)}.png`) }); } - console.log('Number of free unclaimed games (external stores):', await external.count()); + console.log('Number of free unclaimed games (external stores):', external.length); // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft - for (const card of await external.elementHandles()) { // TODO refactor (result of external locator changes with each iteration) - // if (!card) continue; - const title = await (await card.$('.item-card-details__body__primary')).innerText(); - console.log('Current free game:', title); + const external_info = []; + for (const card of external) { // need to get data incl. URLs in this loop and then navigate in another, otherwise .all() would update after coming back and .elementHandles() like above would lead to error due to page navigation: elementHandle.$: Protocol error (Page.adoptNode) + const title = await card.locator('.item-card-details__body__primary').innerText(); + const slug = await card.locator('a:has-text("Claim")').first().getAttribute('href'); + const url = 'https://gaming.amazon.com' + slug.split('?')[0]; + console.log('Current free game:', title); //, url); + // await (await card.$('text=Claim')).click(); // goes to URL of game, no need to wait + external_info.push({title, url}); + } + for (const {title, url} of external_info) { + await page.goto(url, { waitUntil: 'domcontentloaded' }); if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; - await (await card.$('text=Claim')).click(); // goes to URL of game, no need to wait await Promise.any([page.click('button:has-text("Get game")'), page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation // TODO would be simpler than the below, but will block for linked stores without code @@ -150,7 +156,6 @@ try { } console.log(' External store:', store); - const url = page.url().split('?')[0]; db.data[user][title] ||= { title, time: datetime(), url, store }; const notify_game = { title, url }; notify_games.push(notify_game); // status is updated below @@ -268,9 +273,9 @@ try { // console.info(' Saved a screenshot of page to', p); } // await page.pause(); - await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); - await page.click('button[data-type="Game"]'); } + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); + await page.click('button[data-type="Game"]'); if (notify_games.length) { // make screenshot of all games if something was claimed const p = screenshot(`${filenamify(datetime())}.png`); From 6a7cca31a4b87ecbe08387c1c5da966cc163056d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 24 Aug 2023 13:13:54 +0200 Subject: [PATCH 311/520] update dependencies via `ncu -u`, lowdb: pass defaultData MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dotenv ^16.0.3 → ^16.3.1 enquirer ^2.3.6 → ^2.4.1 lowdb ^5.1.0 → ^6.0.1 playwright-firefox ^1.31.0 → ^1.37.1 puppeteer-extra-plugin-stealth ^2.11.1 → ^2.11.2 https://github.com/typicode/lowdb/releases/tag/v6.0.0 > Require defaultData parameter for Low and LowSync constructors to improve TypeScript experience --- epic-games.js | 3 +- gog.js | 3 +- package-lock.json | 227 +++++++++++++++++++++++++++------------------- package.json | 10 +- prime-gaming.js | 3 +- unrealengine.js | 3 +- util.js | 4 +- 7 files changed, 143 insertions(+), 110 deletions(-) diff --git a/epic-games.js b/epic-games.js index 314b349..f828031 100644 --- a/epic-games.js +++ b/epic-games.js @@ -12,8 +12,7 @@ const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect= console.log(datetime(), 'started checking epic-games'); -const db = await jsonDb('epic-games.json'); -db.data ||= {}; +const db = await jsonDb('epic-games.json', {}); handleSIGINT(); diff --git a/gog.js b/gog.js index cde7acb..4943ff8 100644 --- a/gog.js +++ b/gog.js @@ -8,8 +8,7 @@ const URL_CLAIM = 'https://www.gog.com/en'; console.log(datetime(), 'started checking gog'); -const db = await jsonDb('gog.json'); -db.data ||= {}; +const db = await jsonDb('gog.json', {}); handleSIGINT(); diff --git a/package-lock.json b/package-lock.json index 8dee708..b40dc0e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,12 +10,12 @@ "license": "MIT", "dependencies": { "cross-env": "^7.0.3", - "dotenv": "^16.0.3", - "enquirer": "^2.3.6", - "lowdb": "^5.1.0", + "dotenv": "^16.3.1", + "enquirer": "^2.4.1", + "lowdb": "^6.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.31.0", - "puppeteer-extra-plugin-stealth": "^2.11.1" + "playwright-firefox": "^1.37.1", + "puppeteer-extra-plugin-stealth": "^2.11.2" } }, "node_modules/@otplib/core": { @@ -61,9 +61,9 @@ } }, "node_modules/@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", "dependencies": { "@types/ms": "*" } @@ -81,6 +81,14 @@ "node": ">=6" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, "node_modules/arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -170,27 +178,31 @@ } }, "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", "engines": { "node": ">=0.10.0" } }, "node_modules/dotenv": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", - "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==", + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", "engines": { "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" } }, "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dependencies": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8.6" @@ -253,9 +265,9 @@ } }, "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "node_modules/inflight": { "version": "1.0.6", @@ -339,14 +351,14 @@ } }, "node_modules/lowdb": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-5.1.0.tgz", - "integrity": "sha512-OEysJ2S3j05RqehEypEv3h6EgdV4Y7LTq7LngRNqe1IxsInOm66/sa3fzoI6mmqs2CC+zIJW3vfncGNv2IGi3A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-6.0.1.tgz", + "integrity": "sha512-1ktuKYLlQzAWwl4/PQkIr8JzNXgcTM6rAhpXaQ6BR+VwI98Q8ZwMFhBOn9u0ldcW3K/WWzhYpS3xyGTshgVGzA==", "dependencies": { "steno": "^3.0.0" }, "engines": { - "node": ">=14.16" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -436,35 +448,35 @@ } }, "node_modules/playwright-core": { - "version": "1.31.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.0.tgz", - "integrity": "sha512-/KquBjS5DcASCh8cGeNVHuC0kyb7c9plKTwaKxgOGtxT7+DZO2fjmFvPDBSXslEIK5CeOO/2kk5rOCktFXKEdA==", + "version": "1.37.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz", + "integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==", "bin": { - "playwright": "cli.js" + "playwright-core": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/playwright-firefox": { - "version": "1.31.0", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.31.0.tgz", - "integrity": "sha512-E+v16LzBt6SaSRCLH0ZV8NuikTmbmbh9Ky1JgD5sCoF8OHJ3jEjtuoHAJCkO57PJhMX9q/oZ+x133seUMIsKzA==", + "version": "1.37.1", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.37.1.tgz", + "integrity": "sha512-I8QScyW+hjGltywqLNh3Y1W96/3x70el9wNneuI34l3uVhiCRt9Co27+kiL+UlA1V8MTzaMere3ONQ8lGeut5w==", "hasInstallScript": true, "dependencies": { - "playwright-core": "1.31.0" + "playwright-core": "1.37.1" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=14" + "node": ">=16" } }, "node_modules/puppeteer-extra-plugin": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", - "integrity": "sha512-0uatQxzuVn8yegbrEwSk03wvwpMB5jNs7uTTnermylLZzoT+1rmAQaJXwlS3+vADUbw6ELNgNEHC7Skm0RqHbQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz", + "integrity": "sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q==", "dependencies": { "@types/debug": "^4.1.0", "debug": "^4.1.1", @@ -487,13 +499,13 @@ } }, "node_modules/puppeteer-extra-plugin-stealth": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.1.tgz", - "integrity": "sha512-n0wdC0Ilc9tk5L6FWLyd0P2gT8b2fp+2NuB+KB0oTSw3wXaZ0D6WNakjJsayJ4waGzIJFCUHkmK9zgx5NKMoFw==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.2.tgz", + "integrity": "sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ==", "dependencies": { "debug": "^4.1.1", - "puppeteer-extra-plugin": "^3.2.2", - "puppeteer-extra-plugin-user-preferences": "^2.4.0" + "puppeteer-extra-plugin": "^3.2.3", + "puppeteer-extra-plugin-user-preferences": "^2.4.1" }, "engines": { "node": ">=8" @@ -512,13 +524,13 @@ } }, "node_modules/puppeteer-extra-plugin-user-data-dir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.0.tgz", - "integrity": "sha512-qrhYPTGIqzL2hpeJ5DXjf8xMy5rt1UvcqSgpGTTOUOjIMz1ROWnKHjBoE9fNBJ4+ToRZbP8MzIDXWlEk/e1zJA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.1.tgz", + "integrity": "sha512-kH1GnCcqEDoBXO7epAse4TBPJh9tEpVEK/vkedKfjOVOhZAvLkHGc9swMs5ChrJbRnf8Hdpug6TJlEuimXNQ+g==", "dependencies": { "debug": "^4.1.1", "fs-extra": "^10.0.0", - "puppeteer-extra-plugin": "^3.2.2", + "puppeteer-extra-plugin": "^3.2.3", "rimraf": "^3.0.2" }, "engines": { @@ -538,14 +550,14 @@ } }, "node_modules/puppeteer-extra-plugin-user-preferences": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.0.tgz", - "integrity": "sha512-4XxMhMkJ+qqLsPY9ULF90qS9Bj1Qrwwgp1TY9zTdp1dJuy7QSgYE7xlyamq3cKrRuzg3QUOqygJo52sVeXSg5A==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz", + "integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==", "dependencies": { "debug": "^4.1.1", "deepmerge": "^4.2.2", - "puppeteer-extra-plugin": "^3.2.2", - "puppeteer-extra-plugin-user-data-dir": "^2.4.0" + "puppeteer-extra-plugin": "^3.2.3", + "puppeteer-extra-plugin-user-data-dir": "^2.4.1" }, "engines": { "node": ">=8" @@ -640,6 +652,17 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/thirty-two": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", @@ -720,9 +743,9 @@ } }, "@types/debug": { - "version": "4.1.7", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz", - "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==", + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", "requires": { "@types/ms": "*" } @@ -737,6 +760,11 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -800,21 +828,22 @@ } }, "deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, "dotenv": { - "version": "16.0.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", - "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==" }, "enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "requires": { - "ansi-colors": "^4.1.1" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" } }, "for-in": { @@ -859,9 +888,9 @@ } }, "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, "inflight": { "version": "1.0.6", @@ -928,9 +957,9 @@ "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" }, "lowdb": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-5.1.0.tgz", - "integrity": "sha512-OEysJ2S3j05RqehEypEv3h6EgdV4Y7LTq7LngRNqe1IxsInOm66/sa3fzoI6mmqs2CC+zIJW3vfncGNv2IGi3A==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-6.0.1.tgz", + "integrity": "sha512-1ktuKYLlQzAWwl4/PQkIr8JzNXgcTM6rAhpXaQ6BR+VwI98Q8ZwMFhBOn9u0ldcW3K/WWzhYpS3xyGTshgVGzA==", "requires": { "steno": "^3.0.0" } @@ -1003,22 +1032,22 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright-core": { - "version": "1.31.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.31.0.tgz", - "integrity": "sha512-/KquBjS5DcASCh8cGeNVHuC0kyb7c9plKTwaKxgOGtxT7+DZO2fjmFvPDBSXslEIK5CeOO/2kk5rOCktFXKEdA==" + "version": "1.37.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz", + "integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==" }, "playwright-firefox": { - "version": "1.31.0", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.31.0.tgz", - "integrity": "sha512-E+v16LzBt6SaSRCLH0ZV8NuikTmbmbh9Ky1JgD5sCoF8OHJ3jEjtuoHAJCkO57PJhMX9q/oZ+x133seUMIsKzA==", + "version": "1.37.1", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.37.1.tgz", + "integrity": "sha512-I8QScyW+hjGltywqLNh3Y1W96/3x70el9wNneuI34l3uVhiCRt9Co27+kiL+UlA1V8MTzaMere3ONQ8lGeut5w==", "requires": { - "playwright-core": "1.31.0" + "playwright-core": "1.37.1" } }, "puppeteer-extra-plugin": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.2.tgz", - "integrity": "sha512-0uatQxzuVn8yegbrEwSk03wvwpMB5jNs7uTTnermylLZzoT+1rmAQaJXwlS3+vADUbw6ELNgNEHC7Skm0RqHbQ==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz", + "integrity": "sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q==", "requires": { "@types/debug": "^4.1.0", "debug": "^4.1.1", @@ -1026,35 +1055,35 @@ } }, "puppeteer-extra-plugin-stealth": { - "version": "2.11.1", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.1.tgz", - "integrity": "sha512-n0wdC0Ilc9tk5L6FWLyd0P2gT8b2fp+2NuB+KB0oTSw3wXaZ0D6WNakjJsayJ4waGzIJFCUHkmK9zgx5NKMoFw==", + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.2.tgz", + "integrity": "sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ==", "requires": { "debug": "^4.1.1", - "puppeteer-extra-plugin": "^3.2.2", - "puppeteer-extra-plugin-user-preferences": "^2.4.0" + "puppeteer-extra-plugin": "^3.2.3", + "puppeteer-extra-plugin-user-preferences": "^2.4.1" } }, "puppeteer-extra-plugin-user-data-dir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.0.tgz", - "integrity": "sha512-qrhYPTGIqzL2hpeJ5DXjf8xMy5rt1UvcqSgpGTTOUOjIMz1ROWnKHjBoE9fNBJ4+ToRZbP8MzIDXWlEk/e1zJA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.1.tgz", + "integrity": "sha512-kH1GnCcqEDoBXO7epAse4TBPJh9tEpVEK/vkedKfjOVOhZAvLkHGc9swMs5ChrJbRnf8Hdpug6TJlEuimXNQ+g==", "requires": { "debug": "^4.1.1", "fs-extra": "^10.0.0", - "puppeteer-extra-plugin": "^3.2.2", + "puppeteer-extra-plugin": "^3.2.3", "rimraf": "^3.0.2" } }, "puppeteer-extra-plugin-user-preferences": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.0.tgz", - "integrity": "sha512-4XxMhMkJ+qqLsPY9ULF90qS9Bj1Qrwwgp1TY9zTdp1dJuy7QSgYE7xlyamq3cKrRuzg3QUOqygJo52sVeXSg5A==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz", + "integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==", "requires": { "debug": "^4.1.1", "deepmerge": "^4.2.2", - "puppeteer-extra-plugin": "^3.2.2", - "puppeteer-extra-plugin-user-data-dir": "^2.4.0" + "puppeteer-extra-plugin": "^3.2.3", + "puppeteer-extra-plugin-user-data-dir": "^2.4.1" } }, "rimraf": { @@ -1109,6 +1138,14 @@ "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", "integrity": "sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw==" }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, "thirty-two": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", diff --git a/package.json b/package.json index 04cf02b..6179433 100644 --- a/package.json +++ b/package.json @@ -11,12 +11,12 @@ "type": "module", "dependencies": { "cross-env": "^7.0.3", - "dotenv": "^16.0.3", - "enquirer": "^2.3.6", - "lowdb": "^5.1.0", + "dotenv": "^16.3.1", + "enquirer": "^2.4.1", + "lowdb": "^6.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.31.0", - "puppeteer-extra-plugin-stealth": "^2.11.1" + "playwright-firefox": "^1.37.1", + "puppeteer-extra-plugin-stealth": "^2.11.2" }, "repository": { "type": "git", diff --git a/prime-gaming.js b/prime-gaming.js index 63c1d96..659bd26 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -10,8 +10,7 @@ const URL_CLAIM = 'https://gaming.amazon.com/home'; console.log(datetime(), 'started checking prime-gaming'); -const db = await jsonDb('prime-gaming.json'); -db.data ||= {}; +const db = await jsonDb('prime-gaming.json', {}); handleSIGINT(); diff --git a/unrealengine.js b/unrealengine.js index e44e684..4370a85 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -15,8 +15,7 @@ const URL_LOGIN = 'https://www.epicgames.com/id/login?lang=en-US&noHostRedirect= console.log(datetime(), 'started checking unrealengine'); -const db = await jsonDb('unrealengine.json'); -db.data ||= {}; +const db = await jsonDb('unrealengine.json', {}); handleSIGINT(); diff --git a/util.js b/util.js index e581684..8cc91e9 100644 --- a/util.js +++ b/util.js @@ -13,8 +13,8 @@ export const resolve = (...a) => a.length && a[0] == '0' ? null : path.resolve(. // json database import { Low } from 'lowdb'; import { JSONFile } from 'lowdb/node'; -export const jsonDb = async file => { - const db = new Low(new JSONFile(dataDir(file))); +export const jsonDb = async (file, defaultData) => { + const db = new Low(new JSONFile(dataDir(file)), defaultData); await db.read(); return db; }; From da23bc2f92f513ffda801e5156ec870a6f0673bc Mon Sep 17 00:00:00 2001 From: 4n4n4s Date: Fri, 25 Aug 2023 17:42:52 +0200 Subject: [PATCH 312/520] Update README.md Update documentation --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 538ea4f..ac35150 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Available options/variables and their default values: | WIDTH | 1280 | Width of the opened browser (and of screen for VNC in Docker). | | HEIGHT | 1280 | Height of the opened browser (and of screen for VNC in Docker). | | VNC_PASSWORD | | VNC password for Docker. No password used by default! | -| NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. | +| NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. [Apprise]([Apprise](https://github.com/caronc/apprise)) | | NOTIFY_TITLE | | Optional title for notifications, e.g. for Pushover. | | BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. | | TIMEOUT | 60 | Timeout for any page action. Should be fine even on slow machines. | @@ -93,7 +93,7 @@ See `config.js` for all options. You can add options directly in the command or put them in a file to load. ##### Docker -You can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@bar.baz -e NOTIFY='tgram://...' ...` or using `--env-file fgc.env` where `fgc.env` is a file on your host system (see [docs](https://docs.docker.com/engine/reference/commandline/run/#env)). You can also `docker cp` your configuration file to `/fgc/data/config.env` in the `fgc` volume to store it with the rest of the data instead of on the host ([example](https://github.com/moby/moby/issues/25245#issuecomment-365980572)). +You can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@bar.baz -e NOTIFY='tgram://bottoken/ChatID' ...` or using `--env-file fgc.env` where `fgc.env` is a file on your host system (see [docs](https://docs.docker.com/engine/reference/commandline/run/#env)). You can also `docker cp` your configuration file to `/fgc/data/config.env` in the `fgc` volume to store it with the rest of the data instead of on the host ([example](https://github.com/moby/moby/issues/25245#issuecomment-365980572)). If you are using [docker compose](https://docs.docker.com/compose/environment-variables/) (or Portainer etc.), you can put options in the `environment:` section. ##### Without Docker @@ -147,6 +147,7 @@ If you want it to run regularly, you have to schedule the runs yourself: - macOS: [launchd](https://stackoverflow.com/questions/132955/how-do-i-set-a-task-to-run-every-so-often) - Windows: [task scheduler](https://active-directory-wp.com/docs/Usage/How_to_add_a_cron_job_on_Windows/Scheduled_tasks_and_cron_jobs_on_Windows/index.html), [other options](https://stackoverflow.com/questions/132971/what-is-the-windows-version-of-cron) - any OS: use a process manager like [pm2](https://pm2.keymetrics.io/docs/usage/restart-strategies/) +- Docker Compose `command: bash -c "node epic-games; node prime-gaming; node gog; echo sleeping; sleep 1d"` TODO: ~~add some server-mode where the script just keeps running and claims games e.g. every day.~~ From 66c12b8ea2003a7ffa1541be3aad6086d77e65e9 Mon Sep 17 00:00:00 2001 From: 4n4n4s Date: Fri, 25 Aug 2023 18:06:31 +0200 Subject: [PATCH 313/520] Create dependabot.yml --- .github/dependabot.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..8c4fdf8 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,15 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "npm" + directory: "/" + schedule: + interval: "daily" + - package-ecosystem: "docker" + directory: "/" + schedule: + interval: "daily" From a3adaae99fbae283091554efeee9eaa8b1a96cd0 Mon Sep 17 00:00:00 2001 From: 4n4n4s Date: Fri, 25 Aug 2023 16:30:04 +0000 Subject: [PATCH 314/520] Fixed README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ac35150..a5f7113 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ Available options/variables and their default values: | WIDTH | 1280 | Width of the opened browser (and of screen for VNC in Docker). | | HEIGHT | 1280 | Height of the opened browser (and of screen for VNC in Docker). | | VNC_PASSWORD | | VNC password for Docker. No password used by default! | -| NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. [Apprise]([Apprise](https://github.com/caronc/apprise)) | +| NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. [Apprise](https://github.com/caronc/apprise) | | NOTIFY_TITLE | | Optional title for notifications, e.g. for Pushover. | | BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. | | TIMEOUT | 60 | Timeout for any page action. Should be fine even on slow machines. | @@ -147,7 +147,7 @@ If you want it to run regularly, you have to schedule the runs yourself: - macOS: [launchd](https://stackoverflow.com/questions/132955/how-do-i-set-a-task-to-run-every-so-often) - Windows: [task scheduler](https://active-directory-wp.com/docs/Usage/How_to_add_a_cron_job_on_Windows/Scheduled_tasks_and_cron_jobs_on_Windows/index.html), [other options](https://stackoverflow.com/questions/132971/what-is-the-windows-version-of-cron) - any OS: use a process manager like [pm2](https://pm2.keymetrics.io/docs/usage/restart-strategies/) -- Docker Compose `command: bash -c "node epic-games; node prime-gaming; node gog; echo sleeping; sleep 1d"` +- Docker Compose `command: bash -c "node epic-games; node prime-gaming; node gog; echo sleeping; sleep 1d"` additionally add `restart: unless-stopped` to it. TODO: ~~add some server-mode where the script just keeps running and claims games e.g. every day.~~ From a7d045f0e2439b86dcccb09bbdeed36875deeda8 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 25 Aug 2023 21:40:28 +0200 Subject: [PATCH 315/520] ignore dependabot.yml in workflows/docker.yml --- .github/workflows/docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1914326..f32d514 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -6,6 +6,7 @@ on: push: # on every branch, but not for PRs from forks? paths-ignore: - "README.md" + - ".github/dependabot.yml" - ".github/ISSUE_TEMPLATE/**" pull_request: # includes PRs from forks but only triggers on creation, not pushes? branches: From 70b5182bedb41c671f739ce633e70486a0ab6b13 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 25 Aug 2023 21:43:57 +0200 Subject: [PATCH 316/520] same as before, just ignore .github/** and then include docker.yml --- .github/workflows/docker.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f32d514..01781f8 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -4,10 +4,11 @@ on: workflow_dispatch: # allow manual trigger # https://github.com/orgs/community/discussions/26276 push: # on every branch, but not for PRs from forks? - paths-ignore: - - "README.md" - - ".github/dependabot.yml" - - ".github/ISSUE_TEMPLATE/**" + paths: + - '**' + - '!README.md' + - '!.github/**' + - '.github/workflows/docker.yml' pull_request: # includes PRs from forks but only triggers on creation, not pushes? branches: - "main" # only PRs against main From c09d20766c17d01c5c0b8d326a2aafbe34134048 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 25 Aug 2023 21:50:17 +0200 Subject: [PATCH 317/520] dependabot daily -> weekly, may want to customize `commit-message` --- .github/dependabot.yml | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8c4fdf8..1b47972 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,8 +8,21 @@ updates: - package-ecosystem: "npm" directory: "/" schedule: - interval: "daily" + interval: "weekly" + # commit-message: + # prefix: "npm" + # include: "scope" - package-ecosystem: "docker" directory: "/" schedule: - interval: "daily" + interval: "weekly" + # commit-message: + # prefix: "docker" + # include: "scope" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + # commit-message: + # prefix: "github-actions" + # include: "scope" From 682c6512245e47f5968818922ff8ede6cbafb3fa Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 26 Aug 2023 10:42:24 +0200 Subject: [PATCH 318/520] docker: use OCI labels https://github.com/opencontainers/image-spec/blob/main/annotations.md https://docs.docker.com/engine/reference/builder/#label Before: ```console $ docker image inspect --format='{{json .Config.Labels}}' ghcr.io/vogler/free-games-claimer | jq { "org.opencontainers.image.ref.name": "ubuntu", "org.opencontainers.image.version": "22.04" } ``` After: ```console $ docker image inspect --format='{{json .Config.Labels}}' ghcr.io/vogler/free-games-claimer | jq { "org.opencontainers.image.base.name": "ubuntu:jammy", "org.opencontainers.image.description": "Automatically claims free games on the Epic Games Store, Amazon Prime Gaming and GOG", "org.opencontainers.image.name": "free-games-claimer", "org.opencontainers.image.ref.name": "", "org.opencontainers.image.revision": "", "org.opencontainers.image.source": "https://github.com/vogler/free-games-claimer", "org.opencontainers.image.title": "free-games-claimer", "org.opencontainers.image.url": "https://github.com/vogler/free-games-claimer", "org.opencontainers.image.version": "latest" } ``` --- Dockerfile | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Dockerfile b/Dockerfile index ebad9eb..df20609 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,6 +60,16 @@ COPY . . RUN dos2unix *.sh && chmod +x *.sh COPY docker-entrypoint.sh /usr/local/bin/ +LABEL org.opencontainers.image.title="free-games-claimer" \ + org.opencontainers.image.name="free-games-claimer" \ + org.opencontainers.image.description="Automatically claims free games on the Epic Games Store, Amazon Prime Gaming and GOG" \ + org.opencontainers.image.url="https://github.com/vogler/free-games-claimer" \ + org.opencontainers.image.source="https://github.com/vogler/free-games-claimer" \ + org.opencontainers.image.revision=${COMMIT_SHA} \ + org.opencontainers.image.ref.name=${BRANCH} \ + org.opencontainers.image.base.name="ubuntu:jammy" \ + org.opencontainers.image.version="latest" + # Configure VNC via environment variables: ENV VNC_PORT 5900 ENV NOVNC_PORT 6080 From 2eb17a1419ca5dd9c682b430e0091a352fd2c414 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 27 Aug 2023 18:33:27 +0200 Subject: [PATCH 319/520] docker: show version via $COMMIT_SHA (and $BRANCH if not "main") --- Dockerfile | 3 +++ docker-entrypoint.sh | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Dockerfile b/Dockerfile index df20609..82bb263 100644 --- a/Dockerfile +++ b/Dockerfile @@ -70,6 +70,9 @@ LABEL org.opencontainers.image.title="free-games-claimer" \ org.opencontainers.image.base.name="ubuntu:jammy" \ org.opencontainers.image.version="latest" +ENV COMMIT_SHA=${COMMIT_SHA} +ENV BRANCH=${BRANCH} + # Configure VNC via environment variables: ENV VNC_PORT 5900 ENV NOVNC_PORT 6080 diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 7796364..cfdac7e 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -2,6 +2,9 @@ set -eo pipefail # exit on error, error on any fail in pipe (not just last cmd); add -x to print each cmd; see gist bash_strict_mode.md +echo "Version: https://github.com/vogler/free-games-claimer/tree/${COMMIT_SHA}" +[ ! -z $BRANCH ] && [ $BRANCH != "main" ] && echo "Branch: ${BRANCH}" + # Remove chromium profile lock. # When running in docker and then killing it, on the next run chromium displayed a dialog to unlock the profile which made the script time out. # Maybe due to changed hostname of container or due to how the docker container kills playwright - didn't check. From 8a010dbcc7521837b784a6f18f1360bce355954b Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 27 Aug 2023 23:32:56 +0200 Subject: [PATCH 320/520] docker: pass in build-args, add $NOW --- .github/workflows/docker.yml | 12 +++++++++++- Dockerfile | 12 ++++++++---- docker-entrypoint.sh | 3 ++- 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 01781f8..8903c55 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -20,6 +20,11 @@ jobs: - name: Checkout uses: actions/checkout@v3 + - + name: Set environment variables + run: | + echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + echo "NOW=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV - name: Set up QEMU uses: docker/setup-qemu-action@v2 @@ -29,6 +34,7 @@ jobs: - name: Login to Docker Hub uses: docker/login-action@v2 + if: ${{ secrets.DOCKERHUB_USERNAME && secrets.DOCKERHUB_TOKEN }} with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -44,8 +50,12 @@ jobs: uses: docker/build-push-action@v4 with: context: . - platforms: linux/amd64,linux/arm64 # ,linux/arm/v7 push: true + build-args: | + COMMIT=${{ github.sha }} + BRANCH=${{ env.BRANCH }} + NOW=${{ env.NOW }} + platforms: linux/amd64,linux/arm64 # ,linux/arm/v7 tags: | voglerr/free-games-claimer:latest ghcr.io/vogler/free-games-claimer:latest diff --git a/Dockerfile b/Dockerfile index 82bb263..67a5e89 100644 --- a/Dockerfile +++ b/Dockerfile @@ -60,19 +60,23 @@ COPY . . RUN dos2unix *.sh && chmod +x *.sh COPY docker-entrypoint.sh /usr/local/bin/ +ARG COMMIT="" +ARG BRANCH="" +ARG NOW="" +ENV COMMIT=${COMMIT} +ENV BRANCH=${BRANCH} +ENV NOW=${NOW} + LABEL org.opencontainers.image.title="free-games-claimer" \ org.opencontainers.image.name="free-games-claimer" \ org.opencontainers.image.description="Automatically claims free games on the Epic Games Store, Amazon Prime Gaming and GOG" \ org.opencontainers.image.url="https://github.com/vogler/free-games-claimer" \ org.opencontainers.image.source="https://github.com/vogler/free-games-claimer" \ - org.opencontainers.image.revision=${COMMIT_SHA} \ + org.opencontainers.image.revision=${COMMIT} \ org.opencontainers.image.ref.name=${BRANCH} \ org.opencontainers.image.base.name="ubuntu:jammy" \ org.opencontainers.image.version="latest" -ENV COMMIT_SHA=${COMMIT_SHA} -ENV BRANCH=${BRANCH} - # Configure VNC via environment variables: ENV VNC_PORT 5900 ENV NOVNC_PORT 6080 diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index cfdac7e..2b5ddee 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -2,8 +2,9 @@ set -eo pipefail # exit on error, error on any fail in pipe (not just last cmd); add -x to print each cmd; see gist bash_strict_mode.md -echo "Version: https://github.com/vogler/free-games-claimer/tree/${COMMIT_SHA}" +echo "Version: https://github.com/vogler/free-games-claimer/tree/${COMMIT}" [ ! -z $BRANCH ] && [ $BRANCH != "main" ] && echo "Branch: ${BRANCH}" +echo "Build: $NOW" # Remove chromium profile lock. # When running in docker and then killing it, on the next run chromium displayed a dialog to unlock the profile which made the script time out. From d7949fb9dd4c696116e056c6930adceb059d7839 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 27 Aug 2023 23:39:52 +0200 Subject: [PATCH 321/520] can't use secrets in if of workflow step? --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 8903c55..4b4c78e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -34,7 +34,7 @@ jobs: - name: Login to Docker Hub uses: docker/login-action@v2 - if: ${{ secrets.DOCKERHUB_USERNAME && secrets.DOCKERHUB_TOKEN }} + # if: ${{ secrets.DOCKERHUB_USERNAME && secrets.DOCKERHUB_TOKEN }} with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} From f528fbfd305218ae59bb3b15b4f45ac9e705ff3e Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 27 Aug 2023 23:41:19 +0200 Subject: [PATCH 322/520] docker: more human-readable format for date $NOW --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4b4c78e..2459dcd 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -24,7 +24,7 @@ jobs: name: Set environment variables run: | echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV - echo "NOW=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV + echo "NOW=$(date -R)" >> $GITHUB_ENV # date -Iseconds; date +'%Y-%m-%dT%H:%M:%S' - name: Set up QEMU uses: docker/setup-qemu-action@v2 From c211472d0c59337545f1c5a91b5aeb5d2fd022df Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 27 Aug 2023 23:59:30 +0200 Subject: [PATCH 323/520] docker: try GitHub Actions cache https://docs.docker.com/build/ci/github-actions/cache/#github-cache --- .github/workflows/docker.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2459dcd..bf7fc06 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -59,3 +59,5 @@ jobs: tags: | voglerr/free-games-claimer:latest ghcr.io/vogler/free-games-claimer:latest + cache-from: type=gha + cache-to: type=gha,mode=max From d4d7ee32819013949e5b1a95f5179cd8a7f9fb77 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 28 Aug 2023 00:47:52 +0200 Subject: [PATCH 324/520] fix indent --- util.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/util.js b/util.js index 8cc91e9..0b2dcbe 100644 --- a/util.js +++ b/util.js @@ -108,11 +108,11 @@ export const notify = (html) => new Promise((resolve, reject) => { const title = cfg.notify_title ? `-t ${cfg.notify_title}` : ''; exec(`apprise ${cfg.notify} -i html '${title}' -b '${html}'`, (error, stdout, stderr) => { if (error) { - console.log(`error: ${error.message}`); - if (error.message.includes('command not found')) { - console.info('Run `pip install apprise`. See https://github.com/vogler/free-games-claimer#notifications'); - } - return resolve(); + console.log(`error: ${error.message}`); + if (error.message.includes('command not found')) { + console.info('Run `pip install apprise`. See https://github.com/vogler/free-games-claimer#notifications'); + } + return resolve(); } if (stderr) console.error(`stderr: ${stderr}`); if (stdout) console.log(`stdout: ${stdout}`); From 6560afa2b5c48e659836b4040a4b9e5f7914b072 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 28 Aug 2023 01:17:57 +0200 Subject: [PATCH 325/520] version.js to check if running the latest version --- version.js | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 version.js diff --git a/version.js b/version.js new file mode 100644 index 0000000..170ed42 --- /dev/null +++ b/version.js @@ -0,0 +1,49 @@ +// check if running the latest version + +import {log} from 'console'; +import { existsSync, readFileSync } from 'fs'; +import { exec } from 'child_process'; + +const execp = (cmd) => new Promise((resolve, reject) => { + exec(cmd, (error, stdout, stderr) => { + if (stderr) console.error(`stderr: ${stderr}`); + // if (stdout) console.log(`stdout: ${stdout}`); + if (error) { + console.log(`error: ${error.message}`); + if (error.message.includes('command not found')) { + console.info('Install git to check for updates!'); + } + return reject(); + } + resolve(stdout.trim()); + }); +}); + +const git_main = () => readFileSync('.git/refs/heads/main').toString().trim(); + +let sha, date; +if (existsSync('/.dockerenv')) { + log('Running inside Docker.'); + ['COMMIT', 'BRANCH', 'NOW'].forEach(v => log(` ${v}:`, process.env[v])); + sha = process.env.COMMIT; + date = process.env.NOW; +} else { + log('Not running inside Docker.'); + sha = await execp('git rev-parse HEAD'); + date = await execp('git show -s --format=%cD'); // same as format as `date -R` (RFC2822) + // date = await execp('git show -s --format=%ch'); // %ch is same as --date=human (short/relative) +} + +const gh = await (await fetch('https://api.github.com/repos/vogler/free-games-claimer/commits/main', { + // headers: { accept: 'application/vnd.github.VERSION.sha' } + })).json(); +// log(gh); + +log('Local commit:', sha, new Date(date)); +log('Online commit:', gh.sha, new Date(gh.commit.committer.date)); + +if (sha == gh.sha) { + log('Running the latest version!') +} else { + log('Not running the latest version!') +} From 9261be690ce0aaa744a3106628b0579a1363fdbf Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 28 Aug 2023 01:25:09 +0200 Subject: [PATCH 326/520] '/.dockerenv' did not exist in container... --- version.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/version.js b/version.js index 170ed42..7b9ebef 100644 --- a/version.js +++ b/version.js @@ -1,7 +1,7 @@ // check if running the latest version import {log} from 'console'; -import { existsSync, readFileSync } from 'fs'; +import { readFileSync } from 'fs'; import { exec } from 'child_process'; const execp = (cmd) => new Promise((resolve, reject) => { @@ -22,7 +22,8 @@ const execp = (cmd) => new Promise((resolve, reject) => { const git_main = () => readFileSync('.git/refs/heads/main').toString().trim(); let sha, date; -if (existsSync('/.dockerenv')) { +// if (existsSync('/.dockerenv')) { // did not work +if (process.env.NOVNC_PORT) { log('Running inside Docker.'); ['COMMIT', 'BRANCH', 'NOW'].forEach(v => log(` ${v}:`, process.env[v])); sha = process.env.COMMIT; From 6bb1dca93412c82219bd8f68fd1dec8a42aab01b Mon Sep 17 00:00:00 2001 From: KevinMatt <36391318+kevinmatthe@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:16:30 +0800 Subject: [PATCH 327/520] epic-games: captcha on login: await notify (#195) Adding async handle in captcha notify --- epic-games.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index f828031..9211042 100644 --- a/epic-games.js +++ b/epic-games.js @@ -79,9 +79,9 @@ try { await page.fill('#email', email); await page.fill('#password', password); await page.click('button[type="submit"]'); - page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(() => { + page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(async () => { console.error('Got a captcha during login (likely due to too many attempts)! You may solve it in the browser, get a new IP or try again in a few hours.'); - notify('epic-games: got captcha during login. Please check.'); + await notify('epic-games: got captcha during login. Please check.'); }).catch(_ => { }); // handle MFA, but don't await it page.waitForURL('**/id/login/mfa**').then(async () => { From 621032e459df85beb2996fa69ff93e8aeb526b9a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 30 Aug 2023 19:20:28 +0200 Subject: [PATCH 328/520] eg: notify about captcha before claim, closes #191 --- epic-games.js | 1 + 1 file changed, 1 insertion(+) diff --git a/epic-games.js b/epic-games.js index 9211042..6e0b62c 100644 --- a/epic-games.js +++ b/epic-games.js @@ -204,6 +204,7 @@ try { captcha.waitFor().then(async () => { // don't await, since element may not be shown // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.') + await notify('epic-games: got captcha challenge right before claim. Use VNC to solve it manually.') // await page.waitForTimeout(2000); // const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); // await captcha.screenshot({ path: p }); From 93d082e7002d63adcae08a2c92370e2953e067d8 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 31 Aug 2023 17:05:12 +0200 Subject: [PATCH 329/520] eg: comment userAgent firefox (docker) --- epic-games.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 6e0b62c..82911ba 100644 --- a/epic-games.js +++ b/epic-games.js @@ -26,7 +26,8 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? - // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 + // 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 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-${datetime()}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools From 5ae4ce5666b8a528e629468ea8e08a55762d4fe4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 31 Aug 2023 18:59:10 +0200 Subject: [PATCH 330/520] eg: option TIME=1 to log duration of steps, #183 --- config.js | 1 + epic-games.js | 15 +++++++++++++++ 2 files changed, 16 insertions(+) diff --git a/config.js b/config.js index d863d8d..e6a9438 100644 --- a/config.js +++ b/config.js @@ -7,6 +7,7 @@ dotenv.config({ path: 'data/config.env' }); // loads env vars from file - will n export const cfg = { debug: process.env.PWDEBUG == '1', // runs non-headless and opens https://playwright.dev/docs/inspector record: process.env.RECORD == '1', // `recordHar` (network) + `recordVideo` + time: process.env.TIME == '1', // log duration of each step dryrun: process.env.DRYRUN == '1', // don't claim anything show: process.env.SHOW == '1', // run non-headless get headless() { return !this.debug && !this.show }, diff --git a/epic-games.js b/epic-games.js index 82911ba..f23f229 100644 --- a/epic-games.js +++ b/epic-games.js @@ -16,6 +16,8 @@ const db = await jsonDb('epic-games.json', {}); handleSIGINT(); +if (cfg.time) console.time('startup'); + // https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 // const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox @@ -63,6 +65,9 @@ try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto + if (cfg.time) console.timeEnd('startup'); + if (cfg.time) console.time('login'); + // page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // Not needed anymore since we set the cookie above. Clicking this did not always work since the message was animated in too slowly. while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { @@ -84,6 +89,9 @@ try { console.error('Got a captcha during login (likely due to too many attempts)! You may solve it in the browser, get a new IP or try again in a few hours.'); await notify('epic-games: got captcha during login. Please check.'); }).catch(_ => { }); + page.waitForSelector('h6:has-text("Incorrect response.")').then(async () => { + console.error('CAPTCHA!') + }).catch(_ => { }); // handle MFA, but don't await it page.waitForURL('**/id/login/mfa**').then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); @@ -107,6 +115,8 @@ try { user = await page.locator('#user span').first().innerHTML(); console.log(`Signed in as ${user}`); db.data[user] ||= {}; + if (cfg.time) console.timeEnd('login'); + if (cfg.time) console.time('claim all games'); // Detect free games const game_loc = page.locator('a:has(span:text-is("Free Now"))'); @@ -120,6 +130,7 @@ try { console.log('Free games:', urls); for (const url of urls) { + if (cfg.time) console.time('claim game'); await page.goto(url); // , { waitUntil: 'domcontentloaded' }); const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); // barrier to block until page is loaded @@ -174,6 +185,7 @@ try { if (await iframe.locator(':has-text("unavailable in your region")').count() > 0) { console.error(' This product is unavailable in your region!'); db.data[user][game_id].status = notify_game.status = 'unavailable-in-region'; + if (cfg.time) console.timeEnd('claim game'); continue; } @@ -190,6 +202,7 @@ try { if (cfg.dryrun) { console.log(' DRYRUN=1 -> Skip order!'); notify_game.status = 'skipped'; + if (cfg.time) console.timeEnd('claim game'); continue; } @@ -230,7 +243,9 @@ try { const p = screenshot(`${game_id}.png`); if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... } + if (cfg.time) console.timeEnd('claim game'); } + if (cfg.time) console.timeEnd('claim all games'); } catch (error) { console.error(error); // .toString()? process.exitCode ||= 1; From 59004cd4bb33fc1f7620a77cb4ee904272979118 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 31 Aug 2023 23:04:42 +0200 Subject: [PATCH 331/520] README: no docker updates: `git pull; npm install` --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a5f7113..7cb4393 100644 --- a/README.md +++ b/README.md @@ -34,6 +34,7 @@ Data (including json files with claimed games, codes to redeem, screenshots) is 2. Clone/download this repository and `cd` into it in a terminal 3. Run `npm install` 4. Run `pip install apprise` to install [apprise](https://github.com/caronc/apprise) if you want notifications +5. To get updates: `git pull; npm install` 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`. From 38975e811bac5133a0895ae450de7a7a5a3f2b9d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 31 Aug 2023 23:08:29 +0200 Subject: [PATCH 332/520] eg: error: Incorrect repsonse for captcha! --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index f23f229..4c0e7bd 100644 --- a/epic-games.js +++ b/epic-games.js @@ -90,7 +90,7 @@ try { await notify('epic-games: got captcha during login. Please check.'); }).catch(_ => { }); page.waitForSelector('h6:has-text("Incorrect response.")').then(async () => { - console.error('CAPTCHA!') + console.error('Incorrect repsonse for captcha!') }).catch(_ => { }); // handle MFA, but don't await it page.waitForURL('**/id/login/mfa**').then(async () => { From d8e2093a0dc3f9c00b1b1cdd812395bbf179e231 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 5 Sep 2023 15:56:24 +0200 Subject: [PATCH 333/520] pg: INTERACTIVE=1 to confirm each claim or skip it --- config.js | 1 + prime-gaming.js | 5 ++++- util.js | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index e6a9438..2dd064d 100644 --- a/config.js +++ b/config.js @@ -9,6 +9,7 @@ export const cfg = { record: process.env.RECORD == '1', // `recordHar` (network) + `recordVideo` time: process.env.TIME == '1', // log duration of each step dryrun: process.env.DRYRUN == '1', // don't claim anything + interactive: process.env.INTERACTIVE == '1', // confirm to claim, default skip show: process.env.SHOW == '1', // run non-headless get headless() { return !this.debug && !this.show }, width: Number(process.env.WIDTH) || 1280, // width of the opened browser diff --git a/prime-gaming.js b/prime-gaming.js index 659bd26..21b18fc 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,6 +1,6 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; -import { resolve, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; +import { resolve, jsonDb, datetime, stealth, filenamify, prompt, confirm, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; const screenshot = (...a) => resolve(cfg.dir.screenshots, 'prime-gaming', ...a); @@ -111,6 +111,7 @@ try { const title = await (await card.$('.item-card-details__body__primary')).innerText(); console.log('Current free game:', title); if (cfg.dryrun) continue; + if (cfg.interactive && !await confirm()) continue; await (await card.$('button:has-text("Claim")')).click(); db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; notify_games.push({ title, status: 'claimed', url: URL_CLAIM }); @@ -133,6 +134,7 @@ try { await page.goto(url, { waitUntil: 'domcontentloaded' }); if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; + if (cfg.interactive && !await confirm()) continue; await Promise.any([page.click('button:has-text("Get game")'), page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation // TODO would be simpler than the below, but will block for linked stores without code @@ -324,6 +326,7 @@ try { console.log('Current DLC:', title); if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; + if (cfg.interactive && !await confirm()) continue; db.data[user][title] ||= { title, time: datetime(), store: 'DLC', status: 'failed: need account linking' }; const notify_game = { title, url }; notify_games.push(notify_game); // status is updated below diff --git a/util.js b/util.js index 0b2dcbe..60ff4a5 100644 --- a/util.js +++ b/util.js @@ -98,6 +98,7 @@ const timeoutPlugin = timeout => enquirer => { // cancel prompt after timeout ms enquirer.use(timeoutPlugin(cfg.login_timeout)); // TODO may not want to have this timeout for all prompts; better extend Prompt and add a timeout prompt option // single prompt that just returns the non-empty value instead of an object export const prompt = o => enquirer.prompt({name: 'name', type: 'input', message: 'Enter value', ...o}).then(r => r.name).catch(_ => {}); +export const confirm = o => prompt({type: 'confirm', message: 'Continue?', ...o}) // notifications via apprise CLI import { exec } from 'child_process'; From d4ebdd091a478fb55cbde658f775f71baa6ab451 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 5 Sep 2023 15:59:17 +0200 Subject: [PATCH 334/520] pg: log current game in claim loop, not data loop --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 21b18fc..3a02bbe 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -126,11 +126,11 @@ try { const title = await card.locator('.item-card-details__body__primary').innerText(); const slug = await card.locator('a:has-text("Claim")').first().getAttribute('href'); const url = 'https://gaming.amazon.com' + slug.split('?')[0]; - console.log('Current free game:', title); //, url); // await (await card.$('text=Claim')).click(); // goes to URL of game, no need to wait external_info.push({title, url}); } for (const {title, url} of external_info) { + console.log('Current free game:', title); //, url); await page.goto(url, { waitUntil: 'domcontentloaded' }); if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; From b449ee4d7fd01fcc4d9054341235696fa9901a9b Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 5 Sep 2023 16:12:20 +0200 Subject: [PATCH 335/520] pg: redeem: legacygames success/error --- prime-gaming.js | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 3a02bbe..1162496 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -246,7 +246,7 @@ try { await page2.click('#nextButton'); redeem_action = 'redeemed?'; console.log(' Redeemed successfully? Please report your Response from above (if it is new) in https://github.com/vogler/free-games-claimer/issues/5'); - // db.data[user][title].status = 'claimed and redeemed'; + db.data[user][title].status = 'claimed and redeemed?'; } } } else if (store == 'legacy games') { @@ -255,9 +255,17 @@ try { await page2.fill('[name=email_validate]', cfg.pg_email); await page2.uncheck('[name=newsletter_sub]'); await page2.click('[type="submit"]'); - redeem_action = 'redeemed?'; - console.log(' Redeemed successfully? Please report problems in https://github.com/vogler/free-games-claimer/issues/5'); - db.data[user][title].status = 'claimed and redeemed'; + try { + await page2.waitForResponse(r => r.url().startsWith('https://promo.legacygames.com/promotion-processing/order-management.php')); + await page2.waitForSelector('h2:has-text("Thanks for redeeming")'); + redeem_action = 'redeemed'; + db.data[user][title].status = 'claimed and redeemed'; + } catch (error) { + console.error(' Got error', error); + redeem_action = 'redeemed?'; + db.data[user][title].status = 'claimed and redeemed?'; + console.log(' Redeemed successfully? Please report problems in https://github.com/vogler/free-games-claimer/issues/5'); + } } else { console.error(` Redeem on ${store} not yet implemented!`); } From 97882c76c3f089c7c187866c42db1ad7864c4329 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 7 Sep 2023 13:25:14 +0200 Subject: [PATCH 336/520] eg: debug: window.screen --- epic-games.js | 1 + 1 file changed, 1 insertion(+) diff --git a/epic-games.js b/epic-games.js index 4c0e7bd..1ab81c6 100644 --- a/epic-games.js +++ b/epic-games.js @@ -50,6 +50,7 @@ if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); +if (cfg.debug) console.debug(await page.evaluate(() => window.screen)); if (cfg.record && cfg.debug) { // const filter = _ => true; const filter = r => r.url().includes('store.epicgames.com'); From c79f4cc40ff73670949c3b784ce8f7872e23ff24 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 7 Sep 2023 13:27:18 +0200 Subject: [PATCH 337/520] docker: migrate to new nodesource repo; node 19 -> 20 old node setup script had a warning to migrate with 60s pause during docker build https://github.com/nodesource/distributions/wiki/How-to-migrate-to-the-new-repository --- Dockerfile | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 67a5e89..fd18159 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,8 +10,11 @@ ARG DEBIAN_FRONTEND=noninteractive # Install up-to-date node & npm, deps for virtual screen & noVNC, firefox, pip for apprise. RUN apt-get update \ - && apt-get install --no-install-recommends -y curl ca-certificates \ - && curl -fsSL https://deb.nodesource.com/setup_19.x | bash - \ + && apt-get install --no-install-recommends -y curl ca-certificates gnupg \ + && mkdir -p /etc/apt/keyrings \ + && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ + && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list \ + && apt-get update \ && apt-get install --no-install-recommends -y \ nodejs \ xvfb \ @@ -42,6 +45,9 @@ RUN apt-get update \ /var/lib/apt/lists/* \ /var/tmp/* +# RUN node --version +# RUN npm --version + RUN ln -s /usr/share/novnc/vnc_auto.html /usr/share/novnc/index.html RUN pip install apprise From e30216c9b86a1d057442641b71547cad35267041 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 7 Sep 2023 13:30:49 +0200 Subject: [PATCH 338/520] eg: default resolution 1280x1280 -> 1920x1080, #183 --- Dockerfile | 4 ++-- config.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index fd18159..f686c55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -90,8 +90,8 @@ EXPOSE 5900 EXPOSE 6080 # Configure Xvfb via environment variables: -ENV WIDTH 1280 -ENV HEIGHT 1280 +ENV WIDTH 1920 +ENV HEIGHT 1080 ENV DEPTH 24 # Show browser instead of running headless diff --git a/config.js b/config.js index 2dd064d..e604d0f 100644 --- a/config.js +++ b/config.js @@ -12,8 +12,8 @@ export const cfg = { interactive: process.env.INTERACTIVE == '1', // confirm to claim, default skip show: process.env.SHOW == '1', // run non-headless get headless() { return !this.debug && !this.show }, - width: Number(process.env.WIDTH) || 1280, // width of the opened browser - height: Number(process.env.HEIGHT) || 1280, // height of the opened browser + width: Number(process.env.WIDTH) || 1920, // width of the opened browser + height: Number(process.env.HEIGHT) || 1080, // height of the opened browser timeout: (Number(process.env.TIMEOUT) || 60) * 1000, // default timeout for playwright is 30s login_timeout: (Number(process.env.LOGIN_TIMEOUT) || 180) * 1000, // higher timeout for login, will wait twice: prompt + wait for manual login novnc_port: process.env.NOVNC_PORT, // running in docker if set From d51f7310d97cfe57206eb82910a0aad5a181924d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 7 Sep 2023 13:56:37 +0200 Subject: [PATCH 339/520] gog: fix #200, uncheck 'Marketing communications through Trusted Partners' --- gog.js | 1 + 1 file changed, 1 insertion(+) diff --git a/gog.js b/gog.js index 4943ff8..fa82c8a 100644 --- a/gog.js +++ b/gog.js @@ -130,6 +130,7 @@ try { if (status == 'claimed' && !cfg.gog_newsletter) { console.log("Unsubscribe from 'Promotions and hot deals' newsletter"); await page.goto('https://www.gog.com/en/account/settings/subscriptions'); + await page.locator('li:has-text("Marketing communications through Trusted Partners") label').uncheck(); await page.locator('li:has-text("Promotions and hot deals") label').uncheck(); } } From 44d712e333c6952d4aadc6a9a7b4865a4511227b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 11 Sep 2023 09:41:42 +0200 Subject: [PATCH 340/520] build(deps): bump actions/checkout from 3 to 4 (#207) Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 4. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index bf7fc06..f80650c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -19,7 +19,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set environment variables run: | From 30db49d9b92f997b75644b43ff3c28f7f2ccd27a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 09:49:00 +0200 Subject: [PATCH 341/520] build(deps): bump docker/setup-qemu-action from 2 to 3 (#215) Bumps [docker/setup-qemu-action](https://github.com/docker/setup-qemu-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-qemu-action/releases) - [Commits](https://github.com/docker/setup-qemu-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-qemu-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index f80650c..2ddc409 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -27,7 +27,7 @@ jobs: echo "NOW=$(date -R)" >> $GITHUB_ENV # date -Iseconds; date +'%Y-%m-%dT%H:%M:%S' - name: Set up QEMU - uses: docker/setup-qemu-action@v2 + uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v2 From 6cdef56c094b860c633c40abb5e37b8f1ab21b60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 09:50:04 +0200 Subject: [PATCH 342/520] build(deps): bump docker/setup-buildx-action from 2 to 3 (#213) Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 2 to 3. - [Release notes](https://github.com/docker/setup-buildx-action/releases) - [Commits](https://github.com/docker/setup-buildx-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/setup-buildx-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2ddc409..a493dc2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -30,7 +30,7 @@ jobs: uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 + uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub uses: docker/login-action@v2 From 0ba693eeb6dbe210ad829189d2142c2e125e27da Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 09:51:23 +0200 Subject: [PATCH 343/520] build(deps): bump docker/login-action from 2 to 3 (#214) Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3. - [Release notes](https://github.com/docker/login-action/releases) - [Commits](https://github.com/docker/login-action/compare/v2...v3) --- updated-dependencies: - dependency-name: docker/login-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a493dc2..1cfe5b2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -33,14 +33,14 @@ jobs: uses: docker/setup-buildx-action@v3 - name: Login to Docker Hub - uses: docker/login-action@v2 + uses: docker/login-action@v3 # if: ${{ secrets.DOCKERHUB_USERNAME && secrets.DOCKERHUB_TOKEN }} with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Login to GitHub Container Registry - uses: docker/login-action@v2 + uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.repository_owner }} From 2a4da3f49cfec3e72ad543727bca50e4f136f3a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Sep 2023 09:51:53 +0200 Subject: [PATCH 344/520] build(deps): bump docker/build-push-action from 4 to 5 (#212) Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 4 to 5. - [Release notes](https://github.com/docker/build-push-action/releases) - [Commits](https://github.com/docker/build-push-action/compare/v4...v5) --- updated-dependencies: - dependency-name: docker/build-push-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 1cfe5b2..25470d5 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -47,7 +47,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . push: true From 1fbabbc0f2ad5f44e8265a98b7b6d949e2256e6a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 18 Sep 2023 12:49:23 +0200 Subject: [PATCH 345/520] log path of recorded video --- epic-games.js | 1 + gog.js | 1 + prime-gaming.js | 1 + unrealengine.js | 1 + 4 files changed, 4 insertions(+) diff --git a/epic-games.js b/epic-games.js index 1ab81c6..56c1035 100644 --- a/epic-games.js +++ b/epic-games.js @@ -259,4 +259,5 @@ try { } } if (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); +if (page.video()) console.log('Recorded video:', await page.video().path()) await context.close(); diff --git a/gog.js b/gog.js index fa82c8a..6b02ee5 100644 --- a/gog.js +++ b/gog.js @@ -145,4 +145,5 @@ try { notify(`gog (${user}):
${html_game_list(notify_games)}`); } } +if (page.video()) console.log('Recorded video:', await page.video().path()) await context.close(); diff --git a/prime-gaming.js b/prime-gaming.js index 1162496..1523e5f 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -387,4 +387,5 @@ try { notify(`prime-gaming (${user}):
${html_game_list(notify_games)}`); } } +if (page.video()) console.log('Recorded video:', await page.video().path()) await context.close(); diff --git a/unrealengine.js b/unrealengine.js index 4370a85..a5d3282 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -199,4 +199,5 @@ try { } } if (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); +if (page.video()) console.log('Recorded video:', await page.video().path()) await context.close(); From b748460b731bd565592e1c68c01a2c4eca9ae1a2 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 18 Sep 2023 12:58:20 +0200 Subject: [PATCH 346/520] disable Playwright's handleSIGINT and close context ourselves to save recordings also on SIGINT --- epic-games.js | 8 +++++--- gog.js | 12 +++++++----- prime-gaming.js | 8 +++++--- unrealengine.js | 8 +++++--- util.js | 3 ++- 5 files changed, 24 insertions(+), 15 deletions(-) diff --git a/epic-games.js b/epic-games.js index 56c1035..e58725b 100644 --- a/epic-games.js +++ b/epic-games.js @@ -14,8 +14,6 @@ console.log(datetime(), 'started checking epic-games'); const db = await jsonDb('epic-games.json', {}); -handleSIGINT(); - if (cfg.time) console.time('startup'); // https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 @@ -33,6 +31,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { 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-${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 args: [ // https://peter.sh/experiments/chromium-command-line-switches // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' // '--restore-last-session', // does not apply for crash/killed @@ -43,6 +42,8 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { // ignoreDefaultArgs: ['--enable-automation'], // remove default arg that shows the info bar with 'Chrome is being controlled by automated test software.'. Since Chromeium 106 this leads to show another info bar with 'You are using an unsupported command-line flag: --no-sandbox. Stability and security will suffer.'. }); +handleSIGINT(context); + // 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); @@ -248,8 +249,9 @@ try { } if (cfg.time) console.timeEnd('claim all games'); } catch (error) { - console.error(error); // .toString()? process.exitCode ||= 1; + console.error('--- Exception:'); + console.error(error); // .toString()? if (error.message && process.exitCode != 130) notify(`epic-games failed: ${error.message.split('\n')[0]}`); } finally { diff --git a/gog.js b/gog.js index 6b02ee5..341ee6c 100644 --- a/gog.js +++ b/gog.js @@ -10,17 +10,18 @@ console.log(datetime(), 'started checking gog'); const db = await jsonDb('gog.json', {}); -handleSIGINT(); - // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, locale: "en-US", // ignore OS locale to be sure to have english text for locators -> done via /en in URL - // recordHar: { path: './data/gog.har' }, // https://toolbox.googleapps.com/apps/har_analyzer/ - // recordVideo: { dir: './data/videos' }, // console.log(await page.video().path()); + 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/gog-${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 }); +handleSIGINT(context); + if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist @@ -135,8 +136,9 @@ try { } } } catch (error) { - console.error(error); // .toString()? process.exitCode ||= 1; + console.error('--- Exception:'); + console.error(error); // .toString()? if (error.message && process.exitCode != 130) notify(`gog failed: ${error.message.split('\n')[0]}`); } finally { diff --git a/prime-gaming.js b/prime-gaming.js index 1523e5f..a5df36d 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -12,8 +12,6 @@ console.log(datetime(), 'started checking prime-gaming'); const db = await jsonDb('prime-gaming.json', {}); -handleSIGINT(); - // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, @@ -21,8 +19,11 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { 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/pg-${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 }); +handleSIGINT(context); + // TODO test if needed await stealth(context); @@ -377,8 +378,9 @@ try { console.log('DLC: Unlinked accounts:', dlc_unlinked); } } catch (error) { - console.error(error); // .toString()? process.exitCode ||= 1; + console.error('--- Exception:'); + console.error(error); // .toString()? if (error.message && process.exitCode != 130) notify(`prime-gaming failed: ${error.message.split('\n')[0]}`); } finally { diff --git a/unrealengine.js b/unrealengine.js index a5d3282..6523503 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -17,8 +17,6 @@ console.log(datetime(), 'started checking unrealengine'); const db = await jsonDb('unrealengine.json', {}); -handleSIGINT(); - // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, @@ -28,8 +26,11 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { 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/ue-${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 }); +handleSIGINT(context); + await stealth(context); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); @@ -188,8 +189,9 @@ try { console.log('Done'); } } catch (error) { - console.error(error); // .toString()? process.exitCode ||= 1; + console.error('--- Exception:'); + console.error(error); // .toString()? if (error.message && process.exitCode != 130) notify(`unrealengine failed: ${error.message.split('\n')[0]}`); } finally { diff --git a/util.js b/util.js index 60ff4a5..94cccfd 100644 --- a/util.js +++ b/util.js @@ -27,9 +27,10 @@ export const datetimeUTC = (d = new Date()) => d.toISOString().replace('T', ' ') export const datetime = (d = new Date()) => datetimeUTC(new Date(d.getTime() - d.getTimezoneOffset() * 60000)); export const filenamify = s => s.replaceAll(':', '.').replace(/[^a-z0-9 _\-.]/gi, '_'); // alternative: https://www.npmjs.com/package/filenamify - On Unix-like systems, / is reserved. On Windows, <>:"/\|?* along with trailing periods are reserved. -export const handleSIGINT = () => process.on('SIGINT', () => { // e.g. when killed by Ctrl-C +export const handleSIGINT = (context = null) => process.on('SIGINT', async () => { // e.g. when killed by Ctrl-C console.error('\nInterrupted by SIGINT. Exit!'); // Exception shows where the script was:\n'); // killed before catch in docker... process.exitCode = 130; // 128+SIGINT to indicate to parent that process was killed + if (context) await context.close(); // in order to save recordings also on SIGINT, we need to disable Playwright's handleSIGINT and close the context ourselves }); // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 From 13b2917dd03c675d6ab9853fe188f15c52782688 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 18 Sep 2023 13:03:04 +0200 Subject: [PATCH 347/520] workaround for recordVideo broken in Playwright 1.36 Recording videos with relative path as in docs was broken for Firefox. Issue: https://github.com/microsoft/playwright/issues/27086 Can be reverted after PR is available in release: https://github.com/microsoft/playwright/pull/27099 --- epic-games.js | 2 +- gog.js | 3 ++- prime-gaming.js | 3 ++- unrealengine.js | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/epic-games.js b/epic-games.js index e58725b..976ce07 100644 --- a/epic-games.js +++ b/epic-games.js @@ -29,7 +29,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { // 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 - 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 + recordVideo: cfg.record ? { dir: path.resolve('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-${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 args: [ // https://peter.sh/experiments/chromium-command-line-switches diff --git a/gog.js b/gog.js index 341ee6c..e41eed7 100644 --- a/gog.js +++ b/gog.js @@ -1,4 +1,5 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra +import path from 'path'; import { resolve, jsonDb, datetime, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; @@ -15,7 +16,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, locale: "en-US", // ignore OS locale to be sure to have english text for locators -> done via /en in URL - 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 + recordVideo: cfg.record ? { dir: path.resolve('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/gog-${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 }); diff --git a/prime-gaming.js b/prime-gaming.js index a5df36d..3816777 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,5 +1,6 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; +import path from 'path'; import { resolve, jsonDb, datetime, stealth, filenamify, prompt, confirm, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; @@ -17,7 +18,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, 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 + recordVideo: cfg.record ? { dir: path.resolve('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/pg-${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 }); diff --git a/unrealengine.js b/unrealengine.js index 6523503..55d4e58 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -24,7 +24,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 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 + recordVideo: cfg.record ? { dir: path.resolve('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/ue-${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 }); From 840d35c2d22bccbc37720c9096d3d773091b30ef Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 18 Sep 2023 13:54:40 +0200 Subject: [PATCH 348/520] =?UTF-8?q?ncu=20-u:=20playwright-firefox=20^1.37.?= =?UTF-8?q?1=20=20=E2=86=92=20=20^1.38.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 3 ++- package-lock.json | 30 +++++++++++++++--------------- package.json | 2 +- 3 files changed, 18 insertions(+), 17 deletions(-) diff --git a/Dockerfile b/Dockerfile index f686c55..7a291b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -55,9 +55,10 @@ WORKDIR /fgc COPY package*.json ./ # Playwright installs patched firefox to ~/.cache/ms-playwright/firefox-* -# Requires some system deps to run (see install-deps above). +# Requires some system deps to run (see inlined install-deps above). RUN npm install # Old: PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD + install firefox (had to be done after `npm install` to get the correct version). Now: playwright-firefox as npm dep and `npm install` will only install that. +# From 1.38 Playwright will no longer install browser automatically for playwright, but apparently still for playwright-firefox: https://github.com/microsoft/playwright/releases/tag/v1.38.0 # RUN npx playwright install firefox COPY . . diff --git a/package-lock.json b/package-lock.json index b40dc0e..8078e6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "enquirer": "^2.4.1", "lowdb": "^6.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.37.1", + "playwright-firefox": "^1.38.0", "puppeteer-extra-plugin-stealth": "^2.11.2" } }, @@ -448,9 +448,9 @@ } }, "node_modules/playwright-core": { - "version": "1.37.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz", - "integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==", + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", + "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==", "bin": { "playwright-core": "cli.js" }, @@ -459,12 +459,12 @@ } }, "node_modules/playwright-firefox": { - "version": "1.37.1", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.37.1.tgz", - "integrity": "sha512-I8QScyW+hjGltywqLNh3Y1W96/3x70el9wNneuI34l3uVhiCRt9Co27+kiL+UlA1V8MTzaMere3ONQ8lGeut5w==", + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.38.0.tgz", + "integrity": "sha512-uNXdvj17JHbKir/EmdLtYEHkzI0ttFMX/3+HO/TW5z1hRmN5CydDNINm9xL/0AwvFSa5unZPM7S7+mUa3EiniA==", "hasInstallScript": true, "dependencies": { - "playwright-core": "1.37.1" + "playwright-core": "1.38.0" }, "bin": { "playwright": "cli.js" @@ -1032,16 +1032,16 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright-core": { - "version": "1.37.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.37.1.tgz", - "integrity": "sha512-17EuQxlSIYCmEMwzMqusJ2ztDgJePjrbttaefgdsiqeLWidjYz9BxXaTaZWxH1J95SHGk6tjE+dwgWILJoUZfA==" + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", + "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==" }, "playwright-firefox": { - "version": "1.37.1", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.37.1.tgz", - "integrity": "sha512-I8QScyW+hjGltywqLNh3Y1W96/3x70el9wNneuI34l3uVhiCRt9Co27+kiL+UlA1V8MTzaMere3ONQ8lGeut5w==", + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.38.0.tgz", + "integrity": "sha512-uNXdvj17JHbKir/EmdLtYEHkzI0ttFMX/3+HO/TW5z1hRmN5CydDNINm9xL/0AwvFSa5unZPM7S7+mUa3EiniA==", "requires": { - "playwright-core": "1.37.1" + "playwright-core": "1.38.0" } }, "puppeteer-extra-plugin": { diff --git a/package.json b/package.json index 6179433..9a91f7f 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "enquirer": "^2.4.1", "lowdb": "^6.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.37.1", + "playwright-firefox": "^1.38.0", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "repository": { From 41f1f9550964563df64e6535b0c2752c944c0c46 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 18 Sep 2023 14:02:15 +0200 Subject: [PATCH 349/520] PW: replace deprecated type() with pressSequentially() for OTPs --- epic-games.js | 4 ++-- gog.js | 2 +- prime-gaming.js | 2 +- unrealengine.js | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/epic-games.js b/epic-games.js index 976ce07..16984a3 100644 --- a/epic-games.js +++ b/epic-games.js @@ -99,7 +99,7 @@ try { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); // TODO locator for text (email or app?) const otp = cfg.eg_otpkey && authenticator.generate(cfg.eg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them - await page.type('input[name="code-input-0"]', otp.toString()); + await page.locator('input[name="code-input-0"]').pressSequentially(otp.toString()); await page.click('button[type="submit"]'); }).catch(_ => { }); } else { @@ -196,7 +196,7 @@ try { console.error(' EG_PARENTALPIN not set. Need to enter Parental Control PIN manually.'); notify('epic-games: EG_PARENTALPIN not set. Need to enter Parental Control PIN manually.'); } - await iframe.locator('input.payment-pin-code__input').first().type(cfg.eg_parentalpin); + await iframe.locator('input.payment-pin-code__input').first().pressSequentially(cfg.eg_parentalpin); await iframe.locator('button:has-text("Continue")').click({ delay: 11 }); }).catch(_ => { }); diff --git a/gog.js b/gog.js index e41eed7..be4514d 100644 --- a/gog.js +++ b/gog.js @@ -61,7 +61,7 @@ try { console.log('Two-Step Verification - Enter security code'); console.log(await iframe.locator('.form__description').innerText()) const otp = await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 4 || 'The code must be 4 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them - await iframe.locator('#second_step_authentication_token_letter_1').type(otp.toString(), {delay: 10}); + await iframe.locator('#second_step_authentication_token_letter_1').pressSequentially(otp.toString(), {delay: 10}); await iframe.locator('#second_step_authentication_send').click(); await page.waitForTimeout(1000); // TODO still needed with wait for username below? }).catch(_ => { }); diff --git a/prime-gaming.js b/prime-gaming.js index 3816777..8fcfa2b 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -68,7 +68,7 @@ try { console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App'); await page.check('[name=rememberDevice]'); const otp = cfg.pg_otpkey && authenticator.generate(cfg.pg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them - await page.type('input[name=otpCode]', otp.toString()); + await page.locator('input[name=otpCode]').pressSequentially(otp.toString()); await page.click('input[type="submit"]'); }).catch(_ => { }); } else { diff --git a/unrealengine.js b/unrealengine.js index 55d4e58..f7ee6fa 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -72,7 +72,7 @@ try { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); // TODO locator for text (email or app?) const otp = cfg.eg_otpkey && authenticator.generate(cfg.eg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them - await page.type('input[name="code-input-0"]', otp.toString()); + await page.locator('input[name="code-input-0"]').pressSequentially(otp.toString()); await page.click('button[type="submit"]'); }).catch(_ => { }); } else { From 9ebd15f20437d93da0dc8c41f2dc8c7c08171268 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 23 Sep 2023 11:01:31 +0200 Subject: [PATCH 350/520] fix log text if $VNC_PASSWORD is set, fixes #223 --- docker-entrypoint.sh | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 2b5ddee..0eea025 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -27,10 +27,15 @@ rm -f /tmp/.X1-lock export DISPLAY=:1 # need to export this, otherwise playwright complains with 'Looks like you launched a headed browser without having a XServer running.' Xvfb $DISPLAY -ac -screen 0 "${WIDTH}x${HEIGHT}x${DEPTH}" & echo "Xvfb display server created screen with resolution ${WIDTH}x${HEIGHT}" -pw="-nopw" -[ -z "$VNC_PASSWORD" ] || pw="-passwd $VNC_PASSWORD" +if [ -z "$VNC_PASSWORD" ]; then + pw="-nopw" + pwt="no password!" +else + pw="-passwd $VNC_PASSWORD" + pwt="with password" +fi x11vnc -display $DISPLAY -forever -shared -rfbport $VNC_PORT -bg $pw 2>/dev/null 1>&2 -echo "VNC is running on port $VNC_PORT (no password!)" +echo "VNC is running on port $VNC_PORT ($pwt)" websockify -D --web "/usr/share/novnc/" $NOVNC_PORT "localhost:$VNC_PORT" 2>/dev/null 1>&2 & echo "noVNC (VNC via browser) is running on http://localhost:$NOVNC_PORT" echo From d318a57be15f0e9608c81541710f7ff042d2242a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 25 Sep 2023 09:35:30 +0200 Subject: [PATCH 351/520] =?UTF-8?q?ncu=20-u:=20playwright-firefox=20^1.38.?= =?UTF-8?q?0=20=20=E2=86=92=20=20^1.38.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 30 +++++++++++++++--------------- package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8078e6d..4cb3c34 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "enquirer": "^2.4.1", "lowdb": "^6.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.38.0", + "playwright-firefox": "^1.38.1", "puppeteer-extra-plugin-stealth": "^2.11.2" } }, @@ -448,9 +448,9 @@ } }, "node_modules/playwright-core": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", - "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", + "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==", "bin": { "playwright-core": "cli.js" }, @@ -459,12 +459,12 @@ } }, "node_modules/playwright-firefox": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.38.0.tgz", - "integrity": "sha512-uNXdvj17JHbKir/EmdLtYEHkzI0ttFMX/3+HO/TW5z1hRmN5CydDNINm9xL/0AwvFSa5unZPM7S7+mUa3EiniA==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.38.1.tgz", + "integrity": "sha512-IhOwSlhz8wpJnuzTCxZSQWgLGb+VX/8FjoUE7HuARbLXNPYM02vvbdSs+MxVEDgN9k2/i0QC4BrUMstP2VtYEg==", "hasInstallScript": true, "dependencies": { - "playwright-core": "1.38.0" + "playwright-core": "1.38.1" }, "bin": { "playwright": "cli.js" @@ -1032,16 +1032,16 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright-core": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.0.tgz", - "integrity": "sha512-f8z1y8J9zvmHoEhKgspmCvOExF2XdcxMW8jNRuX4vkQFrzV4MlZ55iwb5QeyiFQgOFCUolXiRHgpjSEnqvO48g==" + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", + "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==" }, "playwright-firefox": { - "version": "1.38.0", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.38.0.tgz", - "integrity": "sha512-uNXdvj17JHbKir/EmdLtYEHkzI0ttFMX/3+HO/TW5z1hRmN5CydDNINm9xL/0AwvFSa5unZPM7S7+mUa3EiniA==", + "version": "1.38.1", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.38.1.tgz", + "integrity": "sha512-IhOwSlhz8wpJnuzTCxZSQWgLGb+VX/8FjoUE7HuARbLXNPYM02vvbdSs+MxVEDgN9k2/i0QC4BrUMstP2VtYEg==", "requires": { - "playwright-core": "1.38.0" + "playwright-core": "1.38.1" } }, "puppeteer-extra-plugin": { diff --git a/package.json b/package.json index 9a91f7f..d447382 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "enquirer": "^2.4.1", "lowdb": "^6.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.38.0", + "playwright-firefox": "^1.38.1", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "repository": { From 0040d9d96c699d515abe2ff69a5fdf81f32bee4f Mon Sep 17 00:00:00 2001 From: 4n4n4s Date: Tue, 26 Sep 2023 19:12:09 +0000 Subject: [PATCH 352/520] Sonarqube support with ESLint --- .eslintrc.cjs | 27 + .github/workflows/sonar.yml | 28 + package-lock.json | 1518 +++++++++++++++++++++++++++++++++++ package.json | 5 +- sonar-project.properties | 9 + 5 files changed, 1586 insertions(+), 1 deletion(-) create mode 100644 .eslintrc.cjs create mode 100644 .github/workflows/sonar.yml create mode 100644 sonar-project.properties diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..c2d8ac6 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,27 @@ +module.exports = { + 'env': { + 'es2021': true, + 'node': true, + }, + "extends": "eslint:recommended", + 'overrides': [ + { + 'env': { + 'node': true, + }, + 'files': [ + '.eslintrc.{js,cjs}', + ], + 'parserOptions': { + 'sourceType': 'script', + }, + }, + ], + 'parserOptions': { + 'ecmaVersion': 'latest', + 'sourceType': 'module', + }, + 'rules': { + 'semi': 'error', + }, +}; diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml new file mode 100644 index 0000000..62067f3 --- /dev/null +++ b/.github/workflows/sonar.yml @@ -0,0 +1,28 @@ +on: + # Trigger analysis when pushing in master or pull requests, and when creating a pull request. + push: + branches: + - main + pull_request: + types: [opened, synchronize, reopened] +name: Sonar +jobs: + sonarcloud: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + # Disabling shallow clone is recommended for improving relevancy of reporting + fetch-depth: 0 + - name: Install modules + run: npm install -g eslint + - name: Run ESLint + continue-on-error: true + run: eslint . --ext .js,.ts -f json -o eslint_report.json + - name: Fix ESLint paths + run: sed -i 's+/home/runner/work/free-games-claimer/free-games-claimer+/github/workspace+g' eslint_report.json + - name: SonarCloud Scan + uses: sonarsource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/package-lock.json b/package-lock.json index 8078e6d..4f249f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,142 @@ "otplib": "^12.0.1", "playwright-firefox": "^1.38.0", "puppeteer-extra-plugin-stealth": "^2.11.2" + }, + "devDependencies": { + "eslint": "^8.50.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" } }, "node_modules/@otplib/core": { @@ -73,6 +209,43 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -89,6 +262,27 @@ "node": ">=8" } }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "node_modules/arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -111,6 +305,31 @@ "concat-map": "0.0.1" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/clone-deep": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", @@ -126,6 +345,24 @@ "node": ">=0.10.0" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -177,6 +414,12 @@ } } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -185,6 +428,18 @@ "node": ">=0.10.0" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -208,6 +463,234 @@ "node": ">=8.6" } }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.50.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, "node_modules/for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -264,11 +747,87 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -296,6 +855,36 @@ "node": ">=0.10.0" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -320,6 +909,36 @@ "node": ">=0.10.0" } }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -331,6 +950,15 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -350,6 +978,40 @@ "node": ">=0.10.0" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lowdb": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-6.0.1.tgz", @@ -413,6 +1075,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -421,6 +1089,23 @@ "wrappy": "1" } }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/otplib": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", @@ -431,6 +1116,57 @@ "@otplib/preset-v11": "^12.0.1" } }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -473,6 +1209,24 @@ "node": ">=16" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/puppeteer-extra-plugin": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz", @@ -575,6 +1329,45 @@ } } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -589,6 +1382,29 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/shallow-clone": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", @@ -663,6 +1479,36 @@ "node": ">=8" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/thirty-two": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", @@ -671,6 +1517,30 @@ "node": ">=0.2.6" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -679,6 +1549,15 @@ "node": ">= 10.0.0" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -697,9 +1576,114 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } }, "dependencies": { + "@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true + }, + "@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + } + }, + "@eslint/js": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "dev": true + }, + "@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, "@otplib/core": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", @@ -755,6 +1739,31 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, + "acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-colors": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", @@ -765,6 +1774,21 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", @@ -784,6 +1808,22 @@ "concat-map": "0.0.1" } }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "clone-deep": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", @@ -796,6 +1836,21 @@ "shallow-clone": "^0.1.2" } }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -827,11 +1882,26 @@ "ms": "2.1.2" } }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "dotenv": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", @@ -846,6 +1916,177 @@ "strip-ansi": "^6.0.1" } }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.50.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + } + }, + "eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true + }, + "espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "requires": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -887,11 +2128,63 @@ "path-is-absolute": "^1.0.0" } }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -916,6 +2209,27 @@ "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==" }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -934,6 +2248,33 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", @@ -943,6 +2284,15 @@ "universalify": "^2.0.0" } }, + "keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "requires": { + "json-buffer": "3.0.1" + } + }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", @@ -956,6 +2306,31 @@ "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lowdb": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-6.0.1.tgz", @@ -1003,6 +2378,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1011,6 +2392,20 @@ "wrappy": "1" } }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + } + }, "otplib": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/otplib/-/otplib-12.0.1.tgz", @@ -1021,6 +2416,39 @@ "@otplib/preset-v11": "^12.0.1" } }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", @@ -1044,6 +2472,18 @@ "playwright-core": "1.38.0" } }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, "puppeteer-extra-plugin": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz", @@ -1086,6 +2526,24 @@ "puppeteer-extra-plugin-user-data-dir": "^2.4.1" } }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1094,6 +2552,15 @@ "glob": "^7.1.3" } }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, "shallow-clone": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", @@ -1146,16 +2613,61 @@ "ansi-regex": "^5.0.1" } }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "thirty-two": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==" }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1168,6 +2680,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true } } } diff --git a/package.json b/package.json index 9a91f7f..c10bb1c 100644 --- a/package.json +++ b/package.json @@ -23,5 +23,8 @@ "url": "https://github.com/vogler/free-games-claimer.git" }, "author": "Ralf Vogler", - "license": "MIT" + "license": "MIT", + "devDependencies": { + "eslint": "^8.50.0" + } } diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..677d6b3 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,9 @@ +sonar.organization=vogler +sonar.projectKey=vogler_free-games-claimer + +# relative paths to source directories. More details and properties are described +# in https://sonarcloud.io/documentation/project-administration/narrowing-the-focus/ +sonar.sources=. + +#Eslint issues +sonar.eslint.reportPaths = eslint_report.json From 9fc68b881fd6e786f50f2be10fefdab4e2061096 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 10 Oct 2023 22:01:23 +0200 Subject: [PATCH 353/520] pg: legacygames: don't wait for response, just text --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 8fcfa2b..66a9594 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -258,7 +258,7 @@ try { await page2.uncheck('[name=newsletter_sub]'); await page2.click('[type="submit"]'); try { - await page2.waitForResponse(r => r.url().startsWith('https://promo.legacygames.com/promotion-processing/order-management.php')); + // await page2.waitForResponse(r => r.url().startsWith('https://promo.legacygames.com/promotion-processing/order-management.php')); // status code 302 await page2.waitForSelector('h2:has-text("Thanks for redeeming")'); redeem_action = 'redeemed'; db.data[user][title].status = 'claimed and redeemed'; From a8ab989a7ff6c156a241c65f736b7aefb343850f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 10 Oct 2023 22:12:59 +0200 Subject: [PATCH 354/520] pg: external: check for 'Link account' besides 'Link game account' --- prime-gaming.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 66a9594..cf2b532 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -162,7 +162,8 @@ try { db.data[user][title] ||= { title, time: datetime(), url, store }; const notify_game = { title, url }; notify_games.push(notify_game); // status is updated below - if (await page.locator('div:has-text("Link game account")').count()) { + if (await page.locator('div:has-text("Link game account")').count() // TODO still needed? epic games store just has 'Link account' as the button text now. + || await page.locator('div:has-text("Link account")').count()) { console.error(' Account linking is required to claim this offer!'); notify_game.status = `failed: need account linking for ${store}`; db.data[user][title].status = 'failed: need account linking'; From ad2301c3fd50766153a4de4b40eee4c920180444 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 10 Oct 2023 22:42:38 +0200 Subject: [PATCH 355/520] pg: eg: fix detecting successful claim --- prime-gaming.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index cf2b532..89691b8 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -137,7 +137,7 @@ try { if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; - await Promise.any([page.click('button:has-text("Get game")'), page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")')]); // waits for navigation + await Promise.any([page.click('button:has-text("Get game")'), page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")'), page.waitForSelector('.thank-you-title:has-text("Success")')]); // waits for navigation // TODO would be simpler than the below, but will block for linked stores without code // const redeem_text = await page.textContent('text=/ code on /'); // FAQ: How do I redeem my code? @@ -167,6 +167,11 @@ try { console.error(' Account linking is required to claim this offer!'); notify_game.status = `failed: need account linking for ${store}`; db.data[user][title].status = 'failed: need account linking'; + // await page.pause(); + // await page.click('[data-a-target="LinkAccountModal"] [data-a-target="LinkAccountButton"]'); + // TODO login for epic games also needed if already logged in + // wait for https://www.epicgames.com/id/authorize?redirect_uri=https%3A%2F%2Fservice.link.amazon.gg... + // await page.click('button[aria-label="Allow"]'); } else { db.data[user][title].status = 'claimed'; // print code if there is one @@ -213,19 +218,19 @@ try { console.error(' Code was not found!'); } else { // TODO not logged in? need valid unused code to test. redeem_action = 'redeemed?'; - console.log(' Redeemed successfully? Please report your Responses (if new) in https://github.com/vogler/free-games-claimer/issues/5'); + // console.log(' Redeemed successfully? Please report your Responses (if new) in https://github.com/vogler/free-games-claimer/issues/5'); console.debug(` Response 1: ${r1t}`); // then after the click on Redeem there is a POST request which should return {} if claimed successfully const r2 = page2.waitForResponse(r => r.request().method() == 'POST' && r.url().startsWith('https://redeem.gog.com/')); await page2.click('[type="submit"]'); // click Redeem const r2t = await (await r2).text(); - console.debug(` Response 2: ${r2t}`); if (r2t == '{}') { redeem_action = 'redeemed'; console.log(' Redeemed successfully.'); db.data[user][title].status = 'claimed and redeemed'; } else { redeem_action = 'redeemed?'; + console.debug(` Response 2: ${r2t}`); console.log(' Unknown Response 2 - please report in https://github.com/vogler/free-games-claimer/issues/5'); } } From 04787909c7b83b842ebc605ee0ba9e51bca893cd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 13 Oct 2023 00:03:11 +0200 Subject: [PATCH 356/520] eg: waitFor order confirmation to be attached instead of visible, #233 --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 16984a3..78675e3 100644 --- a/epic-games.js +++ b/epic-games.js @@ -227,7 +227,7 @@ try { // console.info(' Saved a screenshot of hcaptcha challenge to', p); // console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? }).catch(_ => { }); // may time out if not shown - await page.waitForSelector('text=Thanks for your order!'); + await page.locator('text=Thanks for your order!').waitFor({state: 'attached'}); db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time console.log(' Claimed successfully!'); From d73a523fe7f76608af5db4e72b567f058814fea1 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Oct 2023 19:44:52 +0200 Subject: [PATCH 357/520] eg: fix sign in, user displayname, #236 --- epic-games.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index 78675e3..d0dd537 100644 --- a/epic-games.js +++ b/epic-games.js @@ -72,7 +72,7 @@ try { // page.click('button:has-text("Accept All Cookies")').catch(_ => { }); // Not needed anymore since we set the cookie above. Clicking this did not always work since the message was animated in too slowly. - while (await page.locator('a[role="button"]:has-text("Sign In")').count() > 0) { + while (await page.locator('egs-navigation').getAttribute('isloggedin') != 'true') { console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); if (cfg.novnc_port) console.info(`Open http://localhost:${cfg.novnc_port} to login inside the docker container.`); if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in @@ -114,7 +114,7 @@ try { await page.waitForURL(URL_CLAIM); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } - user = await page.locator('#user span').first().innerHTML(); + user = await page.locator('egs-navigation').getAttribute('displayname'); // 'null' if !isloggedin console.log(`Signed in as ${user}`); db.data[user] ||= {}; if (cfg.time) console.timeEnd('login'); From a374d483451f1fa19c0f9f461e32cb37a4b46fd7 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 25 Oct 2023 19:48:58 +0200 Subject: [PATCH 358/520] eg: fix login (email/password split), closes #236 --- epic-games.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index d0dd537..0cafd1f 100644 --- a/epic-games.js +++ b/epic-games.js @@ -83,8 +83,9 @@ try { const email = cfg.eg_email || await prompt({message: 'Enter email'}); const password = email && (cfg.eg_password || await prompt({type: 'password', message: 'Enter password'})); if (email && password) { - await page.click('text=Sign in with Epic Games'); + // await page.click('text=Sign in with Epic Games'); await page.fill('#email', email); + await page.click('button[type="submit"]'); await page.fill('#password', password); await page.click('button[type="submit"]'); page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(async () => { From 4137bb5569221812ee065b3307e533774986722a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 26 Oct 2023 15:01:04 +0200 Subject: [PATCH 359/520] DEBUG=1 as alternative to PWDEBUG=1 (also shows Playwright debugger) --- config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.js b/config.js index e604d0f..336db99 100644 --- a/config.js +++ b/config.js @@ -5,7 +5,7 @@ dotenv.config({ path: 'data/config.env' }); // loads env vars from file - will n // Options - also see table in README.md export const cfg = { - debug: process.env.PWDEBUG == '1', // runs non-headless and opens https://playwright.dev/docs/inspector + debug: process.env.DEBUG == '1' || process.env.PWDEBUG == '1', // runs non-headless and opens https://playwright.dev/docs/inspector record: process.env.RECORD == '1', // `recordHar` (network) + `recordVideo` time: process.env.TIME == '1', // log duration of each step dryrun: process.env.DRYRUN == '1', // don't claim anything From 1dbe2f1457f500a382e0326837a5f004c5825052 Mon Sep 17 00:00:00 2001 From: 4n4n4s Date: Fri, 27 Oct 2023 11:37:32 +0000 Subject: [PATCH 360/520] Allow forks to create builds and fix failing build --- .dockerignore | 2 ++ .github/workflows/docker.yml | 32 +++++++++++++++++++------------- CONTRIBUTING.md | 6 ++++++ 3 files changed, 27 insertions(+), 13 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/.dockerignore b/.dockerignore index ffd3c43..4971835 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,3 +4,5 @@ data .gitignore **Dockerfile** .dockerignore + +.github diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 25470d5..5c2dade 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -3,15 +3,15 @@ name: Build and push Docker image (amd64, arm64 to hub.docker.com and ghcr.io) on: workflow_dispatch: # allow manual trigger # https://github.com/orgs/community/discussions/26276 - push: # on every branch, but not for PRs from forks? - paths: - - '**' - - '!README.md' - - '!.github/**' - - '.github/workflows/docker.yml' - pull_request: # includes PRs from forks but only triggers on creation, not pushes? + push: branches: - - "main" # only PRs against main + - "main" + - "v*" + tags: + - "v*" + pull_request: + branches: + - "main" jobs: docker: @@ -25,6 +25,11 @@ jobs: run: | echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV echo "NOW=$(date -R)" >> $GITHUB_ENV # date -Iseconds; date +'%Y-%m-%dT%H:%M:%S' + if [[ "${{ env.BRANCH }}" == "main" ]]; then + echo "IMAGE_TAG=latest" >> $GITHUB_ENV + else + echo "IMAGE_TAG=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + fi - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -34,7 +39,7 @@ jobs: - name: Login to Docker Hub uses: docker/login-action@v3 - # if: ${{ secrets.DOCKERHUB_USERNAME && secrets.DOCKERHUB_TOKEN }} + if: github.event_name != 'pull_request' with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -43,21 +48,22 @@ jobs: uses: docker/login-action@v3 with: registry: ghcr.io - username: ${{ github.repository_owner }} + username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v5 + if: github.event_name != 'pull_request' with: context: . - push: true + push: ${{ github.event_name != 'pull_request' }} build-args: | COMMIT=${{ github.sha }} BRANCH=${{ env.BRANCH }} NOW=${{ env.NOW }} platforms: linux/amd64,linux/arm64 # ,linux/arm/v7 tags: | - voglerr/free-games-claimer:latest - ghcr.io/vogler/free-games-claimer:latest + ${{ secrets.DOCKERHUB_USERNAME }}/free-games-claimer:${{env.IMAGE_TAG}} + ghcr.io/${{ github.actor }}/free-games-claimer:${{env.IMAGE_TAG}} cache-from: type=gha cache-to: type=gha,mode=max diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..aaf8218 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,6 @@ +# Contribute + +## Building and publishing docker images +Setup the secrets for DOCKERHUB_USERNAME and [DOCKERHUB_TOKEN](https://hub.docker.com/settings/security) in https://github.com/YOUR_USERNAME/free-games-claimer/settings/secrets/actions to be able to run the docker.yml workflows. + +Check if under Workflow Permissions in https://github.com/YOUR_USERNAME/free-games-claimer/settings/actions the radio button is set to "Read and write permissions". In case that's not set the push to ghcr.io will fail. \ No newline at end of file From 280ab709752987c316016bdcc71ec26c0f3cb62f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 2 Nov 2023 16:01:08 +0100 Subject: [PATCH 361/520] README.md: recommend to run without docker until #183 is fixed --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 7cb4393..2f2ecd9 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,9 @@ Easy option: [install Docker](https://docs.docker.com/get-docker/) (or [podman]( ``` docker run --rm -it -p 6080:6080 -v fgc:/fgc/data --pull=always ghcr.io/vogler/free-games-claimer ``` + +_This currently gives you a captcha challenge for epic-games. Until [issue #183](https://github.com/vogler/free-games-claimer/issues/183) is fixed, it is recommended to just run `node epic-games` without docker (see below)._ + This will run `node epic-games; node prime-gaming; node gog` - if you only want to claim games for one of the stores, you can override the default command by appending e.g. `node epic-games` at the end of the `docker run` command, or if you want several `bash -c "node epic-games.js; node gog.js"`. Data (including json files with claimed games, codes to redeem, screenshots) is stored in the Docker volume `fgc`. @@ -35,6 +38,7 @@ Data (including json files with claimed games, codes to redeem, screenshots) is 3. Run `npm install` 4. Run `pip install apprise` 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`. From bf870919a66f3872f96328772ad219aa32c684f7 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 2 Nov 2023 16:18:25 +0100 Subject: [PATCH 362/520] upgrade to lowdb 6.1.1 and use JSONPreset See example in https://github.com/typicode/lowdb/releases/tag/v6.1.0 --- package-lock.json | 32 ++++++++++++++++---------------- package.json | 2 +- util.js | 10 ++-------- 3 files changed, 19 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4cb3c34..320fab8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "cross-env": "^7.0.3", "dotenv": "^16.3.1", "enquirer": "^2.4.1", - "lowdb": "^6.0.1", + "lowdb": "^6.1.1", "otplib": "^12.0.1", "playwright-firefox": "^1.38.1", "puppeteer-extra-plugin-stealth": "^2.11.2" @@ -351,11 +351,11 @@ } }, "node_modules/lowdb": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-6.0.1.tgz", - "integrity": "sha512-1ktuKYLlQzAWwl4/PQkIr8JzNXgcTM6rAhpXaQ6BR+VwI98Q8ZwMFhBOn9u0ldcW3K/WWzhYpS3xyGTshgVGzA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-6.1.1.tgz", + "integrity": "sha512-HO13FCxI8SCwfj2JRXOKgXggxnmfSc+l0aJsZ5I34X3pwzG/DPBSKyKu3Zkgg/pNmx854SVgE2la0oUeh6wzNw==", "dependencies": { - "steno": "^3.0.0" + "steno": "^3.1.1" }, "engines": { "node": ">=16" @@ -642,11 +642,11 @@ } }, "node_modules/steno": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", - "integrity": "sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/steno/-/steno-3.1.1.tgz", + "integrity": "sha512-B7c6EVH7oEiaMRW36SjUnktkDwp/qd4pQiduylyiqvcZEZDeX0IIFZRBZdwO/RaVo60M0wkDwC0e8yeKaR4VGg==", "engines": { - "node": ">=14.16" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -957,11 +957,11 @@ "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" }, "lowdb": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-6.0.1.tgz", - "integrity": "sha512-1ktuKYLlQzAWwl4/PQkIr8JzNXgcTM6rAhpXaQ6BR+VwI98Q8ZwMFhBOn9u0ldcW3K/WWzhYpS3xyGTshgVGzA==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-6.1.1.tgz", + "integrity": "sha512-HO13FCxI8SCwfj2JRXOKgXggxnmfSc+l0aJsZ5I34X3pwzG/DPBSKyKu3Zkgg/pNmx854SVgE2la0oUeh6wzNw==", "requires": { - "steno": "^3.0.0" + "steno": "^3.1.1" } }, "merge-deep": { @@ -1134,9 +1134,9 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "steno": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/steno/-/steno-3.0.0.tgz", - "integrity": "sha512-uZtn7Ht9yXLiYgOsmo8btj4+f7VxyYheMt8g6F1ANjyqByQXEE2Gygjgenp3otHH1TlHsS4JAaRGv5wJ1wvMNw==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/steno/-/steno-3.1.1.tgz", + "integrity": "sha512-B7c6EVH7oEiaMRW36SjUnktkDwp/qd4pQiduylyiqvcZEZDeX0IIFZRBZdwO/RaVo60M0wkDwC0e8yeKaR4VGg==" }, "strip-ansi": { "version": "6.0.1", diff --git a/package.json b/package.json index d447382..94a0493 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "cross-env": "^7.0.3", "dotenv": "^16.3.1", "enquirer": "^2.4.1", - "lowdb": "^6.0.1", + "lowdb": "^6.1.1", "otplib": "^12.0.1", "playwright-firefox": "^1.38.1", "puppeteer-extra-plugin-stealth": "^2.11.2" diff --git a/util.js b/util.js index 94cccfd..9904486 100644 --- a/util.js +++ b/util.js @@ -11,14 +11,8 @@ export const dataDir = s => path.resolve(__dirname, 'data', s); export const resolve = (...a) => a.length && a[0] == '0' ? null : path.resolve(...a); // json database -import { Low } from 'lowdb'; -import { JSONFile } from 'lowdb/node'; -export const jsonDb = async (file, defaultData) => { - const db = new Low(new JSONFile(dataDir(file)), defaultData); - await db.read(); - return db; -}; - +import { JSONPreset } from 'lowdb/node'; +export const jsonDb = (file, defaultData) => JSONPreset(dataDir(file), defaultData); export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); // date and time as UTC (no timezone offset) in nicely readable and sortable format, e.g., 2022-10-06 12:05:27.313 From 75f7d774456d481b0c567d2bb4c00759caebfd0d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 2 Nov 2023 16:23:46 +0100 Subject: [PATCH 363/520] upgrade playwright-firefox 1.38.1 -> 1.39.0 --- package-lock.json | 30 +++++++++++++++--------------- package.json | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package-lock.json b/package-lock.json index 320fab8..2570777 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "enquirer": "^2.4.1", "lowdb": "^6.1.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.38.1", + "playwright-firefox": "^1.39.0", "puppeteer-extra-plugin-stealth": "^2.11.2" } }, @@ -448,9 +448,9 @@ } }, "node_modules/playwright-core": { - "version": "1.38.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", - "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", "bin": { "playwright-core": "cli.js" }, @@ -459,12 +459,12 @@ } }, "node_modules/playwright-firefox": { - "version": "1.38.1", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.38.1.tgz", - "integrity": "sha512-IhOwSlhz8wpJnuzTCxZSQWgLGb+VX/8FjoUE7HuARbLXNPYM02vvbdSs+MxVEDgN9k2/i0QC4BrUMstP2VtYEg==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.39.0.tgz", + "integrity": "sha512-DFJRVL8mfOPyfiK8on34kYdvFeV0a0aNGRNUTPuHD2sv2+pMITPSosxRCgPvnFO3oWzEwVBVv/+c5E9fICY7gg==", "hasInstallScript": true, "dependencies": { - "playwright-core": "1.38.1" + "playwright-core": "1.39.0" }, "bin": { "playwright": "cli.js" @@ -1032,16 +1032,16 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright-core": { - "version": "1.38.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.38.1.tgz", - "integrity": "sha512-tQqNFUKa3OfMf4b2jQ7aGLB8o9bS3bOY0yMEtldtC2+spf8QXG9zvXLTXUeRsoNuxEYMgLYR+NXfAa1rjKRcrg==" + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", + "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==" }, "playwright-firefox": { - "version": "1.38.1", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.38.1.tgz", - "integrity": "sha512-IhOwSlhz8wpJnuzTCxZSQWgLGb+VX/8FjoUE7HuARbLXNPYM02vvbdSs+MxVEDgN9k2/i0QC4BrUMstP2VtYEg==", + "version": "1.39.0", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.39.0.tgz", + "integrity": "sha512-DFJRVL8mfOPyfiK8on34kYdvFeV0a0aNGRNUTPuHD2sv2+pMITPSosxRCgPvnFO3oWzEwVBVv/+c5E9fICY7gg==", "requires": { - "playwright-core": "1.38.1" + "playwright-core": "1.39.0" } }, "puppeteer-extra-plugin": { diff --git a/package.json b/package.json index 94a0493..eeb87de 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "enquirer": "^2.4.1", "lowdb": "^6.1.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.38.1", + "playwright-firefox": "^1.39.0", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "repository": { From b99a1542672ce8582fe9527554f2e46c8d71b69d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 2 Nov 2023 16:26:45 +0100 Subject: [PATCH 364/520] Revert "workaround for recordVideo broken in Playwright 1.36" This reverts commit 13b2917dd03c675d6ab9853fe188f15c52782688. Fine to do after upgrade to 1.39 in 75f7d774456d481b0c567d2bb4c00759caebfd0d. which included https://github.com/microsoft/playwright/issues/27086 --- epic-games.js | 2 +- gog.js | 3 +-- prime-gaming.js | 3 +-- unrealengine.js | 2 +- 4 files changed, 4 insertions(+), 6 deletions(-) diff --git a/epic-games.js b/epic-games.js index 0cafd1f..5785b99 100644 --- a/epic-games.js +++ b/epic-games.js @@ -29,7 +29,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { // 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 - recordVideo: cfg.record ? { dir: path.resolve('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 + 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-${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 args: [ // https://peter.sh/experiments/chromium-command-line-switches diff --git a/gog.js b/gog.js index be4514d..aaffd7b 100644 --- a/gog.js +++ b/gog.js @@ -1,5 +1,4 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra -import path from 'path'; import { resolve, jsonDb, datetime, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; @@ -16,7 +15,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, locale: "en-US", // ignore OS locale to be sure to have english text for locators -> done via /en in URL - recordVideo: cfg.record ? { dir: path.resolve('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 + 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/gog-${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 }); diff --git a/prime-gaming.js b/prime-gaming.js index 89691b8..8e630e6 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,6 +1,5 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; -import path from 'path'; import { resolve, jsonDb, datetime, stealth, filenamify, prompt, confirm, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; @@ -18,7 +17,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, locale: "en-US", // ignore OS locale to be sure to have english text for locators - recordVideo: cfg.record ? { dir: path.resolve('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 + 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/pg-${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 }); diff --git a/unrealengine.js b/unrealengine.js index f7ee6fa..64b9c55 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -24,7 +24,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 locale: "en-US", // ignore OS locale to be sure to have english text for locators - recordVideo: cfg.record ? { dir: path.resolve('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 + 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/ue-${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 }); From 32cd0d8990a243375928682b0cfa83269a3df4a9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 6 Nov 2023 19:12:16 +0100 Subject: [PATCH 365/520] Create LICENSE - AGPL-3.0 https://choosealicense.com/licenses/agpl-3.0/ --- LICENSE | 661 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 661 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. From 28a1e42cc4ad4abbd860bd163894f8991349f03a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 6 Nov 2023 19:15:09 +0100 Subject: [PATCH 366/520] npm package*.json license MIT -> AGPL-3.0-only --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2570777..40d4246 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,7 @@ "": { "name": "free-games-claimer", "version": "1.4.0", - "license": "MIT", + "license": "AGPL-3.0-only", "dependencies": { "cross-env": "^7.0.3", "dotenv": "^16.3.1", diff --git a/package.json b/package.json index eeb87de..a67392f 100644 --- a/package.json +++ b/package.json @@ -23,5 +23,5 @@ "url": "https://github.com/vogler/free-games-claimer.git" }, "author": "Ralf Vogler", - "license": "MIT" + "license": "AGPL-3.0-only" } From 0ab9935fb5b38791d93199093156e647d06112c6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 6 Nov 2023 19:22:43 +0100 Subject: [PATCH 367/520] eg: catch timeout in case there are no free games available, #210 --- epic-games.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 5785b99..4f70a6e 100644 --- a/epic-games.js +++ b/epic-games.js @@ -123,7 +123,13 @@ try { // Detect free games const game_loc = page.locator('a:has(span:text-is("Free Now"))'); - await game_loc.last().waitFor(); + await game_loc.last().waitFor().catch(_ => { + // rarely there are no free games available -> catch Timeout + // TODO would be better to wait for alternative like 'coming soon' instead of waiting for timeout + // see https://github.com/vogler/free-games-claimer/issues/210#issuecomment-1727420943 + console.error('Seems like currently there are no free games available in your region...') + // urls below should then be an empty list + }); // clicking on `game_sel` sometimes led to a 404, see https://github.com/vogler/free-games-claimer/issues/25 // 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 From 584130f5d12c8e33739dd07597c46a24f2e2e385 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 11:31:14 +0100 Subject: [PATCH 368/520] edits for #229, build image for PRs from forks? --- .github/workflows/docker.yml | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5c2dade..169c3a0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,9 +9,13 @@ on: - "v*" tags: - "v*" - pull_request: + paths: + - '**' + - '!README.md' + - '!.github/**' + pull_request: # runs when opened/reopned or when the head branch is updated, see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request branches: - - "main" + - "main" # only PRs against main jobs: docker: @@ -39,7 +43,7 @@ jobs: - name: Login to Docker Hub uses: docker/login-action@v3 - if: github.event_name != 'pull_request' + if: github.event_name != 'pull_request' # TODO if DOCKERHUB_* are set? with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -48,22 +52,23 @@ jobs: uses: docker/login-action@v3 with: registry: ghcr.io - username: ${{ github.actor }} + username: ${{ github.actor }} # actor is user that opened PR, was repository_owner before password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v5 - if: github.event_name != 'pull_request' + # if: github.event_name != 'pull_request' # still want to build image with: context: . - push: ${{ github.event_name != 'pull_request' }} + push: ${{ github.event_name != 'pull_request' }} # TODO push for forks? build-args: | COMMIT=${{ github.sha }} BRANCH=${{ env.BRANCH }} NOW=${{ env.NOW }} platforms: linux/amd64,linux/arm64 # ,linux/arm/v7 tags: | - ${{ secrets.DOCKERHUB_USERNAME }}/free-games-claimer:${{env.IMAGE_TAG}} + ${{ secrets.DOCKERHUB_USERNAME }}/free-games-claimer:${{env.IMAGE_TAG}} # TODO if DOCKERHUB_* are set? + ghcr.io/${{ github.actor }}/free-games-claimer:${{env.IMAGE_TAG}} cache-from: type=gha cache-to: type=gha,mode=max From 92ce3d405d41e7ff141acc5ec72d33f706c967bf Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 11:36:17 +0100 Subject: [PATCH 369/520] run docker workflow if its defition changed --- .github/workflows/docker.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 169c3a0..0d75b1e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -9,10 +9,10 @@ on: - "v*" tags: - "v*" - paths: + paths: # ignore changes to certain files - '**' - - '!README.md' - - '!.github/**' + - '!*.md' + # - '!.github/**' pull_request: # runs when opened/reopned or when the head branch is updated, see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request branches: - "main" # only PRs against main From e192365b48c57e4da84e463a444715f8edbef153 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 11:39:08 +0100 Subject: [PATCH 370/520] can't have comment in yml list? --- .github/workflows/docker.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 0d75b1e..a54117c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -66,9 +66,9 @@ jobs: BRANCH=${{ env.BRANCH }} NOW=${{ env.NOW }} platforms: linux/amd64,linux/arm64 # ,linux/arm/v7 + # TODO docker tag only if DOCKERHUB_* are set? tags: | - ${{ secrets.DOCKERHUB_USERNAME }}/free-games-claimer:${{env.IMAGE_TAG}} # TODO if DOCKERHUB_* are set? - + ${{ secrets.DOCKERHUB_USERNAME }}/free-games-claimer:${{env.IMAGE_TAG}} ghcr.io/${{ github.actor }}/free-games-claimer:${{env.IMAGE_TAG}} cache-from: type=gha cache-to: type=gha,mode=max From a62aa8c0c836b98a5330f5e0ae5bcbfab9761a27 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 11:53:29 +0100 Subject: [PATCH 371/520] sonarcloud fix docker: Remove cache after installing packages https://sonarcloud.io/project/issues?resolved=false&types=CODE_SMELL&id=vogler_free-games-claimer&open=AYupZi3__aoWVkCdISRI https://sonarcloud.io/organizations/vogler/rules?open=docker%3AS6587&rule_key=docker%3AS6587&tab=how_to_fix https://askubuntu.com/questions/3167/what-is-difference-between-the-options-autoclean-autoremove-and-clean --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 7a291b3..f44a893 100644 --- a/Dockerfile +++ b/Dockerfile @@ -36,8 +36,8 @@ RUN apt-get update \ libgdk-pixbuf-2.0-0 \ libdbus-glib-1-2 \ libxcursor1 \ - && apt-get autoclean -y \ && apt-get autoremove -y \ + && apt-get clean \ && rm -rf \ /tmp/* \ /usr/share/doc/* \ From a6b9ec96094e1a448b815594a97e1c7164a028ea Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 12:02:33 +0100 Subject: [PATCH 372/520] sonarcloud fix reject(error) https://sonarcloud.io/project/issues?cleanCodeAttributeCategories=CONSISTENT&resolved=false&id=vogler_free-games-claimer&open=AYupZi4O_aoWVkCdISRb&tab=code --- version.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.js b/version.js index 7b9ebef..1df93a1 100644 --- a/version.js +++ b/version.js @@ -13,7 +13,7 @@ const execp = (cmd) => new Promise((resolve, reject) => { if (error.message.includes('command not found')) { console.info('Install git to check for updates!'); } - return reject(); + return reject(error); } resolve(stdout.trim()); }); From b5ef699f4f0387a48f46eecae5536856c94112e9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 12:03:15 +0100 Subject: [PATCH 373/520] sort .dockerignore --- .dockerignore | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.dockerignore b/.dockerignore index 4971835..7fd39f7 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,8 +1,10 @@ -node_modules -data +node_modules/ +data/ +*.env .gitignore +.github/ + **Dockerfile** .dockerignore -.github From e5935faa131187f54cb8b8afe726dd96fcb11fce Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 15:20:58 +0100 Subject: [PATCH 374/520] sonarcloud fixes --- Dockerfile | 2 +- prime-gaming.js | 3 +-- util.js | 15 --------------- xbox.js | 2 -- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/Dockerfile b/Dockerfile index f44a893..a8c5e24 100644 --- a/Dockerfile +++ b/Dockerfile @@ -64,7 +64,7 @@ RUN npm install COPY . . # Shell scripts need Linux line endings. On Windows, git might be configured to check out dos/CRLF line endings, so we convert them for those people in case they want to build the image. They could also use --config core.autocrlf=input -RUN dos2unix *.sh && chmod +x *.sh +RUN dos2unix ./*.sh && chmod +x ./*.sh COPY docker-entrypoint.sh /usr/local/bin/ ARG COMMIT="" diff --git a/prime-gaming.js b/prime-gaming.js index 8e630e6..5acdb06 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -206,7 +206,7 @@ try { // {"reason":"Invalid or no captcha"} // {"reason":"code_used"} // {"reason":"code_not_found"} - if (reason && reason.includes('captcha')) { + if (reason?.includes('captcha')) { redeem_action = 'redeem (got captcha)'; console.error(' Got captcha; could not redeem!'); } else if (reason == 'code_used') { @@ -228,7 +228,6 @@ try { console.log(' Redeemed successfully.'); db.data[user][title].status = 'claimed and redeemed'; } else { - redeem_action = 'redeemed?'; console.debug(` Response 2: ${r2t}`); console.log(' Unknown Response 2 - please report in https://github.com/vogler/free-games-claimer/issues/5'); } diff --git a/util.js b/util.js index 9904486..5b8bef0 100644 --- a/util.js +++ b/util.js @@ -27,21 +27,6 @@ export const handleSIGINT = (context = null) => process.on('SIGINT', async () => if (context) await context.close(); // in order to save recordings also on SIGINT, we need to disable Playwright's handleSIGINT and close the context ourselves }); -// stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 -// gets userAgent and then removes "Headless" from it -const newStealthContext = async (browser, contextOptions = {}, debug = false) => { - if (!debug) { // only need to fix userAgent in headless mode - const dummyContext = await browser.newContext(); - const originalUserAgent = await (await dummyContext.newPage()).evaluate(() => navigator.userAgent); - await dummyContext.close(); - // console.log('originalUserAgent:', originalUserAgent); // Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) HeadlessChrome/96.0.4664.110 Safari/537.36 - contextOptions = { - ...contextOptions, - userAgent: originalUserAgent.replace("Headless", ""), // HeadlessChrome -> Chrome, TODO needed? - }; - } -}; - export const stealth = async (context) => { // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 // https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth/evasions diff --git a/xbox.js b/xbox.js index 501f4f1..a2150f1 100644 --- a/xbox.js +++ b/xbox.js @@ -8,8 +8,6 @@ import { notify, prompt, } from "./util.js"; -import path from "path"; -import { existsSync, writeFileSync } from "fs"; import { cfg } from "./config.js"; // ### SETUP From 798b130c9274a6abdd08b987393db2b1f0d2bf45 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 16:45:17 +0100 Subject: [PATCH 375/520] fix vscode problem in jsconfig with module vs. moduleResolution Option 'module' must be set to 'NodeNext' when option 'moduleResolution' is set to 'NodeNext'.ts Specify what module code is generated. See more: https://www.typescriptlang.org/tsconfig#module --- jsconfig.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jsconfig.json b/jsconfig.json index 1b438cb..2e21de9 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -2,7 +2,7 @@ "compilerOptions": { "checkJs": true, "target": "es2021", - "module": "esnext", + "module": "NodeNext", "moduleResolution": "NodeNext", // https://github.com/typicode/lowdb/issues/554 }, "exclude": ["node_modules", "**/node_modules"] From 2eaf6f059855187200d60bbf2876ec0d6638acf3 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 17:57:10 +0100 Subject: [PATCH 376/520] .eslintignore -> .gitignore, no-unused-vars: ignore args starting with _ https://eslint.org/docs/latest/rules/no-unused-vars#argsignorepattern https://eslint.org/docs/latest/rules/no-undef --- .eslintignore | 1 + .eslintrc.cjs | 1 + epic-games.js | 1 + 3 files changed, 3 insertions(+) create mode 120000 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 120000 index 0000000..3e4e48b --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +.gitignore \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs index c2d8ac6..21ca7c5 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -23,5 +23,6 @@ module.exports = { }, 'rules': { 'semi': 'error', + 'no-unused-vars': ["error", { "argsIgnorePattern": "^_" }], }, }; diff --git a/epic-games.js b/epic-games.js index 4f70a6e..8fc78ca 100644 --- a/epic-games.js +++ b/epic-games.js @@ -51,6 +51,7 @@ if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); +// eslint-disable-next-line no-undef if (cfg.debug) console.debug(await page.evaluate(() => window.screen)); if (cfg.record && cfg.debug) { // const filter = _ => true; From 5083ea408df9b7ce7b4f5fb198267f796d699a9a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 17:58:36 +0100 Subject: [PATCH 377/520] ncu -u: eslint 8.50.0 -> 8.53.0 --- package-lock.json | 128 +++++++++++++++++++++++++--------------------- package.json | 2 +- 2 files changed, 72 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index b725f4d..ec60dff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { - "eslint": "^8.50.0" + "eslint": "^8.53.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -55,9 +55,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -78,21 +78,21 @@ } }, "node_modules/@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" }, @@ -114,9 +114,9 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "node_modules/@nodelib/fs.scandir": { @@ -209,10 +209,16 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -476,18 +482,19 @@ } }, "node_modules/eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -760,9 +767,9 @@ } }, "node_modules/globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1219,9 +1226,9 @@ } }, "node_modules/punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "engines": { "node": ">=6" @@ -1613,9 +1620,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", - "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", + "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -1630,18 +1637,18 @@ } }, "@eslint/js": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", - "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", + "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", "dev": true }, "@humanwhocodes/config-array": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", - "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "version": "0.11.13", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", + "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", "dev": true, "requires": { - "@humanwhocodes/object-schema": "^1.2.1", + "@humanwhocodes/object-schema": "^2.0.1", "debug": "^4.1.1", "minimatch": "^3.0.5" } @@ -1653,9 +1660,9 @@ "dev": true }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", + "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", "dev": true }, "@nodelib/fs.scandir": { @@ -1739,10 +1746,16 @@ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, + "@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "acorn": { - "version": "8.10.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", - "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "version": "8.11.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", + "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", "dev": true }, "acorn-jsx": { @@ -1923,18 +1936,19 @@ "dev": true }, "eslint": { - "version": "8.50.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", - "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "version": "8.53.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", + "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.2", - "@eslint/js": "8.50.0", - "@humanwhocodes/config-array": "^0.11.11", + "@eslint/eslintrc": "^2.1.3", + "@eslint/js": "8.53.0", + "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", @@ -2138,9 +2152,9 @@ } }, "globals": { - "version": "13.22.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", - "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "version": "13.23.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", + "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -2479,9 +2493,9 @@ "dev": true }, "punycode": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", - "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, "puppeteer-extra-plugin": { diff --git a/package.json b/package.json index 6771ee4..189e38e 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,6 @@ "author": "Ralf Vogler", "license": "AGPL-3.0-only", "devDependencies": { - "eslint": "^8.50.0" + "eslint": "^8.53.0" } } From 6a34cb541de3504668aad14293e14d0642ca6d3b Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 18:03:54 +0100 Subject: [PATCH 378/520] format sonar.yml --- .github/workflows/sonar.yml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 62067f3..bb2f89f 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -1,5 +1,5 @@ on: - # Trigger analysis when pushing in master or pull requests, and when creating a pull request. + # Trigger analysis when pushing in main or pull requests, and when creating a pull request. push: branches: - main @@ -10,18 +10,23 @@ jobs: sonarcloud: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - + uses: actions/checkout@v4 with: # Disabling shallow clone is recommended for improving relevancy of reporting fetch-depth: 0 - - name: Install modules + - + name: Install modules run: npm install -g eslint - - name: Run ESLint + - + name: Run ESLint continue-on-error: true run: eslint . --ext .js,.ts -f json -o eslint_report.json - - name: Fix ESLint paths + - + name: Fix ESLint paths run: sed -i 's+/home/runner/work/free-games-claimer/free-games-claimer+/github/workspace+g' eslint_report.json - - name: SonarCloud Scan + - + name: SonarCloud Scan uses: sonarsource/sonarcloud-github-action@master env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From e4e4f2eafa2a186bf25942bac5b06e9053435910 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 18:10:22 +0100 Subject: [PATCH 379/520] fix eslint errors --- epic-games.js | 10 +++++----- notify-test.js | 1 + prime-gaming.js | 2 +- util.js | 7 ++++--- version.js | 7 +++---- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/epic-games.js b/epic-games.js index 8fc78ca..dcfce73 100644 --- a/epic-games.js +++ b/epic-games.js @@ -94,7 +94,7 @@ try { await notify('epic-games: got captcha during login. Please check.'); }).catch(_ => { }); page.waitForSelector('h6:has-text("Incorrect response.")').then(async () => { - console.error('Incorrect repsonse for captcha!') + console.error('Incorrect repsonse for captcha!'); }).catch(_ => { }); // handle MFA, but don't await it page.waitForURL('**/id/login/mfa**').then(async () => { @@ -128,7 +128,7 @@ try { // rarely there are no free games available -> catch Timeout // TODO would be better to wait for alternative like 'coming soon' instead of waiting for timeout // see https://github.com/vogler/free-games-claimer/issues/210#issuecomment-1727420943 - console.error('Seems like currently there are no free games available in your region...') + console.error('Seems like currently there are no free games available in your region...'); // urls below should then be an empty list }); // clicking on `game_sel` sometimes led to a 404, see https://github.com/vogler/free-games-claimer/issues/25 @@ -227,8 +227,8 @@ try { const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); captcha.waitFor().then(async () => { // don't await, since element may not be shown // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') - console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.') - await notify('epic-games: got captcha challenge right before claim. Use VNC to solve it manually.') + console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.'); + await notify('epic-games: got captcha challenge right before claim. Use VNC to solve it manually.'); // await page.waitForTimeout(2000); // const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); // await captcha.screenshot({ path: p }); @@ -269,5 +269,5 @@ try { } } if (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); -if (page.video()) console.log('Recorded video:', await page.video().path()) +if (page.video()) console.log('Recorded video:', await page.video().path()); await context.close(); diff --git a/notify-test.js b/notify-test.js index d8f4dab..d138657 100644 --- a/notify-test.js +++ b/notify-test.js @@ -1,3 +1,4 @@ +/* eslint-disable no-constant-condition */ import { delay, html_game_list, notify } from "./util.js"; const URL_CLAIM = 'https://gaming.amazon.com/home'; // dummy URL diff --git a/prime-gaming.js b/prime-gaming.js index 5acdb06..ae7879e 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -394,5 +394,5 @@ try { notify(`prime-gaming (${user}):
${html_game_list(notify_games)}`); } } -if (page.video()) console.log('Recorded video:', await page.video().path()) +if (page.video()) console.log('Recorded video:', await page.video().path()); await context.close(); diff --git a/util.js b/util.js index 5b8bef0..0ab0912 100644 --- a/util.js +++ b/util.js @@ -74,11 +74,12 @@ const timeoutPlugin = timeout => enquirer => { // cancel prompt after timeout ms prompt.on('submit', _ => clearTimeout(t)); prompt.on('cancel', _ => clearTimeout(t)); }); -} +}; enquirer.use(timeoutPlugin(cfg.login_timeout)); // TODO may not want to have this timeout for all prompts; better extend Prompt and add a timeout prompt option // single prompt that just returns the non-empty value instead of an object +// @ts-ignore export const prompt = o => enquirer.prompt({name: 'name', type: 'input', message: 'Enter value', ...o}).then(r => r.name).catch(_ => {}); -export const confirm = o => prompt({type: 'confirm', message: 'Continue?', ...o}) +export const confirm = o => prompt({type: 'confirm', message: 'Continue?', ...o}); // notifications via apprise CLI import { exec } from 'child_process'; @@ -93,7 +94,7 @@ export const notify = (html) => new Promise((resolve, reject) => { if (error.message.includes('command not found')) { console.info('Run `pip install apprise`. See https://github.com/vogler/free-games-claimer#notifications'); } - return resolve(); + return reject(error); } if (stderr) console.error(`stderr: ${stderr}`); if (stdout) console.log(`stdout: ${stdout}`); diff --git a/version.js b/version.js index 1df93a1..d992afb 100644 --- a/version.js +++ b/version.js @@ -1,7 +1,6 @@ // check if running the latest version import {log} from 'console'; -import { readFileSync } from 'fs'; import { exec } from 'child_process'; const execp = (cmd) => new Promise((resolve, reject) => { @@ -19,7 +18,7 @@ const execp = (cmd) => new Promise((resolve, reject) => { }); }); -const git_main = () => readFileSync('.git/refs/heads/main').toString().trim(); +// const git_main = () => readFileSync('.git/refs/heads/main').toString().trim(); let sha, date; // if (existsSync('/.dockerenv')) { // did not work @@ -44,7 +43,7 @@ log('Local commit:', sha, new Date(date)); log('Online commit:', gh.sha, new Date(gh.commit.committer.date)); if (sha == gh.sha) { - log('Running the latest version!') + log('Running the latest version!'); } else { - log('Not running the latest version!') + log('Not running the latest version!'); } From efe4faab3e3027fb347b39f6ac349fcd55ff3a4d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 7 Nov 2023 18:24:32 +0100 Subject: [PATCH 380/520] add sonarcloud badge for code smells --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 18cf520..5c9e90f 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,7 @@ logo-free-games-claimer

+[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=vogler_free-games-claimer&metric=code_smells)](https://sonarcloud.io/project/overview?id=vogler_free-games-claimer) # free-games-claimer Claims free games periodically on From 011eddf97a096041346cc390e340b70fea5d1290 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 8 Nov 2023 01:02:43 +0100 Subject: [PATCH 381/520] eslint: enable most stylistic rules https://eslint.style/packages/js --- .eslintrc.cjs | 82 ++++++++++++++++++++++++++++++++++++++--------- package-lock.json | 31 ++++++++++++++++++ package.json | 1 + 3 files changed, 98 insertions(+), 16 deletions(-) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 21ca7c5..04989aa 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -1,28 +1,78 @@ module.exports = { - 'env': { - 'es2021': true, - 'node': true, + env: { + es2021: true, + node: true, }, - "extends": "eslint:recommended", - 'overrides': [ + extends: 'eslint:recommended', + overrides: [ { - 'env': { - 'node': true, + env: { + node: true, }, - 'files': [ + files: [ '.eslintrc.{js,cjs}', ], - 'parserOptions': { - 'sourceType': 'script', + parserOptions: { + sourceType: 'script', }, }, ], - 'parserOptions': { - 'ecmaVersion': 'latest', - 'sourceType': 'module', + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', }, - 'rules': { - 'semi': 'error', - 'no-unused-vars': ["error", { "argsIgnorePattern": "^_" }], + plugins: [ + '@stylistic/js', + ], + // https://eslint.org/docs/latest/rules/ + // https://eslint.style/packages/js + rules: { + 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@stylistic/js/array-bracket-newline': ['error', 'consistent'], + '@stylistic/js/array-bracket-spacing': 'error', + '@stylistic/js/array-element-newline': ['error', 'consistent'], + '@stylistic/js/arrow-parens': ['error', 'as-needed'], + '@stylistic/js/arrow-spacing': 'error', + '@stylistic/js/block-spacing': 'error', + '@stylistic/js/brace-style': 'error', + '@stylistic/js/comma-dangle': ['error', 'always-multiline'], + '@stylistic/js/comma-spacing': 'error', + '@stylistic/js/comma-style': 'error', + '@stylistic/js/eol-last': 'error', + '@stylistic/js/func-call-spacing': 'error', + '@stylistic/js/function-paren-newline': ['error', 'consistent'], + '@stylistic/js/implicit-arrow-linebreak': 'error', + '@stylistic/js/indent': ['error', 2], + '@stylistic/js/key-spacing': 'error', + '@stylistic/js/keyword-spacing': 'error', + '@stylistic/js/linebreak-style': 'error', + '@stylistic/js/no-extra-parens': 'error', + '@stylistic/js/no-extra-semi': 'error', + '@stylistic/js/no-mixed-spaces-and-tabs': 'error', + '@stylistic/js/no-multi-spaces': 'error', + '@stylistic/js/no-multiple-empty-lines': 'error', + '@stylistic/js/no-tabs': 'error', + '@stylistic/js/no-trailing-spaces': 'error', + '@stylistic/js/no-whitespace-before-property': 'error', + '@stylistic/js/nonblock-statement-body-position': 'error', + '@stylistic/js/object-curly-newline': 'error', + '@stylistic/js/object-curly-spacing': ['error', 'always'], + '@stylistic/js/object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }], + '@stylistic/js/quote-props': ['error', 'as-needed'], + '@stylistic/js/quotes': ['error', 'single'], + '@stylistic/js/rest-spread-spacing': 'error', + '@stylistic/js/semi': 'error', + '@stylistic/js/semi-spacing': 'error', + '@stylistic/js/semi-style': 'error', + '@stylistic/js/space-before-blocks': 'error', + '@stylistic/js/space-before-function-paren': ['error', { anonymous: 'never', named: 'never', asyncArrow: 'always' }], + '@stylistic/js/space-in-parens': 'error', + '@stylistic/js/space-infix-ops': 'error', + '@stylistic/js/space-unary-ops': 'error', + '@stylistic/js/spaced-comment': 'error', + '@stylistic/js/switch-colon-spacing': 'error', + '@stylistic/js/template-curly-spacing': 'error', + '@stylistic/js/template-tag-spacing': 'error', + '@stylistic/js/wrap-regex': 'error', }, }; diff --git a/package-lock.json b/package-lock.json index ec60dff..4736f13 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { + "@stylistic/eslint-plugin-js": "^1.0.1", "eslint": "^8.53.0" } }, @@ -196,6 +197,21 @@ "@otplib/plugin-thirty-two": "^12.0.1" } }, + "node_modules/@stylistic/eslint-plugin-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.0.1.tgz", + "integrity": "sha512-SfJlEnmBowaWx9GyN/7vQ/7jQP2wVQe5CcaoVL6V5nmCWl9Q+VSeJPSBOjB7XOYSYL1HoEQsvA+8Hy7Zt2XrnA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "acorn": "^8.11.2", + "escape-string-regexp": "^4.0.0", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esutils": "^2.0.3", + "graphemer": "^1.4.0" + } + }, "node_modules/@types/debug": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", @@ -1733,6 +1749,21 @@ "@otplib/plugin-thirty-two": "^12.0.1" } }, + "@stylistic/eslint-plugin-js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.0.1.tgz", + "integrity": "sha512-SfJlEnmBowaWx9GyN/7vQ/7jQP2wVQe5CcaoVL6V5nmCWl9Q+VSeJPSBOjB7XOYSYL1HoEQsvA+8Hy7Zt2XrnA==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.4.0", + "acorn": "^8.11.2", + "escape-string-regexp": "^4.0.0", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esutils": "^2.0.3", + "graphemer": "^1.4.0" + } + }, "@types/debug": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", diff --git a/package.json b/package.json index 189e38e..fe2b52b 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "author": "Ralf Vogler", "license": "AGPL-3.0-only", "devDependencies": { + "@stylistic/eslint-plugin-js": "^1.0.1", "eslint": "^8.53.0" } } From 0832ae57f5e5746b1ee03abafdea1dbd66055aa8 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 8 Nov 2023 01:16:49 +0100 Subject: [PATCH 382/520] run `eslint --fix .` --- config.js | 6 +- epic-games.js | 19 ++- gog.js | 25 ++-- migrate.js | 3 +- notify-test.js | 2 +- prime-gaming.js | 29 ++-- unrealengine.js | 25 ++-- util.js | 19 ++- version.js | 8 +- xbox.js | 386 ++++++++++++++++++++++++------------------------ 10 files changed, 259 insertions(+), 263 deletions(-) diff --git a/config.js b/config.js index f12f99e..0962159 100644 --- a/config.js +++ b/config.js @@ -11,7 +11,9 @@ export const cfg = { dryrun: process.env.DRYRUN == '1', // don't claim anything interactive: process.env.INTERACTIVE == '1', // confirm to claim, default skip show: process.env.SHOW == '1', // run non-headless - get headless() { return !this.debug && !this.show }, + get headless() { + return !this.debug && !this.show; + }, width: Number(process.env.WIDTH) || 1920, // width of the opened browser height: Number(process.env.HEIGHT) || 1080, // height of the opened browser timeout: (Number(process.env.TIMEOUT) || 60) * 1000, // default timeout for playwright is 30s @@ -23,7 +25,7 @@ export const cfg = { return { browser: process.env.BROWSER_DIR || dataDir('browser'), // for multiple accounts or testing screenshots: process.env.SCREENSHOTS_DIR || dataDir('screenshots'), // set to 0 to disable screenshots - } + }; }, // auth epic-games eg_email: process.env.EG_EMAIL || process.env.EMAIL, diff --git a/epic-games.js b/epic-games.js index dcfce73..06d66cc 100644 --- a/epic-games.js +++ b/epic-games.js @@ -28,7 +28,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // 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-${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 @@ -64,7 +64,7 @@ const notify_games = []; let user; try { - await context.addCookies([{name: 'OptanonAlertBoxClosed', value: new Date(Date.now() - 5*24*60*60*1000).toISOString(), domain: '.epicgames.com', path: '/'}]); // Accept cookies to get rid of banner to save space on screen. Set accept time to 5 days ago. + await context.addCookies([{ name: 'OptanonAlertBoxClosed', value: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), domain: '.epicgames.com', path: '/' }]); // Accept cookies to get rid of banner to save space on screen. Set accept time to 5 days ago. await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto @@ -77,12 +77,12 @@ try { console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); if (cfg.novnc_port) console.info(`Open http://localhost:${cfg.novnc_port} to login inside the docker container.`); if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in - console.info(`Login timeout is ${cfg.login_timeout/1000} seconds!`); + console.info(`Login timeout is ${cfg.login_timeout / 1000} seconds!`); await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); if (cfg.eg_email && cfg.eg_password) console.info('Using email and password from environment.'); else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); - const email = cfg.eg_email || await prompt({message: 'Enter email'}); - const password = email && (cfg.eg_password || await prompt({type: 'password', message: 'Enter password'})); + const email = cfg.eg_email || await prompt({ message: 'Enter email' }); + const password = email && (cfg.eg_password || await prompt({ type: 'password', message: 'Enter password' })); if (email && password) { // await page.click('text=Sign in with Epic Games'); await page.fill('#email', email); @@ -100,7 +100,7 @@ try { page.waitForURL('**/id/login/mfa**').then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); // TODO locator for text (email or app?) - const otp = cfg.eg_otpkey && authenticator.generate(cfg.eg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + const otp = cfg.eg_otpkey && authenticator.generate(cfg.eg_otpkey) || await prompt({ type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!' }); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them await page.locator('input[name="code-input-0"]').pressSequentially(otp.toString()); await page.click('button[type="submit"]'); }).catch(_ => { }); @@ -134,7 +134,7 @@ try { // clicking on `game_sel` sometimes led to a 404, see https://github.com/vogler/free-games-claimer/issues/25 // 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 - // 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 + // 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 urls = urlSlugs.map(s => 'https://store.epicgames.com' + s); console.log('Free games:', urls); @@ -235,7 +235,7 @@ try { // console.info(' Saved a screenshot of hcaptcha challenge to', p); // console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? }).catch(_ => { }); // may time out if not shown - await page.locator('text=Thanks for your order!').waitFor({state: 'attached'}); + await page.locator('text=Thanks for your order!').waitFor({ state: 'attached' }); db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time console.log(' Claimed successfully!'); @@ -260,8 +260,7 @@ try { process.exitCode ||= 1; console.error('--- Exception:'); console.error(error); // .toString()? - if (error.message && process.exitCode != 130) - notify(`epic-games failed: ${error.message.split('\n')[0]}`); + if (error.message && process.exitCode != 130) notify(`epic-games failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db if (notify_games.filter(g => g.status == 'claimed' || g.status == 'failed').length) { // don't notify if all have status 'existed', 'manual', 'requires base game', 'unavailable-in-region', 'skipped' diff --git a/gog.js b/gog.js index aaffd7b..c686851 100644 --- a/gog.js +++ b/gog.js @@ -14,7 +14,7 @@ const db = await jsonDb('gog.json', {}); const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, - locale: "en-US", // ignore OS locale to be sure to have english text for locators -> done via /en in URL + locale: 'en-US', // ignore OS locale to be sure to have english text for locators -> done via /en in URL 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/gog-${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 @@ -31,7 +31,7 @@ const notify_games = []; let user; try { - await context.addCookies([{name: 'CookieConsent', value: '{stamp:%274oR8MJL+bxVlG6g+kl2we5+suMJ+Tv7I4C5d4k+YY4vrnhCD+P23RQ==%27%2Cnecessary:true%2Cpreferences:true%2Cstatistics:true%2Cmarketing:true%2Cmethod:%27explicit%27%2Cver:1%2Cutc:1672331618201%2Cregion:%27de%27}', domain: 'www.gog.com', path: '/'}]); // to not waste screen space when non-headless + await context.addCookies([{ name: 'CookieConsent', value: '{stamp:%274oR8MJL+bxVlG6g+kl2we5+suMJ+Tv7I4C5d4k+YY4vrnhCD+P23RQ==%27%2Cnecessary:true%2Cpreferences:true%2Cstatistics:true%2Cmarketing:true%2Cmethod:%27explicit%27%2Cver:1%2Cutc:1672331618201%2Cregion:%27de%27}', domain: 'www.gog.com', path: '/' }]); // to not waste screen space when non-headless await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever @@ -45,11 +45,11 @@ try { await page.waitForSelector('#GalaxyAccountsFrameContainer iframe'); // TODO needed? const iframe = page.frameLocator('#GalaxyAccountsFrameContainer iframe'); if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in - console.info(`Login timeout is ${cfg.login_timeout/1000} seconds!`); + console.info(`Login timeout is ${cfg.login_timeout / 1000} seconds!`); if (cfg.gog_email && cfg.gog_password) console.info('Using email and password from environment.'); else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); - const email = cfg.gog_email || await prompt({message: 'Enter email'}); - const password = email && (cfg.gog_password || await prompt({type: 'password', message: 'Enter password'})); + const email = cfg.gog_email || await prompt({ message: 'Enter email' }); + const password = email && (cfg.gog_password || await prompt({ type: 'password', message: 'Enter password' })); if (email && password) { iframe.locator('a[href="/logout"]').click().catch(_ => { }); // Click 'Change account' (email from previous login is set in some cookie) await iframe.locator('#login_username').fill(email); @@ -58,9 +58,9 @@ try { // handle MFA, but don't await it iframe.locator('form[name=second_step_authentication]').waitFor().then(async () => { console.log('Two-Step Verification - Enter security code'); - console.log(await iframe.locator('.form__description').innerText()) - const otp = await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 4 || 'The code must be 4 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them - await iframe.locator('#second_step_authentication_token_letter_1').pressSequentially(otp.toString(), {delay: 10}); + console.log(await iframe.locator('.form__description').innerText()); + const otp = await prompt({ type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 4 || 'The code must be 4 digits!' }); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + await iframe.locator('#second_step_authentication_token_letter_1').pressSequentially(otp.toString(), { delay: 10 }); await iframe.locator('#second_step_authentication_send').click(); await page.waitForTimeout(1000); // TODO still needed with wait for username below? }).catch(_ => { }); @@ -71,7 +71,7 @@ try { notify('gog: got captcha during login. Please check.'); // TODO solve reCAPTCHA? }).catch(_ => { }); - await page.waitForSelector('#menuUsername') + await page.waitForSelector('#menuUsername'); } else { console.log('Waiting for you to login in the browser.'); await notify('gog: no longer signed in and not enough options set for automatic login.'); @@ -129,7 +129,7 @@ try { notify_games.push({ title, url, status }); if (status == 'claimed' && !cfg.gog_newsletter) { - console.log("Unsubscribe from 'Promotions and hot deals' newsletter"); + console.log('Unsubscribe from \'Promotions and hot deals\' newsletter'); await page.goto('https://www.gog.com/en/account/settings/subscriptions'); await page.locator('li:has-text("Marketing communications through Trusted Partners") label').uncheck(); await page.locator('li:has-text("Promotions and hot deals") label').uncheck(); @@ -139,13 +139,12 @@ try { process.exitCode ||= 1; console.error('--- Exception:'); console.error(error); // .toString()? - if (error.message && process.exitCode != 130) - notify(`gog failed: ${error.message.split('\n')[0]}`); + if (error.message && process.exitCode != 130) notify(`gog failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed notify(`gog (${user}):
${html_game_list(notify_games)}`); } } -if (page.video()) console.log('Recorded video:', await page.video().path()) +if (page.video()) console.log('Recorded video:', await page.video().path()); await context.close(); diff --git a/migrate.js b/migrate.js index 41bbe13..b2db945 100644 --- a/migrate.js +++ b/migrate.js @@ -4,8 +4,7 @@ import { JSONFile } from 'lowdb/node'; import { datetime } from './util.js'; const datetime_UTCtoLocalTimezone = async file => { - if (!existsSync(file)) - return console.error('File does not exist:', file); + if (!existsSync(file)) return console.error('File does not exist:', file); const db = new Low(new JSONFile(file)); await db.read(); db.data ||= {}; diff --git a/notify-test.js b/notify-test.js index d138657..9d593c0 100644 --- a/notify-test.js +++ b/notify-test.js @@ -1,5 +1,5 @@ /* eslint-disable no-constant-condition */ -import { delay, html_game_list, notify } from "./util.js"; +import { delay, html_game_list, notify } from './util.js'; const URL_CLAIM = 'https://gaming.amazon.com/home'; // dummy URL diff --git a/prime-gaming.js b/prime-gaming.js index ae7879e..e60caf7 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -16,7 +16,7 @@ const db = await jsonDb('prime-gaming.json', {}); const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, - 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/pg-${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 @@ -44,11 +44,11 @@ try { console.error('Not signed in anymore.'); await page.click('button:has-text("Sign in")'); if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in - console.info(`Login timeout is ${cfg.login_timeout/1000} seconds!`); + console.info(`Login timeout is ${cfg.login_timeout / 1000} seconds!`); if (cfg.pg_email && cfg.pg_password) console.info('Using email and password from environment.'); else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); - const email = cfg.pg_email || await prompt({message: 'Enter email'}); - const password = email && (cfg.pg_password || await prompt({type: 'password', message: 'Enter password'})); + const email = cfg.pg_email || await prompt({ message: 'Enter email' }); + const password = email && (cfg.pg_password || await prompt({ type: 'password', message: 'Enter password' })); if (email && password) { await page.fill('[name=email]', email); await page.fill('[name=password]', password); @@ -66,7 +66,7 @@ try { page.waitForURL('**/ap/mfa**').then(async () => { console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App'); await page.check('[name=rememberDevice]'); - const otp = cfg.pg_otpkey && authenticator.generate(cfg.pg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + const otp = cfg.pg_otpkey && authenticator.generate(cfg.pg_otpkey) || await prompt({ type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!' }); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them await page.locator('input[name=otpCode]').pressSequentially(otp.toString()); await page.click('input[type="submit"]'); }).catch(_ => { }); @@ -128,10 +128,10 @@ try { const slug = await card.locator('a:has-text("Claim")').first().getAttribute('href'); const url = 'https://gaming.amazon.com' + slug.split('?')[0]; // await (await card.$('text=Claim')).click(); // goes to URL of game, no need to wait - external_info.push({title, url}); + external_info.push({ title, url }); } - for (const {title, url} of external_info) { - console.log('Current free game:', title); //, url); + for (const { title, url } of external_info) { + console.log('Current free game:', title); // , url); await page.goto(url, { waitUntil: 'domcontentloaded' }); if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; @@ -224,9 +224,9 @@ try { await page2.click('[type="submit"]'); // click Redeem const r2t = await (await r2).text(); if (r2t == '{}') { - redeem_action = 'redeemed'; - console.log(' Redeemed successfully.'); - db.data[user][title].status = 'claimed and redeemed'; + redeem_action = 'redeemed'; + console.log(' Redeemed successfully.'); + db.data[user][title].status = 'claimed and redeemed'; } else { console.debug(` Response 2: ${r2t}`); console.log(' Unknown Response 2 - please report in https://github.com/vogler/free-games-claimer/issues/5'); @@ -298,7 +298,7 @@ try { await page.keyboard.press('End'); // scroll to bottom to show all games await page.waitForTimeout(1000); // wait for fade in animation const viewportSize = page.viewportSize(); // current viewport size - await page.setViewportSize({...viewportSize, height: 3000}); // increase height, otherwise element screenshot is cut off at the top and bottom + await page.setViewportSize({ ...viewportSize, height: 3000 }); // increase height, otherwise element screenshot is cut off at the top and bottom await games.screenshot({ path: p }); // screenshot of all claimed games } @@ -357,7 +357,7 @@ try { console.debug(' LinkAccountButton label:', unlinked_store); const match = unlinked_store.match(/Link (.*) account/); if (match && match.length == 2) unlinked_store = match[1]; - } else if(await page.locator('text=Link game account').count()) { // epic-games only? + } else if (await page.locator('text=Link game account').count()) { // epic-games only? console.error(' Missing account linking (epic-games specific button?):', await page.locator('button[data-a-target="gms-cta"]').innerText()); // TODO needed? unlinked_store = 'epic-games'; } @@ -386,8 +386,7 @@ try { process.exitCode ||= 1; console.error('--- Exception:'); console.error(error); // .toString()? - if (error.message && process.exitCode != 130) - notify(`prime-gaming failed: ${error.message.split('\n')[0]}`); + if (error.message && process.exitCode != 130) notify(`prime-gaming failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db if (notify_games.length) { // list should only include claimed games diff --git a/unrealengine.js b/unrealengine.js index 64b9c55..5bbf988 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -23,7 +23,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { viewport: { width: cfg.width, height: cfg.height }, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.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/ue-${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 @@ -42,7 +42,7 @@ const notify_games = []; let user; try { - await context.addCookies([{name: 'OptanonAlertBoxClosed', value: new Date(Date.now() - 5*24*60*60*1000).toISOString(), domain: '.epicgames.com', path: '/'}]); // Accept cookies to get rid of banner to save space on screen. Set accept time to 5 days ago. + await context.addCookies([{ name: 'OptanonAlertBoxClosed', value: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), domain: '.epicgames.com', path: '/' }]); // Accept cookies to get rid of banner to save space on screen. Set accept time to 5 days ago. await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto @@ -52,12 +52,12 @@ try { console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); if (cfg.novnc_port) console.info(`Open http://localhost:${cfg.novnc_port} to login inside the docker container.`); if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in - console.info(`Login timeout is ${cfg.login_timeout/1000} seconds!`); + console.info(`Login timeout is ${cfg.login_timeout / 1000} seconds!`); await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); if (cfg.eg_email && cfg.eg_password) console.info('Using email and password from environment.'); else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); - const email = cfg.eg_email || await prompt({message: 'Enter email'}); - const password = email && (cfg.eg_password || await prompt({type: 'password', message: 'Enter password'})); + const email = cfg.eg_email || await prompt({ message: 'Enter email' }); + const password = email && (cfg.eg_password || await prompt({ type: 'password', message: 'Enter password' })); if (email && password) { await page.click('text=Sign in with Epic Games'); await page.fill('#email', email); @@ -71,7 +71,7 @@ try { page.waitForURL('**/id/login/mfa**').then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); // TODO locator for text (email or app?) - const otp = cfg.eg_otpkey && authenticator.generate(cfg.eg_otpkey) || await prompt({type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!'}); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + const otp = cfg.eg_otpkey && authenticator.generate(cfg.eg_otpkey) || await prompt({ type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!' }); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them await page.locator('input[name="code-input-0"]').pressSequentially(otp.toString()); await page.click('button[type="submit"]'); }).catch(_ => { }); @@ -105,7 +105,7 @@ try { const notify_game = { title, url, status: 'failed' }; notify_games.push(notify_game); // status is updated below // if (await p.locator('.btn .add-review-btn').count()) { // did not work - if((await p.getAttribute('class')).includes('asset--owned')) { + if ((await p.getAttribute('class')).includes('asset--owned')) { console.log(' ↳ Already claimed'); if (db.data[user][id].status != 'claimed') { db.data[user][id].status = 'existed'; @@ -128,7 +128,7 @@ try { const price = (await page.locator('.shopping-cart .total .price').innerText()).split(' '); console.log('Price: ', price[1], 'instead of', price[0]); if (price[1] != '0') { - const err = 'Price is not 0! Exit! Please report.' + const err = 'Price is not 0! Exit! Please report.'; console.error(err); notify('unrealengine: ' + err); process.exit(1); @@ -142,7 +142,7 @@ try { // maybe: Accept End User License Agreement page.locator('[name=accept-label]').check().then(() => { console.log('Accept End User License Agreement'); - page.locator('span:text-is("Accept")').click() // otherwise matches 'Accept All Cookies' + page.locator('span:text-is("Accept")').click(); // otherwise matches 'Accept All Cookies' }).catch(_ => { }); await page.waitForSelector('#webPurchaseContainer iframe'); // TODO needed? const iframe = page.frameLocator('#webPurchaseContainer iframe'); @@ -165,7 +165,7 @@ try { const captcha = iframe.locator('#h_captcha_challenge_checkout_free_prod iframe'); captcha.waitFor().then(async () => { // don't await, since element may not be shown // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') - console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.') + console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.'); }).catch(_ => { }); // may time out if not shown await page.waitForSelector('text=Thank you'); for (const id of ids) { @@ -192,8 +192,7 @@ try { process.exitCode ||= 1; console.error('--- Exception:'); console.error(error); // .toString()? - if (error.message && process.exitCode != 130) - notify(`unrealengine failed: ${error.message.split('\n')[0]}`); + if (error.message && process.exitCode != 130) notify(`unrealengine failed: ${error.message.split('\n')[0]}`); } finally { await db.write(); // write out json db if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed @@ -201,5 +200,5 @@ try { } } if (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); -if (page.video()) console.log('Recorded video:', await page.video().path()) +if (page.video()) console.log('Recorded video:', await page.video().path()); await context.close(); diff --git a/util.js b/util.js index 0ab0912..16ae806 100644 --- a/util.js +++ b/util.js @@ -27,7 +27,7 @@ export const handleSIGINT = (context = null) => process.on('SIGINT', async () => if (context) await context.close(); // in order to save recordings also on SIGINT, we need to disable Playwright's handleSIGINT and close the context ourselves }); -export const stealth = async (context) => { +export const stealth = async context => { // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 // https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth/evasions const enabledEvasions = [ @@ -47,13 +47,13 @@ export const stealth = async (context) => { 'sourceurl', // 'user-agent-override', // doesn't work since playwright has no page.browser() 'webgl.vendor', - 'window.outerdimensions' + 'window.outerdimensions', ]; const stealth = { callbacks: [], async evaluateOnNewDocument(...args) { this.callbacks.push({ cb: args[0], a: args[1] }); - } + }, }; for (const e of enabledEvasions) { const evasion = await import(`puppeteer-extra-plugin-stealth/evasions/${e}/index.js`); @@ -70,7 +70,10 @@ export const stealth = async (context) => { import Enquirer from 'enquirer'; const enquirer = new Enquirer(); const timeoutPlugin = timeout => enquirer => { // cancel prompt after timeout ms enquirer.on('prompt', prompt => { - const t = setTimeout(() => { prompt.hint = () => 'timeout'; prompt.cancel(); }, timeout); + const t = setTimeout(() => { + prompt.hint = () => 'timeout'; + prompt.cancel(); + }, timeout); prompt.on('submit', _ => clearTimeout(t)); prompt.on('cancel', _ => clearTimeout(t)); }); @@ -78,14 +81,14 @@ const timeoutPlugin = timeout => enquirer => { // cancel prompt after timeout ms enquirer.use(timeoutPlugin(cfg.login_timeout)); // TODO may not want to have this timeout for all prompts; better extend Prompt and add a timeout prompt option // single prompt that just returns the non-empty value instead of an object // @ts-ignore -export const prompt = o => enquirer.prompt({name: 'name', type: 'input', message: 'Enter value', ...o}).then(r => r.name).catch(_ => {}); -export const confirm = o => prompt({type: 'confirm', message: 'Continue?', ...o}); +export const prompt = o => enquirer.prompt({ name: 'name', type: 'input', message: 'Enter value', ...o }).then(r => r.name).catch(_ => {}); +export const confirm = o => prompt({ type: 'confirm', message: 'Continue?', ...o }); // notifications via apprise CLI import { exec } from 'child_process'; import { cfg } from './config.js'; -export const notify = (html) => new Promise((resolve, reject) => { +export const notify = html => new Promise((resolve, reject) => { if (!cfg.notify) return resolve(); const title = cfg.notify_title ? `-t ${cfg.notify_title}` : ''; exec(`apprise ${cfg.notify} -i html '${title}' -b '${html}'`, (error, stdout, stderr) => { @@ -102,6 +105,6 @@ export const notify = (html) => new Promise((resolve, reject) => { }); }); -export const escapeHtml = (unsafe) => unsafe.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll("'", '''); +export const escapeHtml = unsafe => unsafe.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>').replaceAll('"', '"').replaceAll('\'', '''); export const html_game_list = games => games.map(g => `- ${escapeHtml(g.title)} (${g.status})`).join('
'); diff --git a/version.js b/version.js index d992afb..7f875cf 100644 --- a/version.js +++ b/version.js @@ -1,9 +1,9 @@ // check if running the latest version -import {log} from 'console'; +import { log } from 'console'; import { exec } from 'child_process'; -const execp = (cmd) => new Promise((resolve, reject) => { +const execp = cmd => new Promise((resolve, reject) => { exec(cmd, (error, stdout, stderr) => { if (stderr) console.error(`stderr: ${stderr}`); // if (stdout) console.log(`stdout: ${stdout}`); @@ -35,8 +35,8 @@ if (process.env.NOVNC_PORT) { } const gh = await (await fetch('https://api.github.com/repos/vogler/free-games-claimer/commits/main', { - // headers: { accept: 'application/vnd.github.VERSION.sha' } - })).json(); + // headers: { accept: 'application/vnd.github.VERSION.sha' } +})).json(); // log(gh); log('Local commit:', sha, new Date(date)); diff --git a/xbox.js b/xbox.js index a2150f1..54cccf2 100644 --- a/xbox.js +++ b/xbox.js @@ -1,37 +1,37 @@ -import { firefox } from "playwright-firefox"; // stealth plugin needs no outdated playwright-extra -import { authenticator } from "otplib"; +import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra +import { authenticator } from 'otplib'; import { - datetime, - handleSIGINT, - html_game_list, - jsonDb, - notify, - prompt, -} from "./util.js"; -import { cfg } from "./config.js"; + datetime, + handleSIGINT, + html_game_list, + jsonDb, + notify, + prompt, +} from './util.js'; +import { cfg } from './config.js'; // ### SETUP -const URL_CLAIM = "https://www.xbox.com/en-US/live/gold"; // #gameswithgold"; +const URL_CLAIM = 'https://www.xbox.com/en-US/live/gold'; // #gameswithgold"; -console.log(datetime(), "started checking xbox"); +console.log(datetime(), 'started checking xbox'); -const db = await jsonDb("xbox.json"); +const db = await jsonDb('xbox.json'); db.data ||= {}; handleSIGINT(); // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(cfg.dir.browser, { - headless: cfg.headless, - viewport: { width: cfg.width, height: cfg.height }, - locale: "en-US", // ignore OS locale to be sure to have english text for locators -> done via /en in URL + headless: cfg.headless, + viewport: { width: cfg.width, height: cfg.height }, + locale: 'en-US', // ignore OS locale to be sure to have english text for locators -> done via /en in URL }); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length - ? context.pages()[0] - : await context.newPage(); // should always exist + ? context.pages()[0] + : await context.newPage(); // should always exist const notify_games = []; let user; @@ -39,216 +39,212 @@ let user; main(); async function main() { - try { - await performLogin(); - await getAndSaveUser(); - await redeemFreeGames(); - } catch (error) { - console.error(error); - process.exitCode ||= 1; - if (error.message && process.exitCode != 130) - notify(`xbox failed: ${error.message.split("\n")[0]}`); - } finally { - await db.write(); // write out json db - if (notify_games.filter((g) => g.status != "existed").length) { - // don't notify if all were already claimed - notify(`xbox (${user}):
${html_game_list(notify_games)}`); - } - await context.close(); + try { + await performLogin(); + await getAndSaveUser(); + await redeemFreeGames(); + } catch (error) { + console.error(error); + process.exitCode ||= 1; + if (error.message && process.exitCode != 130) notify(`xbox failed: ${error.message.split('\n')[0]}`); + } finally { + await db.write(); // write out json db + if (notify_games.filter(g => g.status != 'existed').length) { + // don't notify if all were already claimed + notify(`xbox (${user}):
${html_game_list(notify_games)}`); } + await context.close(); + } } async function performLogin() { - await page.goto(URL_CLAIM, { waitUntil: "domcontentloaded" }); // default 'load' takes forever + await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever - const signInLocator = page - .getByRole("link", { - name: "Sign in to your account", - }) - .first(); - const usernameLocator = page - .getByRole("button", { - name: "Account manager for", - }) - .first(); + const signInLocator = page + .getByRole('link', { + name: 'Sign in to your account', + }) + .first(); + const usernameLocator = page + .getByRole('button', { + name: 'Account manager for', + }) + .first(); - await Promise.any([signInLocator.waitFor(), usernameLocator.waitFor()]); + await Promise.any([signInLocator.waitFor(), usernameLocator.waitFor()]); - if (await usernameLocator.isVisible()) { - return; // logged in using saved cookie - } else if (await signInLocator.isVisible()) { - console.error("Not signed in anymore."); - await signInLocator.click(); - await signInToXbox(); - } else { - console.error("lost! where am i?"); - } + if (await usernameLocator.isVisible()) { + return; // logged in using saved cookie + } else if (await signInLocator.isVisible()) { + console.error('Not signed in anymore.'); + await signInLocator.click(); + await signInToXbox(); + } else { + console.error('lost! where am i?'); + } } async function signInToXbox() { - page.waitForLoadState("domcontentloaded"); - if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in - console.info(`Login timeout is ${cfg.login_timeout / 1000} seconds!`); + page.waitForLoadState('domcontentloaded'); + if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in + console.info(`Login timeout is ${cfg.login_timeout / 1000} seconds!`); - // ### FETCH EMAIL/PASS - if (cfg.xbox_email && cfg.xbox_password) - console.info("Using email and password from environment."); - else - console.info( - "Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode)." - ); - const email = cfg.xbox_email || (await prompt({ message: "Enter email" })); - const password = + // ### FETCH EMAIL/PASS + if (cfg.xbox_email && cfg.xbox_password) console.info('Using email and password from environment.'); + else console.info( + 'Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).', + ); + const email = cfg.xbox_email || await prompt({ message: 'Enter email' }); + const password = email && (cfg.xbox_password || - (await prompt({ - type: "password", - message: "Enter password", - }))); + await prompt({ + type: 'password', + message: 'Enter password', + })); // ### FILL IN EMAIL/PASS - if (email && password) { - const usernameLocator = page - .getByPlaceholder("Email, phone, or Skype") - .first(); - const passwordLocator = page.getByPlaceholder("Password").first(); + if (email && password) { + const usernameLocator = page + .getByPlaceholder('Email, phone, or Skype') + .first(); + const passwordLocator = page.getByPlaceholder('Password').first(); - await Promise.any([ - usernameLocator.waitFor(), - passwordLocator.waitFor(), - ]); + await Promise.any([ + usernameLocator.waitFor(), + passwordLocator.waitFor(), + ]); - // username may already be saved from before, if so, skip to filling in password - if (await page.getByPlaceholder("Email, phone, or Skype").isVisible()) { - await usernameLocator.fill(email); - await page.getByRole("button", { name: "Next" }).click(); - } - - await passwordLocator.fill(password); - await page.getByRole("button", { name: "Sign in" }).click(); - - // handle MFA, but don't await it - page.locator('input[name="otc"]') - .waitFor() - .then(async () => { - console.log("Two-Step Verification - Enter security code"); - console.log( - await page - .locator('div[data-bind="text: description"]') - .innerText() - ); - const otp = - (cfg.xbox_otpkey && - authenticator.generate(cfg.xbox_otpkey)) || - (await prompt({ - type: "text", - message: "Enter two-factor sign in code", - validate: (n) => - n.toString().length == 6 || - "The code must be 6 digits!", - })); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them - await page.type('input[name="otc"]', otp.toString()); - await page - .getByLabel("Don't ask me again on this device") - .check(); // Trust this Browser - await page.getByRole("button", { name: "Verify" }).click(); - }) - .catch((_) => {}); - - // Trust this browser, but don't await it - page.getByLabel("Don't show this again") - .waitFor() - .then(async () => { - await page.getByLabel("Don't show this again").check(); - await page.getByRole("button", { name: "Yes" }).click(); - }) - .catch((_) => {}); - } else { - console.log("Waiting for you to login in the browser."); - await notify( - "xbox: no longer signed in and not enough options set for automatic login." - ); - if (cfg.headless) { - console.log( - "Run `SHOW=1 node xbox` to login in the opened browser." - ); - await context.close(); - process.exit(1); - } + // username may already be saved from before, if so, skip to filling in password + if (await page.getByPlaceholder('Email, phone, or Skype').isVisible()) { + await usernameLocator.fill(email); + await page.getByRole('button', { name: 'Next' }).click(); } - // ### VERIFY SIGNED IN - await page.waitForURL(`${URL_CLAIM}**`); + await passwordLocator.fill(password); + await page.getByRole('button', { name: 'Sign in' }).click(); - if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); + // handle MFA, but don't await it + page.locator('input[name="otc"]') + .waitFor() + .then(async () => { + console.log('Two-Step Verification - Enter security code'); + console.log( + await page + .locator('div[data-bind="text: description"]') + .innerText(), + ); + const otp = + cfg.xbox_otpkey && + authenticator.generate(cfg.xbox_otpkey) || + await prompt({ + type: 'text', + message: 'Enter two-factor sign in code', + validate: n => n.toString().length == 6 || + 'The code must be 6 digits!', + }); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + await page.type('input[name="otc"]', otp.toString()); + await page + .getByLabel('Don\'t ask me again on this device') + .check(); // Trust this Browser + await page.getByRole('button', { name: 'Verify' }).click(); + }) + .catch(_ => {}); + + // Trust this browser, but don't await it + page.getByLabel('Don\'t show this again') + .waitFor() + .then(async () => { + await page.getByLabel('Don\'t show this again').check(); + await page.getByRole('button', { name: 'Yes' }).click(); + }) + .catch(_ => {}); + } else { + console.log('Waiting for you to login in the browser.'); + await notify( + 'xbox: no longer signed in and not enough options set for automatic login.', + ); + if (cfg.headless) { + console.log( + 'Run `SHOW=1 node xbox` to login in the opened browser.', + ); + await context.close(); + process.exit(1); + } + } + + // ### VERIFY SIGNED IN + await page.waitForURL(`${URL_CLAIM}**`); + + if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } async function getAndSaveUser() { - user = await page.locator("#mectrl_currentAccount_primary").innerHTML(); - console.log(`Signed in as '${user}'`); - db.data[user] ||= {}; + user = await page.locator('#mectrl_currentAccount_primary').innerHTML(); + console.log(`Signed in as '${user}'`); + db.data[user] ||= {}; } async function redeemFreeGames() { - const monthlyGamesLocator = await page.locator(".f-size-large").all(); + const monthlyGamesLocator = await page.locator('.f-size-large').all(); - const monthlyGamesPageLinks = await Promise.all( - monthlyGamesLocator.map( - async (el) => await el.locator("a").getAttribute("href") - ) - ); - console.log("Free games:", monthlyGamesPageLinks); + const monthlyGamesPageLinks = await Promise.all( + monthlyGamesLocator.map( + async el => await el.locator('a').getAttribute('href'), + ), + ); + console.log('Free games:', monthlyGamesPageLinks); - for (const url of monthlyGamesPageLinks) { - await page.goto(url); + for (const url of monthlyGamesPageLinks) { + await page.goto(url); - const title = await page.locator("h1").first().innerText(); - const game_id = page.url().split("/").pop(); - db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! - console.log("Current free game:", title); - const notify_game = { title, url, status: "failed" }; - notify_games.push(notify_game); // status is updated below + const title = await page.locator('h1').first().innerText(); + const game_id = page.url().split('/').pop(); + db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! + console.log('Current free game:', title); + const notify_game = { title, url, status: 'failed' }; + notify_games.push(notify_game); // status is updated below - // SELECTORS - const getBtnLocator = page.getByText("GET", { exact: true }).first(); - const installToLocator = page - .getByText("INSTALL TO", { exact: true }) - .first(); + // SELECTORS + const getBtnLocator = page.getByText('GET', { exact: true }).first(); + const installToLocator = page + .getByText('INSTALL TO', { exact: true }) + .first(); - await Promise.any([ - getBtnLocator.waitFor(), - installToLocator.waitFor(), - ]); + await Promise.any([ + getBtnLocator.waitFor(), + installToLocator.waitFor(), + ]); - if (await installToLocator.isVisible()) { - console.log(" Already in library! Nothing to claim."); - notify_game.status = "existed"; - db.data[user][game_id].status ||= "existed"; // does not overwrite claimed or failed - } else if (await getBtnLocator.isVisible()) { - console.log(" Not in library yet! Click GET."); - await getBtnLocator.click(); + if (await installToLocator.isVisible()) { + console.log(' Already in library! Nothing to claim.'); + notify_game.status = 'existed'; + db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed + } else if (await getBtnLocator.isVisible()) { + console.log(' Not in library yet! Click GET.'); + await getBtnLocator.click(); - // wait for popup - await page - .locator('iframe[name="purchase-sdk-hosted-iframe"]') - .waitFor(); - const popupLocator = page.frameLocator( - "[name=purchase-sdk-hosted-iframe]" - ); + // wait for popup + await page + .locator('iframe[name="purchase-sdk-hosted-iframe"]') + .waitFor(); + const popupLocator = page.frameLocator( + '[name=purchase-sdk-hosted-iframe]', + ); - const finalGetBtnLocator = popupLocator.getByText("GET"); - await finalGetBtnLocator.waitFor(); - await finalGetBtnLocator.click(); + const finalGetBtnLocator = popupLocator.getByText('GET'); + await finalGetBtnLocator.waitFor(); + await finalGetBtnLocator.click(); - await page.getByText("Thank you for your purchase.").waitFor(); - notify_game.status = "claimed"; - db.data[user][game_id].status = "claimed"; - db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time - console.log(" Claimed successfully!"); - } - - // notify_game.status = db.data[user][game_id].status; // claimed or failed - - // const p = path.resolve(cfg.dir.screenshots, playstation-plus', `${game_id}.png`); - // if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... + await page.getByText('Thank you for your purchase.').waitFor(); + notify_game.status = 'claimed'; + db.data[user][game_id].status = 'claimed'; + db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time + console.log(' Claimed successfully!'); } + + // notify_game.status = db.data[user][game_id].status; // claimed or failed + + // const p = path.resolve(cfg.dir.screenshots, playstation-plus', `${game_id}.png`); + // if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... + } } From d4685ff370e12dea6d7ecfaa6d96c133a87d9fcc Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 8 Nov 2023 01:32:20 +0100 Subject: [PATCH 383/520] gha: sonar: install dev deps for eslint plugins --- .github/workflows/sonar.yml | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index bb2f89f..3300fac 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -10,18 +10,17 @@ jobs: sonarcloud: runs-on: ubuntu-latest steps: - - - uses: actions/checkout@v4 + - uses: actions/checkout@v4 + - uses: actions/setup-node@v3 with: - # Disabling shallow clone is recommended for improving relevancy of reporting - fetch-depth: 0 + cache: 'npm' - - name: Install modules - run: npm install -g eslint + name: Install dev dependencies which includde ESLint + plugins + run: npm install --only=dev - name: Run ESLint continue-on-error: true - run: eslint . --ext .js,.ts -f json -o eslint_report.json + run: npx eslint . -f json -o eslint_report.json - name: Fix ESLint paths run: sed -i 's+/home/runner/work/free-games-claimer/free-games-claimer+/github/workspace+g' eslint_report.json From c5f75e06c8e583013556240cd9ddda67e2085651 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 8 Nov 2023 01:36:35 +0100 Subject: [PATCH 384/520] gha: sonar: disable shallow clone (irrelevant) to get rid of warning --- .github/workflows/sonar.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 3300fac..b532c17 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -10,8 +10,13 @@ jobs: sonarcloud: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v3 + - + uses: actions/checkout@v4 + with: + # Disabling shallow clone is recommended for improving relevancy of reporting. Otherwise sonarcloud will show a warning. + fetch-depth: 0 + - + uses: actions/setup-node@v3 with: cache: 'npm' - From 110b2ea4c643e71cd6986526f2a8a17931b136f9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 8 Nov 2023 02:28:34 +0100 Subject: [PATCH 385/520] eslint: migrate to new flat config (.eslintrc.cjs -> eslint.config.js) https://eslint.org/docs/latest/use/configure/configuration-files-new https://eslint.org/docs/latest/use/configure/migration-guide --- .eslintignore | 1 - .eslintrc.cjs | 78 ------------------------------------------------ eslint.config.js | 75 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 79 deletions(-) delete mode 120000 .eslintignore delete mode 100644 .eslintrc.cjs create mode 100644 eslint.config.js diff --git a/.eslintignore b/.eslintignore deleted file mode 120000 index 3e4e48b..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -.gitignore \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index 04989aa..0000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,78 +0,0 @@ -module.exports = { - env: { - es2021: true, - node: true, - }, - extends: 'eslint:recommended', - overrides: [ - { - env: { - node: true, - }, - files: [ - '.eslintrc.{js,cjs}', - ], - parserOptions: { - sourceType: 'script', - }, - }, - ], - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - }, - plugins: [ - '@stylistic/js', - ], - // https://eslint.org/docs/latest/rules/ - // https://eslint.style/packages/js - rules: { - 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], - '@stylistic/js/array-bracket-newline': ['error', 'consistent'], - '@stylistic/js/array-bracket-spacing': 'error', - '@stylistic/js/array-element-newline': ['error', 'consistent'], - '@stylistic/js/arrow-parens': ['error', 'as-needed'], - '@stylistic/js/arrow-spacing': 'error', - '@stylistic/js/block-spacing': 'error', - '@stylistic/js/brace-style': 'error', - '@stylistic/js/comma-dangle': ['error', 'always-multiline'], - '@stylistic/js/comma-spacing': 'error', - '@stylistic/js/comma-style': 'error', - '@stylistic/js/eol-last': 'error', - '@stylistic/js/func-call-spacing': 'error', - '@stylistic/js/function-paren-newline': ['error', 'consistent'], - '@stylistic/js/implicit-arrow-linebreak': 'error', - '@stylistic/js/indent': ['error', 2], - '@stylistic/js/key-spacing': 'error', - '@stylistic/js/keyword-spacing': 'error', - '@stylistic/js/linebreak-style': 'error', - '@stylistic/js/no-extra-parens': 'error', - '@stylistic/js/no-extra-semi': 'error', - '@stylistic/js/no-mixed-spaces-and-tabs': 'error', - '@stylistic/js/no-multi-spaces': 'error', - '@stylistic/js/no-multiple-empty-lines': 'error', - '@stylistic/js/no-tabs': 'error', - '@stylistic/js/no-trailing-spaces': 'error', - '@stylistic/js/no-whitespace-before-property': 'error', - '@stylistic/js/nonblock-statement-body-position': 'error', - '@stylistic/js/object-curly-newline': 'error', - '@stylistic/js/object-curly-spacing': ['error', 'always'], - '@stylistic/js/object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }], - '@stylistic/js/quote-props': ['error', 'as-needed'], - '@stylistic/js/quotes': ['error', 'single'], - '@stylistic/js/rest-spread-spacing': 'error', - '@stylistic/js/semi': 'error', - '@stylistic/js/semi-spacing': 'error', - '@stylistic/js/semi-style': 'error', - '@stylistic/js/space-before-blocks': 'error', - '@stylistic/js/space-before-function-paren': ['error', { anonymous: 'never', named: 'never', asyncArrow: 'always' }], - '@stylistic/js/space-in-parens': 'error', - '@stylistic/js/space-infix-ops': 'error', - '@stylistic/js/space-unary-ops': 'error', - '@stylistic/js/spaced-comment': 'error', - '@stylistic/js/switch-colon-spacing': 'error', - '@stylistic/js/template-curly-spacing': 'error', - '@stylistic/js/template-tag-spacing': 'error', - '@stylistic/js/wrap-regex': 'error', - }, -}; diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..bc6447f --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,75 @@ +// https://eslint.org/docs/latest/use/configure/configuration-files-new +// https://eslint.org/docs/latest/use/configure/migration-guide +import js from '@eslint/js'; +import globals from 'globals'; +import stylistic from '@stylistic/eslint-plugin-js'; + +export default [ + // https://eslint.org/docs/latest/use/configure/configuration-files-new#globally-ignoring-files-with-ignores + // object with just `ignores` applies to all configuration objects + // had `ln -s .gitignore .eslintignore` before, but .eslintignore no longer supported + { + ignores: ['data/**'], + }, + js.configs.recommended, // TODO still needed? + { + // files: ['*.js'], + languageOptions: { + globals: globals.node, + }, + plugins: { + '@stylistic/js': stylistic, + }, + // https://eslint.org/docs/latest/rules/ + // https://eslint.style/packages/js + rules: { + 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + '@stylistic/js/array-bracket-newline': ['error', 'consistent'], + '@stylistic/js/array-bracket-spacing': 'error', + '@stylistic/js/array-element-newline': ['error', 'consistent'], + '@stylistic/js/arrow-parens': ['error', 'as-needed'], + '@stylistic/js/arrow-spacing': 'error', + '@stylistic/js/block-spacing': 'error', + '@stylistic/js/brace-style': 'error', + '@stylistic/js/comma-dangle': ['error', 'always-multiline'], + '@stylistic/js/comma-spacing': 'error', + '@stylistic/js/comma-style': 'error', + '@stylistic/js/eol-last': 'error', + '@stylistic/js/func-call-spacing': 'error', + '@stylistic/js/function-paren-newline': ['error', 'consistent'], + '@stylistic/js/implicit-arrow-linebreak': 'error', + '@stylistic/js/indent': ['error', 2], + '@stylistic/js/key-spacing': 'error', + '@stylistic/js/keyword-spacing': 'error', + '@stylistic/js/linebreak-style': 'error', + '@stylistic/js/no-extra-parens': 'error', + '@stylistic/js/no-extra-semi': 'error', + '@stylistic/js/no-mixed-spaces-and-tabs': 'error', + '@stylistic/js/no-multi-spaces': 'error', + '@stylistic/js/no-multiple-empty-lines': 'error', + '@stylistic/js/no-tabs': 'error', + '@stylistic/js/no-trailing-spaces': 'error', + '@stylistic/js/no-whitespace-before-property': 'error', + '@stylistic/js/nonblock-statement-body-position': 'error', + '@stylistic/js/object-curly-newline': 'error', + '@stylistic/js/object-curly-spacing': ['error', 'always'], + '@stylistic/js/object-property-newline': ['error', { allowAllPropertiesOnSameLine: true }], + '@stylistic/js/quote-props': ['error', 'as-needed'], + '@stylistic/js/quotes': ['error', 'single'], + '@stylistic/js/rest-spread-spacing': 'error', + '@stylistic/js/semi': 'error', + '@stylistic/js/semi-spacing': 'error', + '@stylistic/js/semi-style': 'error', + '@stylistic/js/space-before-blocks': 'error', + '@stylistic/js/space-before-function-paren': ['error', { anonymous: 'never', named: 'never', asyncArrow: 'always' }], + '@stylistic/js/space-in-parens': 'error', + '@stylistic/js/space-infix-ops': 'error', + '@stylistic/js/space-unary-ops': 'error', + '@stylistic/js/spaced-comment': 'error', + '@stylistic/js/switch-colon-spacing': 'error', + '@stylistic/js/template-curly-spacing': 'error', + '@stylistic/js/template-tag-spacing': 'error', + '@stylistic/js/wrap-regex': 'error', + }, + }, +]; From 739fb0dbfe19d065a68d39fc1b971e58b83d4b85 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 8 Nov 2023 02:30:21 +0100 Subject: [PATCH 386/520] vscode workspace settings for formatOnSave with `eslint --fix` using flat config https://eslint.style/guide/faq#vs-code --- .vscode/settings.json | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c2cd6b0 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + // https://eslint.style/guide/faq#vs-code + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "modifications", + "editor.codeActionsOnSave": { + "source.fixAll.eslint": true + }, + "eslint.experimental.useFlatConfig": true, +} From a7cc68b6db41a9542684099583c9127a9b703fa9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 9 Nov 2023 01:41:01 +0100 Subject: [PATCH 387/520] ue: login: fix #248, similar to #236 Commits for #236: https://github.com/vogler/free-games-claimer/commit/d73a523fe7f76608af5db4e72b567f058814fea1 https://github.com/vogler/free-games-claimer/commit/a374d483451f1fa19c0f9f461e32cb37a4b46fd7 --- unrealengine.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/unrealengine.js b/unrealengine.js index 5bbf988..cd2eb33 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -48,7 +48,7 @@ try { await page.waitForResponse(r => r.request().method() == 'POST' && r.url().startsWith('https://graphql.unrealengine.com/ue/graphql')); - while (await page.locator('.display-name').count() == 0) { + while (await page.locator('unrealengine-navigation').getAttribute('isloggedin') != 'true') { console.error('Not signed in anymore. Please login in the browser or here in the terminal.'); if (cfg.novnc_port) console.info(`Open http://localhost:${cfg.novnc_port} to login inside the docker container.`); if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in @@ -59,8 +59,9 @@ try { const email = cfg.eg_email || await prompt({ message: 'Enter email' }); const password = email && (cfg.eg_password || await prompt({ type: 'password', message: 'Enter password' })); if (email && password) { - await page.click('text=Sign in with Epic Games'); + // await page.click('text=Sign in with Epic Games'); await page.fill('#email', email); + await page.click('button[type="submit"]'); await page.fill('#password', password); await page.click('button[type="submit"]'); page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(() => { @@ -88,7 +89,7 @@ try { if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } await page.waitForTimeout(1000); - user = await page.locator('.display-name').first().innerHTML(); + user = await page.locator('unrealengine-navigation').getAttribute('displayname'); // 'null' if !isloggedin console.log(`Signed in as ${user}`); db.data[user] ||= {}; From 3ddf1720bb10d1995bcf36a924805d5349ff8b63 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 9 Nov 2023 02:34:17 +0100 Subject: [PATCH 388/520] notify: use execFile with arg array instead of exec to avoid shell-escape, fixes #239 Also proper fix for https://github.com/vogler/free-games-claimer/pull/167 https://www.npmjs.com/package/shell-escape https://stackoverflow.com/questions/1779858/how-do-i-escape-a-string-for-a-shell-command-in-node https://nodejs.org/api/child_process.html#child_processexecfilefile-args-options-callback --- notify-test.js | 5 ++++- util.js | 14 ++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/notify-test.js b/notify-test.js index 9d593c0..b9c293f 100644 --- a/notify-test.js +++ b/notify-test.js @@ -1,15 +1,18 @@ /* eslint-disable no-constant-condition */ import { delay, html_game_list, notify } from './util.js'; +import { cfg } from './config.js'; const URL_CLAIM = 'https://gaming.amazon.com/home'; // dummy URL +console.debug('NOTIFY:', cfg.notify); + if (true) { const notify_games = [ // { title: 'Kerbal Space Program', status: 'claimed', url: URL_CLAIM }, // { title: "Shadow Tactics - Aiko's Choice", status: 'claimed', url: URL_CLAIM }, { title: 'Epistory - Typing Chronicles', status: 'claimed', url: URL_CLAIM }, ]; - notify(`epic-games:
${html_game_list(notify_games)}`); + await notify(`epic-games:
${html_game_list(notify_games)}`); } if (false) { diff --git a/util.js b/util.js index 16ae806..acd6c0b 100644 --- a/util.js +++ b/util.js @@ -85,13 +85,19 @@ export const prompt = o => enquirer.prompt({ name: 'name', type: 'input', messag export const confirm = o => prompt({ type: 'confirm', message: 'Continue?', ...o }); // notifications via apprise CLI -import { exec } from 'child_process'; +import { execFile } from 'child_process'; import { cfg } from './config.js'; export const notify = html => new Promise((resolve, reject) => { - if (!cfg.notify) return resolve(); - const title = cfg.notify_title ? `-t ${cfg.notify_title}` : ''; - exec(`apprise ${cfg.notify} -i html '${title}' -b '${html}'`, (error, stdout, stderr) => { + if (!cfg.notify) { + if (cfg.debug) console.debug('notify: NOTIFY is not set!'); + return resolve(); + } + // const cmd = `apprise '${cfg.notify}' ${title} -i html -b '${html}'`; // this had problems if e.g. ' was used in arg; could have `npm i shell-escape`, but instead using safer execFile which takes args as array instead of exec which spawned a shell to execute the command + const args = [cfg.notify, '-i', 'html', '-b', html]; + if (cfg.notify_title) args.push(...['-t', cfg.notify_title]); + if (cfg.debug) console.debug(`apprise ${args.map(a => `'${a}'`).join(' ')}`); // this also doesn't escape, but it's just for info + execFile('apprise', args, (error, stdout, stderr) => { if (error) { console.log(`error: ${error.message}`); if (error.message.includes('command not found')) { From 4231b7dd28892878ce2f2181984cff6e62dce127 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 9 Nov 2023 02:43:43 +0100 Subject: [PATCH 389/520] gog: don't wait for screenshot to not be loading, closes #240 --- gog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gog.js b/gog.js index c686851..74623e6 100644 --- a/gog.js +++ b/gog.js @@ -99,7 +99,7 @@ try { console.log(`Current free game: ${title} - ${url}`); db.data[user][title] ||= { title, time: datetime(), url }; if (cfg.dryrun) process.exit(1); - await page.locator('#giveaway:not(.is-loading)').waitFor(); // otherwise screenshot is sometimes with loading indicator instead of game title + // await page.locator('#giveaway:not(.is-loading)').waitFor(); // otherwise screenshot is sometimes with loading indicator instead of game title; #TODO fix, skipped due to timeout, see #240 await banner.screenshot({ path: screenshot(`${filenamify(title)}.png`) }); // overwrites every time - only keep first? // await banner.getByRole('button', { name: 'Add to library' }).click(); From 324f6bffe723c951abe7fa8ab830edce79929de2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 13 Nov 2023 06:01:59 +0000 Subject: [PATCH 390/520] build(deps): bump actions/setup-node from 3 to 4 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/sonar.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index b532c17..eb4ad62 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -16,7 +16,7 @@ jobs: # Disabling shallow clone is recommended for improving relevancy of reporting. Otherwise sonarcloud will show a warning. fetch-depth: 0 - - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: cache: 'npm' - From 5919d37efaabad98c303e087c4874cffb58b3cb9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Mon, 20 Nov 2023 11:41:15 +0100 Subject: [PATCH 391/520] ncu -u: playwright-firefox 1.39.0 -> 1.40.0, eslint --- package-lock.json | 138 +++++++++++++++++++++++----------------------- package.json | 6 +- 2 files changed, 73 insertions(+), 71 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4736f13..2b19e37 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,12 @@ "enquirer": "^2.4.1", "lowdb": "^6.1.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.39.0", + "playwright-firefox": "^1.40.0", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { - "@stylistic/eslint-plugin-js": "^1.0.1", - "eslint": "^8.53.0" + "@stylistic/eslint-plugin-js": "^1.4.1", + "eslint": "^8.54.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -47,9 +47,9 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", - "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" @@ -79,9 +79,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", - "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -198,18 +198,22 @@ } }, "node_modules/@stylistic/eslint-plugin-js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.0.1.tgz", - "integrity": "sha512-SfJlEnmBowaWx9GyN/7vQ/7jQP2wVQe5CcaoVL6V5nmCWl9Q+VSeJPSBOjB7XOYSYL1HoEQsvA+8Hy7Zt2XrnA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.4.1.tgz", + "integrity": "sha512-WXHPEVw5PB7OML7cLwHJDEcCyLiP7vzKeBbSwmpHLK0oh0JYkoJfTg2hEdFuQT5rQxFy3KzCy9R1mZ0wgLjKrA==", "dev": true, "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", "acorn": "^8.11.2", "escape-string-regexp": "^4.0.0", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", - "esutils": "^2.0.3", "graphemer": "^1.4.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "peerDependencies": { + "eslint": ">=8.40.0" } }, "node_modules/@types/debug": { @@ -498,15 +502,15 @@ } }, "node_modules/eslint": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", - "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.53.0", + "@eslint/js": "8.54.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -695,17 +699,17 @@ } }, "node_modules/flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "dependencies": { - "flatted": "^3.2.7", + "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { - "node": ">=12.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { @@ -818,9 +822,9 @@ } }, "node_modules/ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "dev": true, "engines": { "node": ">= 4" @@ -974,9 +978,9 @@ } }, "node_modules/keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "dependencies": { "json-buffer": "3.0.1" @@ -1207,9 +1211,9 @@ } }, "node_modules/playwright-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", - "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0.tgz", + "integrity": "sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q==", "bin": { "playwright-core": "cli.js" }, @@ -1218,12 +1222,12 @@ } }, "node_modules/playwright-firefox": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.39.0.tgz", - "integrity": "sha512-DFJRVL8mfOPyfiK8on34kYdvFeV0a0aNGRNUTPuHD2sv2+pMITPSosxRCgPvnFO3oWzEwVBVv/+c5E9fICY7gg==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.40.0.tgz", + "integrity": "sha512-01KUdoo9Sk9lMGUhlc9tuvWun/mspBINuAvGFm0RFS7ZaZ27uWD9WJ0MnJ6cWcIkL0nLgjlrWmvHgQs1fIq7LQ==", "hasInstallScript": true, "dependencies": { - "playwright-core": "1.39.0" + "playwright-core": "1.40.0" }, "bin": { "playwright": "cli.js" @@ -1630,9 +1634,9 @@ } }, "@eslint-community/regexpp": { - "version": "4.8.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", - "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", + "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true }, "@eslint/eslintrc": { @@ -1653,9 +1657,9 @@ } }, "@eslint/js": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz", - "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", + "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", "dev": true }, "@humanwhocodes/config-array": { @@ -1750,17 +1754,15 @@ } }, "@stylistic/eslint-plugin-js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.0.1.tgz", - "integrity": "sha512-SfJlEnmBowaWx9GyN/7vQ/7jQP2wVQe5CcaoVL6V5nmCWl9Q+VSeJPSBOjB7XOYSYL1HoEQsvA+8Hy7Zt2XrnA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.4.1.tgz", + "integrity": "sha512-WXHPEVw5PB7OML7cLwHJDEcCyLiP7vzKeBbSwmpHLK0oh0JYkoJfTg2hEdFuQT5rQxFy3KzCy9R1mZ0wgLjKrA==", "dev": true, "requires": { - "@eslint-community/eslint-utils": "^4.4.0", "acorn": "^8.11.2", "escape-string-regexp": "^4.0.0", "eslint-visitor-keys": "^3.4.3", "espree": "^9.6.1", - "esutils": "^2.0.3", "graphemer": "^1.4.0" } }, @@ -1967,15 +1969,15 @@ "dev": true }, "eslint": { - "version": "8.53.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz", - "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==", + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", + "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.53.0", + "@eslint/js": "8.54.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2116,12 +2118,12 @@ } }, "flat-cache": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", - "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dev": true, "requires": { - "flatted": "^3.2.7", + "flatted": "^3.2.9", "keyv": "^4.5.3", "rimraf": "^3.0.2" } @@ -2209,9 +2211,9 @@ "dev": true }, "ignore": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", - "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", + "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", "dev": true }, "import-fresh": { @@ -2330,9 +2332,9 @@ } }, "keyv": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", - "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "requires": { "json-buffer": "3.0.1" @@ -2505,16 +2507,16 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright-core": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.39.0.tgz", - "integrity": "sha512-+k4pdZgs1qiM+OUkSjx96YiKsXsmb59evFoqv8SKO067qBA+Z2s/dCzJij/ZhdQcs2zlTAgRKfeiiLm8PQ2qvw==" + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0.tgz", + "integrity": "sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q==" }, "playwright-firefox": { - "version": "1.39.0", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.39.0.tgz", - "integrity": "sha512-DFJRVL8mfOPyfiK8on34kYdvFeV0a0aNGRNUTPuHD2sv2+pMITPSosxRCgPvnFO3oWzEwVBVv/+c5E9fICY7gg==", + "version": "1.40.0", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.40.0.tgz", + "integrity": "sha512-01KUdoo9Sk9lMGUhlc9tuvWun/mspBINuAvGFm0RFS7ZaZ27uWD9WJ0MnJ6cWcIkL0nLgjlrWmvHgQs1fIq7LQ==", "requires": { - "playwright-core": "1.39.0" + "playwright-core": "1.40.0" } }, "prelude-ls": { diff --git a/package.json b/package.json index fe2b52b..9395767 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "enquirer": "^2.4.1", "lowdb": "^6.1.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.39.0", + "playwright-firefox": "^1.40.0", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "repository": { @@ -25,7 +25,7 @@ "author": "Ralf Vogler", "license": "AGPL-3.0-only", "devDependencies": { - "@stylistic/eslint-plugin-js": "^1.0.1", - "eslint": "^8.53.0" + "@stylistic/eslint-plugin-js": "^1.4.1", + "eslint": "^8.54.0" } } From 544eff8a23ddc016637508385b8df4144ff322a2 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 19 Dec 2023 11:25:21 +0100 Subject: [PATCH 392/520] ncu -u: playwright-firefox 1.40.0 -> 1.40.1, eslint --- package-lock.json | 108 +++++++++++++++++++++++----------------------- package.json | 6 +-- 2 files changed, 56 insertions(+), 58 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b19e37..c17609e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,12 @@ "enquirer": "^2.4.1", "lowdb": "^6.1.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.40.0", + "playwright-firefox": "^1.40.1", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { - "@stylistic/eslint-plugin-js": "^1.4.1", - "eslint": "^8.54.0" + "@stylistic/eslint-plugin-js": "^1.5.1", + "eslint": "^8.56.0" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -56,9 +56,9 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "dependencies": { "ajv": "^6.12.4", @@ -79,9 +79,9 @@ } }, "node_modules/@eslint/js": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", - "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -198,16 +198,15 @@ } }, "node_modules/@stylistic/eslint-plugin-js": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.4.1.tgz", - "integrity": "sha512-WXHPEVw5PB7OML7cLwHJDEcCyLiP7vzKeBbSwmpHLK0oh0JYkoJfTg2hEdFuQT5rQxFy3KzCy9R1mZ0wgLjKrA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.5.1.tgz", + "integrity": "sha512-iZF0rF+uOhAmOJYOJx1Yvmm3CZ1uz9n0SRd9dpBYHA3QAvfABUORh9LADWwZCigjHJkp2QbCZelGFJGwGz7Siw==", "dev": true, "dependencies": { "acorn": "^8.11.2", "escape-string-regexp": "^4.0.0", "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "graphemer": "^1.4.0" + "espree": "^9.6.1" }, "engines": { "node": "^16.0.0 || >=18.0.0" @@ -502,15 +501,15 @@ } }, "node_modules/eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -787,9 +786,9 @@ } }, "node_modules/globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "dependencies": { "type-fest": "^0.20.2" @@ -1211,9 +1210,9 @@ } }, "node_modules/playwright-core": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0.tgz", - "integrity": "sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", "bin": { "playwright-core": "cli.js" }, @@ -1222,12 +1221,12 @@ } }, "node_modules/playwright-firefox": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.40.0.tgz", - "integrity": "sha512-01KUdoo9Sk9lMGUhlc9tuvWun/mspBINuAvGFm0RFS7ZaZ27uWD9WJ0MnJ6cWcIkL0nLgjlrWmvHgQs1fIq7LQ==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.40.1.tgz", + "integrity": "sha512-+C5eOWZv/CvALM0yBZFSYThvUzGKusNw6soDMhTEJwDUB5i9q/yZVFkj6I8CFXM4U6Pf1q0PXHMca70HoIwnCQ==", "hasInstallScript": true, "dependencies": { - "playwright-core": "1.40.0" + "playwright-core": "1.40.1" }, "bin": { "playwright": "cli.js" @@ -1640,9 +1639,9 @@ "dev": true }, "@eslint/eslintrc": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.3.tgz", - "integrity": "sha512-yZzuIG+jnVu6hNSzFEN07e8BxF3uAzYtQb6uDkaYZLo6oYZDCq454c5kB8zxnzfCYyP4MIuyBn10L0DqwujTmA==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -1657,9 +1656,9 @@ } }, "@eslint/js": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz", - "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", + "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", "dev": true }, "@humanwhocodes/config-array": { @@ -1754,16 +1753,15 @@ } }, "@stylistic/eslint-plugin-js": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.4.1.tgz", - "integrity": "sha512-WXHPEVw5PB7OML7cLwHJDEcCyLiP7vzKeBbSwmpHLK0oh0JYkoJfTg2hEdFuQT5rQxFy3KzCy9R1mZ0wgLjKrA==", + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.5.1.tgz", + "integrity": "sha512-iZF0rF+uOhAmOJYOJx1Yvmm3CZ1uz9n0SRd9dpBYHA3QAvfABUORh9LADWwZCigjHJkp2QbCZelGFJGwGz7Siw==", "dev": true, "requires": { "acorn": "^8.11.2", "escape-string-regexp": "^4.0.0", "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "graphemer": "^1.4.0" + "espree": "^9.6.1" } }, "@types/debug": { @@ -1969,15 +1967,15 @@ "dev": true }, "eslint": { - "version": "8.54.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz", - "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==", + "version": "8.56.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", + "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.3", - "@eslint/js": "8.54.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.56.0", "@humanwhocodes/config-array": "^0.11.13", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", @@ -2185,9 +2183,9 @@ } }, "globals": { - "version": "13.23.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.23.0.tgz", - "integrity": "sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==", + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, "requires": { "type-fest": "^0.20.2" @@ -2507,16 +2505,16 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright-core": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.0.tgz", - "integrity": "sha512-fvKewVJpGeca8t0ipM56jkVSU6Eo0RmFvQ/MaCQNDYm+sdvKkMBBWTE1FdeMqIdumRaXXjZChWHvIzCGM/tA/Q==" + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", + "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==" }, "playwright-firefox": { - "version": "1.40.0", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.40.0.tgz", - "integrity": "sha512-01KUdoo9Sk9lMGUhlc9tuvWun/mspBINuAvGFm0RFS7ZaZ27uWD9WJ0MnJ6cWcIkL0nLgjlrWmvHgQs1fIq7LQ==", + "version": "1.40.1", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.40.1.tgz", + "integrity": "sha512-+C5eOWZv/CvALM0yBZFSYThvUzGKusNw6soDMhTEJwDUB5i9q/yZVFkj6I8CFXM4U6Pf1q0PXHMca70HoIwnCQ==", "requires": { - "playwright-core": "1.40.0" + "playwright-core": "1.40.1" } }, "prelude-ls": { diff --git a/package.json b/package.json index 9395767..c6890cf 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "enquirer": "^2.4.1", "lowdb": "^6.1.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.40.0", + "playwright-firefox": "^1.40.1", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "repository": { @@ -25,7 +25,7 @@ "author": "Ralf Vogler", "license": "AGPL-3.0-only", "devDependencies": { - "@stylistic/eslint-plugin-js": "^1.4.1", - "eslint": "^8.54.0" + "@stylistic/eslint-plugin-js": "^1.5.1", + "eslint": "^8.56.0" } } From 36fe60a2b109902bd29f5fa623bb6697b290cc99 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 19 Dec 2023 11:33:38 +0100 Subject: [PATCH 393/520] vscode update changed config --- .vscode/settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index c2cd6b0..6106b4f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -3,7 +3,8 @@ "editor.formatOnSave": true, "editor.formatOnSaveMode": "modifications", "editor.codeActionsOnSave": { - "source.fixAll.eslint": true + "source.fixAll.eslint": "explicit" }, "eslint.experimental.useFlatConfig": true, + "eslint.codeActionsOnSave.rules": null, } From 9c89bf06a4cec739ae36c9fe2f0faf1142afc7eb Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 19 Dec 2023 11:34:00 +0100 Subject: [PATCH 394/520] npm run lint = npx eslint . --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c6890cf..15b2165 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "main": "index.js", "scripts": { "docker:build": "docker build . -t ghcr.io/vogler/free-games-claimer", - "docker": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name fgc ghcr.io/vogler/free-games-claimer" + "docker": "cross-env-shell docker run --rm -it -p 5900:5900 -p 6080:6080 -v \\\"$INIT_CWD/data\\\":/fgc/data --name fgc ghcr.io/vogler/free-games-claimer", + "lint": "npx eslint ." }, "type": "module", "dependencies": { From 76597f4315db0ea9839319ff965bc4895cac9850 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 19 Dec 2023 11:52:35 +0100 Subject: [PATCH 395/520] eg: include link to game in captcha notification, closes #259 TODO use purchaseURL from https://github.com/vogler/free-games-claimer/pull/130 --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 06d66cc..90e0683 100644 --- a/epic-games.js +++ b/epic-games.js @@ -228,7 +228,7 @@ try { captcha.waitFor().then(async () => { // don't await, since element may not be shown // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.'); - await notify('epic-games: got captcha challenge right before claim. Use VNC to solve it manually.'); + await notify(`epic-games: got captcha challenge right before claim of ${title}. Use VNC to solve it manually.`); // TODO could even create purchase URL, see https://github.com/vogler/free-games-claimer/pull/130 // await page.waitForTimeout(2000); // const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); // await captcha.screenshot({ path: p }); From 3c15252d8babebd994acbd9eb8518af35edfedc3 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 19 Dec 2023 12:06:15 +0100 Subject: [PATCH 396/520] pg: use chalk to color game codes blue, closes #250 --- package-lock.json | 53 ++++++++++++++++++++++++++++++++--------------- package.json | 1 + prime-gaming.js | 5 +++-- 3 files changed, 40 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index c17609e..26603de 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.4.0", "license": "AGPL-3.0-only", "dependencies": { + "chalk": "^5.3.0", "cross-env": "^7.0.3", "dotenv": "^16.3.1", "enquirer": "^2.4.1", @@ -340,16 +341,11 @@ } }, "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" @@ -583,6 +579,22 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -1859,14 +1871,9 @@ "dev": true }, "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==" }, "clone-deep": { "version": "0.2.4", @@ -2010,6 +2017,18 @@ "optionator": "^0.9.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + } } }, "eslint-scope": { diff --git a/package.json b/package.json index 15b2165..1f3279e 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "type": "module", "dependencies": { + "chalk": "^5.3.0", "cross-env": "^7.0.3", "dotenv": "^16.3.1", "enquirer": "^2.4.1", diff --git a/prime-gaming.js b/prime-gaming.js index e60caf7..0d9502b 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,5 +1,6 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; +import chalk from 'chalk'; import { resolve, jsonDb, datetime, stealth, filenamify, prompt, confirm, notify, html_game_list, handleSIGINT } from './util.js'; import { cfg } from './config.js'; @@ -182,7 +183,7 @@ try { }; if (store in redeem) { // did not work for linked origin: && !await page.locator('div:has-text("Successfully Claimed")').count() const code = await Promise.any([page.inputValue('input[type="text"]'), page.textContent('[data-a-target="ClaimStateClaimCodeContent"]').then(s => s.replace('Your code: ', ''))]); // input: Legacy Games; text: gog.com - console.log(' Code to redeem game:', code); + console.log(' Code to redeem game:', chalk.blue(code)); if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); // full text: Click here to enter your redemption code. } @@ -367,7 +368,7 @@ try { dlc_unlinked[unlinked_store].push(title); } else { const code = await page.inputValue('input[type="text"]'); - console.log(' Code to redeem game:', code); + console.log(' Code to redeem game:', chalk.blue(code)); db.data[user][title].code = code; db.data[user][title].status = 'claimed'; // notify_game.status = `${redeem_action} ${code} on ${store}`; From 8bf36a2158f4b5410d5a4e3f9a87725d51309412 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 19 Dec 2023 12:27:55 +0100 Subject: [PATCH 397/520] specify engines.node >=15 for ||=, #264 --- package-lock.json | 3 +++ package.json | 3 +++ 2 files changed, 6 insertions(+) diff --git a/package-lock.json b/package-lock.json index 26603de..930e7bf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,6 +21,9 @@ "devDependencies": { "@stylistic/eslint-plugin-js": "^1.5.1", "eslint": "^8.56.0" + }, + "engines": { + "node": ">=15" } }, "node_modules/@aashutoshrathi/word-wrap": { diff --git a/package.json b/package.json index 1f3279e..3c1be2a 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,9 @@ "lint": "npx eslint ." }, "type": "module", + "engines": { + "node": ">=15" + }, "dependencies": { "chalk": "^5.3.0", "cross-env": "^7.0.3", From f4270e176fa12aca5f7f14c0e998f8cfc9045d9c Mon Sep 17 00:00:00 2001 From: Jannis Hell Date: Sun, 12 Nov 2023 11:18:30 +0100 Subject: [PATCH 398/520] Update prime-gaming.js The button was renamed to "Claim" instead of "Claim now" this causes / fixes #208 --- prime-gaming.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 0d9502b..95860b5 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -137,7 +137,7 @@ try { if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; - await Promise.any([page.click('button:has-text("Get game")'), page.click('button:has-text("Claim now")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")'), page.waitForSelector('.thank-you-title:has-text("Success")')]); // waits for navigation + await Promise.any([page.click('button:has-text("Get game")'), page.click('button:has-text("Claim")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")'), page.waitForSelector('.thank-you-title:has-text("Success")')]); // waits for navigation // TODO would be simpler than the below, but will block for linked stores without code // const redeem_text = await page.textContent('text=/ code on /'); // FAQ: How do I redeem my code? @@ -348,8 +348,8 @@ try { try { await page.goto(url, { waitUntil: 'domcontentloaded' }); // most games have a button 'Get in-game content' - // epic-games: Fall Guys: Claim now -> Continue -> Go to Epic Games (despite account linked and logged into epic-games) -> not tied to account but via some cookie? - await Promise.any([page.click('button:has-text("Get in-game content")'), page.click('button:has-text("Claim your gift")'), page.click('button:has-text("Claim now")').then(() => page.click('button:has-text("Continue")'))]); + // epic-games: Fall Guys: Claim -> Continue -> Go to Epic Games (despite account linked and logged into epic-games) -> not tied to account but via some cookie? + await Promise.any([page.click('button:has-text("Get in-game content")'), page.click('button:has-text("Claim your gift")'), page.click('button:has-text("Claim")').then(() => page.click('button:has-text("Continue")'))]); page.click('button:has-text("Continue")').catch(_ => { }); const linkAccountButton = page.locator('[data-a-target="LinkAccountButton"]'); let unlinked_store; From bdc305aa83b23b25b3dd3d58b5b4520d30b00b01 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 21 Dec 2023 01:53:47 +0100 Subject: [PATCH 399/520] reorder package.json --- package.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package.json b/package.json index 3c1be2a..6c954cd 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,12 @@ "version": "1.4.0", "description": "Automatically claims free games on the Epic Games Store, Amazon Prime Gaming and GOG.", "homepage": "https://github.com/vogler/free-games-claimer", + "repository": { + "type": "git", + "url": "https://github.com/vogler/free-games-claimer.git" + }, + "author": "Ralf Vogler", + "license": "AGPL-3.0-only", "main": "index.js", "scripts": { "docker:build": "docker build . -t ghcr.io/vogler/free-games-claimer", @@ -23,12 +29,6 @@ "playwright-firefox": "^1.40.1", "puppeteer-extra-plugin-stealth": "^2.11.2" }, - "repository": { - "type": "git", - "url": "https://github.com/vogler/free-games-claimer.git" - }, - "author": "Ralf Vogler", - "license": "AGPL-3.0-only", "devDependencies": { "@stylistic/eslint-plugin-js": "^1.5.1", "eslint": "^8.56.0" From 19e9bb9cfc614fbf8b632b96cda6aebea035f3bf Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 21 Dec 2023 01:55:54 +0100 Subject: [PATCH 400/520] eg: changed: move password fill and fix captcha locators, #260 --- epic-games.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/epic-games.js b/epic-games.js index 90e0683..069eee5 100644 --- a/epic-games.js +++ b/epic-games.js @@ -87,15 +87,15 @@ try { // await page.click('text=Sign in with Epic Games'); await page.fill('#email', email); await page.click('button[type="submit"]'); - await page.fill('#password', password); - await page.click('button[type="submit"]'); - page.waitForSelector('#h_captcha_challenge_login_prod iframe').then(async () => { + page.waitForSelector('.h_captcha_challenge iframe').then(async () => { console.error('Got a captcha during login (likely due to too many attempts)! You may solve it in the browser, get a new IP or try again in a few hours.'); await notify('epic-games: got captcha during login. Please check.'); }).catch(_ => { }); - page.waitForSelector('h6:has-text("Incorrect response.")').then(async () => { + page.waitForSelector('p:has-text("Incorrect response.")').then(async () => { console.error('Incorrect repsonse for captcha!'); }).catch(_ => { }); + await page.fill('#password', password); + await page.click('button[type="submit"]'); // handle MFA, but don't await it page.waitForURL('**/id/login/mfa**').then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); From 30957d63d0289514b88b509f8d8fdd114b106d36 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 21 Dec 2023 16:16:48 +0100 Subject: [PATCH 401/520] DEBUG_NETWORK=1 to log network requests and responses instead of DEBUG && RECORD --- config.js | 1 + epic-games.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/config.js b/config.js index 0962159..bd41c7d 100644 --- a/config.js +++ b/config.js @@ -6,6 +6,7 @@ dotenv.config({ path: 'data/config.env' }); // loads env vars from file - will n // Options - also see table in README.md export const cfg = { debug: process.env.DEBUG == '1' || process.env.PWDEBUG == '1', // runs non-headless and opens https://playwright.dev/docs/inspector + debug_network: process.env.DEBUG_NETWORK == '1', // log network requests and responses record: process.env.RECORD == '1', // `recordHar` (network) + `recordVideo` time: process.env.TIME == '1', // log duration of each step dryrun: process.env.DRYRUN == '1', // don't claim anything diff --git a/epic-games.js b/epic-games.js index 069eee5..880e337 100644 --- a/epic-games.js +++ b/epic-games.js @@ -53,7 +53,7 @@ const page = context.pages().length ? context.pages()[0] : await context.newPage // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); // eslint-disable-next-line no-undef if (cfg.debug) console.debug(await page.evaluate(() => window.screen)); -if (cfg.record && cfg.debug) { +if (cfg.debug_network) { // const filter = _ => true; const filter = r => r.url().includes('store.epicgames.com'); page.on('request', request => filter(request) && console.log('>>', request.method(), request.url())); From 9f97805517041e60dddd90274c02834165ae6a1f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 21 Dec 2023 16:38:12 +0100 Subject: [PATCH 402/520] debug navigator.{userAgent, platform} --- epic-games.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index 880e337..349b726 100644 --- a/epic-games.js +++ b/epic-games.js @@ -50,9 +50,8 @@ await stealth(context); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist -// console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); // eslint-disable-next-line no-undef -if (cfg.debug) console.debug(await page.evaluate(() => window.screen)); +if (cfg.debug) console.debug(await page.evaluate(() => [window.screen, navigator.userAgent, navigator.platform])); if (cfg.debug_network) { // const filter = _ => true; const filter = r => r.url().includes('store.epicgames.com'); From e06ad3a27ff4dc0c7aa13625d5e2f1e96526e344 Mon Sep 17 00:00:00 2001 From: Jannis Hell Date: Fri, 22 Dec 2023 16:40:02 +0100 Subject: [PATCH 403/520] use tw-button css class over button html element selector --- prime-gaming.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 95860b5..9558a92 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -114,7 +114,7 @@ try { console.log('Current free game:', title); if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; - await (await card.$('button:has-text("Claim")')).click(); + await (await card.$('.tw-button:has-text("Claim")')).click(); db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; notify_games.push({ title, status: 'claimed', url: URL_CLAIM }); // const img = await (await card.$('img.tw-image')).getAttribute('src'); @@ -137,7 +137,7 @@ try { if (cfg.debug) await page.pause(); if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; - await Promise.any([page.click('button:has-text("Get game")'), page.click('button:has-text("Claim")'), page.click('button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")'), page.waitForSelector('.thank-you-title:has-text("Success")')]); // waits for navigation + await Promise.any([page.click('.tw-button:has-text("Get game")'), page.click('.tw-button:has-text("Claim")'), page.click('.tw-button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")'), page.waitForSelector('.thank-you-title:has-text("Success")')]); // waits for navigation // TODO would be simpler than the below, but will block for linked stores without code // const redeem_text = await page.textContent('text=/ code on /'); // FAQ: How do I redeem my code? From 093ed813c63e1df7d43c04b8eeb109a02d2c932a Mon Sep 17 00:00:00 2001 From: Jannis Hell Date: Sat, 23 Dec 2023 11:55:27 +0100 Subject: [PATCH 404/520] Update prime-gaming.js account for more than one account link button --- prime-gaming.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 9558a92..25574b2 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -349,12 +349,12 @@ try { await page.goto(url, { waitUntil: 'domcontentloaded' }); // most games have a button 'Get in-game content' // epic-games: Fall Guys: Claim -> Continue -> Go to Epic Games (despite account linked and logged into epic-games) -> not tied to account but via some cookie? - await Promise.any([page.click('button:has-text("Get in-game content")'), page.click('button:has-text("Claim your gift")'), page.click('button:has-text("Claim")').then(() => page.click('button:has-text("Continue")'))]); + await Promise.any([page.click('.tw-button:has-text("Get in-game content")'), page.click('.tw-button:has-text("Claim your gift")'), page.click('.tw-button:has-text("Claim")').then(() => page.click('button:has-text("Continue")'))]); page.click('button:has-text("Continue")').catch(_ => { }); const linkAccountButton = page.locator('[data-a-target="LinkAccountButton"]'); let unlinked_store; if (await linkAccountButton.count()) { - unlinked_store = await linkAccountButton.getAttribute('aria-label'); + unlinked_store = await linkAccountButton.first().getAttribute('aria-label'); console.debug(' LinkAccountButton label:', unlinked_store); const match = unlinked_store.match(/Link (.*) account/); if (match && match.length == 2) unlinked_store = match[1]; From ace4ef8303b0cf312323a62c9138ad335788b75d Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 23 Dec 2023 18:16:30 +0100 Subject: [PATCH 405/520] pg: DLC: ignore timeout if there is no code https://github.com/vogler/free-games-claimer/issues/208#issuecomment-1868317276 --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 25574b2..067ef37 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -367,7 +367,7 @@ try { dlc_unlinked[unlinked_store] ??= []; dlc_unlinked[unlinked_store].push(title); } else { - const code = await page.inputValue('input[type="text"]'); + const code = await page.inputValue('input[type="text"]').catch(_ => undefined); console.log(' Code to redeem game:', chalk.blue(code)); db.data[user][title].code = code; db.data[user][title].status = 'claimed'; From 5343535429e447009812e4e6ec969d8564383c97 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 24 Dec 2023 01:10:17 +0100 Subject: [PATCH 406/520] Create FUNDING.yml --- .github/FUNDING.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..23af7b2 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: https://paypal.me/voglerr # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From 2a191006014571908942cc0c27d9b2e562660873 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 24 Dec 2023 15:39:38 +0100 Subject: [PATCH 407/520] Update FUNDING.yml --- .github/FUNDING.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 23af7b2..0bf5ac4 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,7 @@ # These are supported funding model platforms -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: # Replace with a single Patreon username +github: [vogler] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: 11239349 # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel From 68b66444ca093c23beb37f56caf183a496acae7f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 24 Dec 2023 15:51:57 +0100 Subject: [PATCH 408/520] eg: set cookie to void 'please provide your date of birth', closes #275 --- epic-games.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 349b726..261057b 100644 --- a/epic-games.js +++ b/epic-games.js @@ -63,7 +63,10 @@ const notify_games = []; let user; try { - await context.addCookies([{ name: 'OptanonAlertBoxClosed', value: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), domain: '.epicgames.com', path: '/' }]); // Accept cookies to get rid of banner to save space on screen. Set accept time to 5 days ago. + await context.addCookies([ + { name: 'OptanonAlertBoxClosed', value: new Date(Date.now() - 5 * 24 * 60 * 60 * 1000).toISOString(), domain: '.epicgames.com', path: '/' }, // Accept cookies to get rid of banner to save space on screen. Set accept time to 5 days ago. + { name: 'HasAcceptedAgeGates', value: 'USK:9007199254740991,general:18,EPIC SUGGESTED RATING:18', domain: 'store.epicgames.com', path: '/' }, // gets rid of 'To continue, please provide your date of birth', https://github.com/vogler/free-games-claimer/issues/275, USK number doesn't seem to matter, cookie from 'Fallout 3: Game of the Year Edition' + ]); await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // 'domcontentloaded' faster than default 'load' https://playwright.dev/docs/api/class-page#page-goto @@ -146,6 +149,9 @@ try { // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { console.log(' This game contains mature content recommended only for ages 18+'); + if (await page.locator('[data-testid="AgeSelect"]').count()) { + console.error(' Got "To continue, please provide your date of birth" - This shouldn\'t happen due to cookie set above. Please report to https://github.com/vogler/free-games-claimer/issues/275'); + } await page.click('button:has-text("Continue")', { delay: 111 }); await page.waitForTimeout(2000); } From e66d10574d5c64c309dcae54cb30b6ce69d1fbdf Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 24 Dec 2023 16:21:15 +0100 Subject: [PATCH 409/520] Update FUNDING.yml --- .github/FUNDING.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 0bf5ac4..b8e0cc8 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,7 @@ # These are supported funding model platforms -github: [vogler] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: 11239349 # Replace with a single Patreon username +github: vogler # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: "111239349" # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel From a2af1ccd09339bed5f7d32b089e6e4ab4ff47901 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 24 Dec 2023 16:22:35 +0100 Subject: [PATCH 410/520] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index b8e0cc8..82bc994 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,7 @@ # These are supported funding model platforms github: vogler # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: "111239349" # Replace with a single Patreon username +patreon: https://patreon.com/user?u=111239349 # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel From 2dec0080d922ca14db9aa8ab890980092f02419c Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 24 Dec 2023 16:23:01 +0100 Subject: [PATCH 411/520] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 82bc994..4baac3b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,7 @@ # These are supported funding model platforms github: vogler # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -patreon: https://patreon.com/user?u=111239349 # Replace with a single Patreon username +# patreon: 111239349 # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel From 377ee736cb49566e4631161952e136c6f88d4ac9 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 24 Dec 2023 16:29:05 +0100 Subject: [PATCH 412/520] Update FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 4baac3b..95655c3 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,7 +1,7 @@ # These are supported funding model platforms github: vogler # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] -# patreon: 111239349 # Replace with a single Patreon username +patreon: fgc # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel From 4518914bfda0c8052942f909d2079938955ef21f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 24 Dec 2023 17:04:37 +0100 Subject: [PATCH 413/520] Update FUNDING.yml --- .github/FUNDING.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 95655c3..9a0d965 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -3,11 +3,11 @@ github: vogler # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: fgc # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username -ko_fi: # Replace with a single Ko-fi username +ko_fi: vogler # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry -liberapay: # Replace with a single Liberapay username +liberapay: vogler # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry -custom: https://paypal.me/voglerr # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] +custom: ["https://www.buymeacoffee.com/vogler", "https://paypal.me/voglerr"] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] From 09a364c67d05d8f337123d83c562fee35b7076fa Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 26 Dec 2023 16:48:25 +0100 Subject: [PATCH 414/520] extract chrome-specific comments/args into util.js/launchChromium --- epic-games.js | 15 +++------------ util.js | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/epic-games.js b/epic-games.js index 261057b..f91d7e5 100644 --- a/epic-games.js +++ b/epic-games.js @@ -16,13 +16,8 @@ const db = await jsonDb('epic-games.json', {}); if (cfg.time) console.time('startup'); -// https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 -// const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox - // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(cfg.dir.browser, { - // chrome will not work in linux arm64, only chromium - // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? @@ -32,14 +27,10 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { 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-${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 - args: [ // https://peter.sh/experiments/chromium-command-line-switches - // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' - // '--restore-last-session', // does not apply for crash/killed - '--hide-crash-restore-bubble', - // `--disable-extensions-except=${ext}`, - // `--load-extension=${ext}`, + // user settings for firefox have to be put in $BROWSER_DIR/user.js + args: [ // https://wiki.mozilla.org/Firefox/CommandLineOptions + // '-kiosk', ], - // ignoreDefaultArgs: ['--enable-automation'], // remove default arg that shows the info bar with 'Chrome is being controlled by automated test software.'. Since Chromeium 106 this leads to show another info bar with 'You are using an unsupported command-line flag: --no-sandbox. Stability and security will suffer.'. }); handleSIGINT(context); diff --git a/util.js b/util.js index acd6c0b..6ed1d10 100644 --- a/util.js +++ b/util.js @@ -27,6 +27,28 @@ export const handleSIGINT = (context = null) => process.on('SIGINT', async () => if (context) await context.close(); // in order to save recordings also on SIGINT, we need to disable Playwright's handleSIGINT and close the context ourselves }); +export const launchChromium = async options => { + const { chromium } = await import('playwright-chromium'); // stealth plugin needs no outdated playwright-extra + + // https://www.nopecha.com extension source from https://github.com/NopeCHA/NopeCHA/releases/tag/0.1.16 + // const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox + + const context = chromium.launchPersistentContext(cfg.dir.browser, { + // chrome will not work in linux arm64, only chromium + // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge + args: [ // https://peter.sh/experiments/chromium-command-line-switches + // don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' + // '--restore-last-session', // does not apply for crash/killed + '--hide-crash-restore-bubble', + // `--disable-extensions-except=${ext}`, + // `--load-extension=${ext}`, + ], + // ignoreDefaultArgs: ['--enable-automation'], // remove default arg that shows the info bar with 'Chrome is being controlled by automated test software.'. Since Chromeium 106 this leads to show another info bar with 'You are using an unsupported command-line flag: --no-sandbox. Stability and security will suffer.'. + ...options, + }); + return context; +}; + export const stealth = async context => { // stealth with playwright: https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212 // https://github.com/berstend/puppeteer-extra/tree/master/packages/puppeteer-extra-plugin-stealth/evasions From 5a4f07ce702bf158435bede953720612027d072a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 26 Dec 2023 16:54:10 +0100 Subject: [PATCH 415/520] mv notify-test.js test/notify.js --- notify-test.js => test/notify.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename notify-test.js => test/notify.js (93%) diff --git a/notify-test.js b/test/notify.js similarity index 93% rename from notify-test.js rename to test/notify.js index b9c293f..3479076 100644 --- a/notify-test.js +++ b/test/notify.js @@ -1,6 +1,6 @@ /* eslint-disable no-constant-condition */ -import { delay, html_game_list, notify } from './util.js'; -import { cfg } from './config.js'; +import { delay, html_game_list, notify } from '../util.js'; +import { cfg } from '../config.js'; const URL_CLAIM = 'https://gaming.amazon.com/home'; // dummy URL From 6b9420804bc877d86705efc9124634a2c2dd7529 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 26 Dec 2023 16:58:14 +0100 Subject: [PATCH 416/520] test to show enquirer's sigint issue --- test/sigint-enquirer-raw.js | 40 ++++++++++++++++++++++++++++++++++ test/sigint-enquirer-simple.js | 15 +++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 test/sigint-enquirer-raw.js create mode 100644 test/sigint-enquirer-simple.js diff --git a/test/sigint-enquirer-raw.js b/test/sigint-enquirer-raw.js new file mode 100644 index 0000000..0459aa7 --- /dev/null +++ b/test/sigint-enquirer-raw.js @@ -0,0 +1,40 @@ +// https://github.com/enquirer/enquirer/issues/372 +import { prompt } from '../util.js'; + +const handleSIGINT = () => process.on('SIGINT', () => { // e.g. when killed by Ctrl-C + console.log('\nInterrupted by SIGINT. Exit!'); + process.exitCode = 130; +}); +handleSIGINT(); + +function onRawSIGINT(fn) { + const { stdin, stdout } = process; + stdin.setRawMode(true); + stdin.resume(); + stdin.on('data', data => { + const key = data.toString('utf-8'); + if (key === '\u0003') { // ctrl + c + fn(); + } else { + stdout.write(key); + } + }); +} +onRawSIGINT(() => { + console.log('raw'); process.exit(1); +}); + +console.log('hello'); +console.error('hello error'); +try { + let i = 'foo'; + i = await prompt(); // SIGINT no longer handled if this is executed + // handleSIGINT(); + console.log('value:', i); + setTimeout(() => console.log('timeout 3s'), 3000); +} catch (e) { + process.exitCode ||= 1; + console.log('catch. exitCode:', process.exitCode); + console.error(e); +} +console.log('end. exitCode:', process.exitCode); diff --git a/test/sigint-enquirer-simple.js b/test/sigint-enquirer-simple.js new file mode 100644 index 0000000..13e7d02 --- /dev/null +++ b/test/sigint-enquirer-simple.js @@ -0,0 +1,15 @@ +// https://github.com/enquirer/enquirer/issues/372 +import Enquirer from 'enquirer'; +const enquirer = new Enquirer(); + +let interrupted = false; +process.on('SIGINT', () => { + if (interrupted) process.exit(); + interrupted = true; + console.log('SIGINT'); +}); +await enquirer.prompt({ + type: 'input', + name: 'username', + message: 'What is your username?', +}); From 64676795d144bef79a10700cde20ced5a267a4bd Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 26 Dec 2023 17:09:14 +0100 Subject: [PATCH 417/520] mv {config,migrate,util,version}.js src/ --- README.md | 2 +- epic-games.js | 4 ++-- gog.js | 4 ++-- prime-gaming.js | 2 +- config.js => src/config.js | 0 migrate.js => src/migrate.js | 0 util.js => src/util.js | 2 +- version.js => src/version.js | 0 unrealengine.js | 4 ++-- xbox.js | 4 ++-- 10 files changed, 11 insertions(+), 11 deletions(-) rename config.js => src/config.js (100%) rename migrate.js => src/migrate.js (100%) rename util.js => src/util.js (99%) rename version.js => src/version.js (100%) diff --git a/README.md b/README.md index 5c9e90f..84ab623 100644 --- a/README.md +++ b/README.md @@ -93,7 +93,7 @@ Available options/variables and their default values: | GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | | GOG_NEWSLETTER | 0 | Do not unsubscribe from newsletter after claiming a game if 1. | -See `config.js` for all options. +See `src/config.js` for all options. #### How to set options You can add options directly in the command or put them in a file to load. diff --git a/epic-games.js b/epic-games.js index f91d7e5..ae1505d 100644 --- a/epic-games.js +++ b/epic-games.js @@ -2,8 +2,8 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdate import { authenticator } from 'otplib'; import path from 'path'; import { existsSync, writeFileSync } from 'fs'; -import { resolve, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; -import { cfg } from './config.js'; +import { resolve, jsonDb, datetime, stealth, 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); diff --git a/gog.js b/gog.js index 74623e6..048e04a 100644 --- a/gog.js +++ b/gog.js @@ -1,6 +1,6 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra -import { resolve, jsonDb, datetime, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; -import { cfg } from './config.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, 'gog', ...a); diff --git a/prime-gaming.js b/prime-gaming.js index 067ef37..f437d91 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -2,7 +2,7 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdate import { authenticator } from 'otplib'; import chalk from 'chalk'; import { resolve, jsonDb, datetime, stealth, filenamify, prompt, confirm, notify, html_game_list, handleSIGINT } from './util.js'; -import { cfg } from './config.js'; +import { cfg } from './src/config.js'; const screenshot = (...a) => resolve(cfg.dir.screenshots, 'prime-gaming', ...a); diff --git a/config.js b/src/config.js similarity index 100% rename from config.js rename to src/config.js diff --git a/migrate.js b/src/migrate.js similarity index 100% rename from migrate.js rename to src/migrate.js diff --git a/util.js b/src/util.js similarity index 99% rename from util.js rename to src/util.js index 6ed1d10..f82102b 100644 --- a/util.js +++ b/src/util.js @@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // explicit object instead of Object.fromEntries since the built-in type would loose the keys, better type: https://dev.to/svehla/typescript-object-fromentries-389c -export const dataDir = s => path.resolve(__dirname, 'data', s); +export const dataDir = s => path.resolve(__dirname, '..', 'data', s); // modified path.resolve to return null if first argument is '0', used to disable screenshots export const resolve = (...a) => a.length && a[0] == '0' ? null : path.resolve(...a); diff --git a/version.js b/src/version.js similarity index 100% rename from version.js rename to src/version.js diff --git a/unrealengine.js b/unrealengine.js index cd2eb33..bfd3417 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -5,8 +5,8 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdate import { authenticator } from 'otplib'; import path from 'path'; import { writeFileSync } from 'fs'; -import { resolve, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js'; -import { cfg } from './config.js'; +import { resolve, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './src/util.js'; +import { cfg } from './src/config.js'; const screenshot = (...a) => resolve(cfg.dir.screenshots, 'unrealengine', ...a); diff --git a/xbox.js b/xbox.js index 54cccf2..008388c 100644 --- a/xbox.js +++ b/xbox.js @@ -7,8 +7,8 @@ import { jsonDb, notify, prompt, -} from './util.js'; -import { cfg } from './config.js'; +} from './src/util.js'; +import { cfg } from './src/config.js'; // ### SETUP const URL_CLAIM = 'https://www.xbox.com/en-US/live/gold'; // #gameswithgold"; From 105f6f414acd8d5113c69d1ef81f585a981ecf60 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 26 Dec 2023 17:49:51 +0100 Subject: [PATCH 418/520] eg: only ask for password after email submit -> notice captcha before --- epic-games.js | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/epic-games.js b/epic-games.js index ae1505d..1a2b602 100644 --- a/epic-games.js +++ b/epic-games.js @@ -74,9 +74,18 @@ try { await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); if (cfg.eg_email && cfg.eg_password) console.info('Using email and password from environment.'); else console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).'); + const notifyBrowserLogin = async () => { + console.log('Waiting for you to login in the browser.'); + await notify('epic-games: no longer signed in and not enough options set for automatic login.'); + if (cfg.headless) { + console.log('Run `SHOW=1 node epic-games` to login in the opened browser.'); + await context.close(); // finishes potential recording + process.exit(1); + } + }; const email = cfg.eg_email || await prompt({ message: 'Enter email' }); - const password = email && (cfg.eg_password || await prompt({ type: 'password', message: 'Enter password' })); - if (email && password) { + if (!email) await notifyBrowserLogin(); + else { // await page.click('text=Sign in with Epic Games'); await page.fill('#email', email); await page.click('button[type="submit"]'); @@ -87,6 +96,8 @@ try { page.waitForSelector('p:has-text("Incorrect response.")').then(async () => { console.error('Incorrect repsonse for captcha!'); }).catch(_ => { }); + const password = email && (cfg.eg_password || await prompt({ type: 'password', message: 'Enter password' })); + if (!password) await notifyBrowserLogin(); await page.fill('#password', password); await page.click('button[type="submit"]'); // handle MFA, but don't await it @@ -97,14 +108,6 @@ try { await page.locator('input[name="code-input-0"]').pressSequentially(otp.toString()); await page.click('button[type="submit"]'); }).catch(_ => { }); - } else { - console.log('Waiting for you to login in the browser.'); - await notify('epic-games: no longer signed in and not enough options set for automatic login.'); - if (cfg.headless) { - console.log('Run `SHOW=1 node epic-games` to login in the opened browser.'); - await context.close(); // finishes potential recording - process.exit(1); - } } await page.waitForURL(URL_CLAIM); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); From baeaaa64f8bb9b8d0ba8c699dcd04ae5265e3d84 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 26 Dec 2023 17:52:18 +0100 Subject: [PATCH 419/520] docker: firefox: privacy.resistFingerprinting fixes #261 https://github.com/vogler/free-games-claimer/issues/261#issuecomment-1868385830 Docker container will ask/fill email, challenge captcha, ask/fill password, challenge captcha again. --- docker-entrypoint.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 0eea025..77b5588 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -12,6 +12,13 @@ echo "Build: $NOW" # https://bugs.chromium.org/p/chromium/issues/detail?id=367048 rm -f /fgc/data/browser/SingletonLock +# Firefox preferences are stored in $BROWSER_DIR/pref.js and can be overridden by a file user.js +# Since this file has to be in the volume (data/browser), we can't do this in Dockerfile. +mkdir -p /fgc/data/browser +# fix for 'Incorrect response' after solving a captcha correctly - https://github.com/vogler/free-games-claimer/issues/261#issuecomment-1868385830 +echo 'user_pref("privacy.resistFingerprinting", true);' >> /fgc/data/browser/user.js +# TODO disable session restore message? + # Remove X server display lock, fix for `docker compose up` which reuses container which made it fail after initial run, https://github.com/vogler/free-games-claimer/issues/31 # echo $DISPLAY # ls -l /tmp/.X11-unix/ From b68021b8e824ff4f0b66727a8be41c1eb141e239 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 26 Dec 2023 18:01:32 +0100 Subject: [PATCH 420/520] docker: firefox: overwrite user.js instead of appending on every run, #261 --- docker-entrypoint.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 77b5588..679acc6 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -16,7 +16,13 @@ rm -f /fgc/data/browser/SingletonLock # Since this file has to be in the volume (data/browser), we can't do this in Dockerfile. mkdir -p /fgc/data/browser # fix for 'Incorrect response' after solving a captcha correctly - https://github.com/vogler/free-games-claimer/issues/261#issuecomment-1868385830 -echo 'user_pref("privacy.resistFingerprinting", true);' >> /fgc/data/browser/user.js +# echo 'user_pref("privacy.resistFingerprinting", true);' > /fgc/data/browser/user.js +cat << EOT > /fgc/data/browser/user.js +user_pref("privacy.resistFingerprinting", true); +// user_pref("privacy.resistFingerprinting.letterboxing", true); +// user_pref("browser.contentblocking.category", "strict"); +// user_pref("webgl.disabled", true); +EOT # TODO disable session restore message? # Remove X server display lock, fix for `docker compose up` which reuses container which made it fail after initial run, https://github.com/vogler/free-games-claimer/issues/31 From a9e20928eec5b6ec8aee77cc2377be5c7f4feb82 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 26 Dec 2023 18:56:36 +0100 Subject: [PATCH 421/520] pg: fix util.js -> src/util.js https://github.com/vogler/free-games-claimer/commit/64676795d144bef79a10700cde20ced5a267a4bd#r135743209 --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index f437d91..7b7f18b 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -1,7 +1,7 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; import chalk from 'chalk'; -import { resolve, jsonDb, datetime, stealth, filenamify, prompt, confirm, notify, html_game_list, handleSIGINT } from './util.js'; +import { resolve, jsonDb, datetime, stealth, filenamify, prompt, confirm, notify, html_game_list, handleSIGINT } from './src/util.js'; import { cfg } from './src/config.js'; const screenshot = (...a) => resolve(cfg.dir.screenshots, 'prime-gaming', ...a); From fb8f38706aca029eef140e66724d1e3d6ec09621 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 27 Dec 2023 10:22:54 +0100 Subject: [PATCH 422/520] mention pipx, #276 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 84ab623..dfb5c92 100644 --- a/README.md +++ b/README.md @@ -37,7 +37,7 @@ 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` -4. Run `pip install apprise` to install [apprise](https://github.com/caronc/apprise) if you want notifications +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`... From 56cfab6e21c3b8e86f9066f2019e1cfd69e8ea2a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 28 Dec 2023 11:28:23 +0100 Subject: [PATCH 423/520] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dfb5c92..265bfad 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ _Works on Windows/macOS/Linux._ Raspberry Pi (3, 4, Zero 2): [requires 64-bit OS](https://github.com/vogler/free-games-claimer/issues/3) like Raspberry Pi OS or Ubuntu (Raspbian won't work since it's 32-bit). ## How to run -Easy option: [install Docker](https://docs.docker.com/get-docker/) (or [podman](https://podman-desktop.io/)) and run this command in a terminal (Windows: `cmd`, `.bat` file): +Easy option: [install Docker](https://docs.docker.com/get-docker/) (or [podman](https://podman-desktop.io/)) and run this command in a terminal: ``` docker run --rm -it -p 6080:6080 -v fgc:/fgc/data --pull=always ghcr.io/vogler/free-games-claimer ``` @@ -156,7 +156,7 @@ If you want it to run regularly, you have to schedule the runs yourself: - Linux/macOS: `crontab -e` ([example](https://github.com/vogler/free-games-claimer/discussions/56)) - macOS: [launchd](https://stackoverflow.com/questions/132955/how-do-i-set-a-task-to-run-every-so-often) -- Windows: [task scheduler](https://active-directory-wp.com/docs/Usage/How_to_add_a_cron_job_on_Windows/Scheduled_tasks_and_cron_jobs_on_Windows/index.html), [other options](https://stackoverflow.com/questions/132971/what-is-the-windows-version-of-cron) +- Windows: [task scheduler](https://active-directory-wp.com/docs/Usage/How_to_add_a_cron_job_on_Windows/Scheduled_tasks_and_cron_jobs_on_Windows/index.html), [other options](https://stackoverflow.com/questions/132971/what-is-the-windows-version-of-cron), or just put the command in a `.bat` file in Autostart if you restart often... - any OS: use a process manager like [pm2](https://pm2.keymetrics.io/docs/usage/restart-strategies/) - Docker Compose `command: bash -c "node epic-games; node prime-gaming; node gog; echo sleeping; sleep 1d"` additionally add `restart: unless-stopped` to it. From 7ca79bd4139d61eabc05a50326356923c1350f48 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 28 Dec 2023 17:31:18 +0100 Subject: [PATCH 424/520] eg: debug window.screen --- epic-games.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 1a2b602..2644c40 100644 --- a/epic-games.js +++ b/epic-games.js @@ -41,8 +41,10 @@ await stealth(context); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist + +// some debug info about the page (screen dimensions, user agent, platform) // eslint-disable-next-line no-undef -if (cfg.debug) console.debug(await page.evaluate(() => [window.screen, navigator.userAgent, navigator.platform])); +if (cfg.debug) console.debug(await page.evaluate(() => [(({ width, height, availWidth, availHeight }) => ({ width, height, availWidth, availHeight }))(window.screen), navigator.userAgent, navigator.platform, navigator.vendor])); // deconstruct screen needed since `window.screen` prints {}, `window.screen.toString()` '[object Screen]', and can't use some pick function without defining it on `page` if (cfg.debug_network) { // const filter = _ => true; const filter = r => r.url().includes('store.epicgames.com'); From c3657f05ac6440563b5bf82cd3db37941d8bf1b5 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 28 Dec 2023 17:56:09 +0100 Subject: [PATCH 425/520] workaround for cropped viewport (Playwright regression), fixes #277 --- epic-games.js | 1 + gog.js | 1 + prime-gaming.js | 1 + unrealengine.js | 1 + xbox.js | 1 + 5 files changed, 5 insertions(+) diff --git a/epic-games.js b/epic-games.js index 2644c40..55d9ad7 100644 --- a/epic-games.js +++ b/epic-games.js @@ -41,6 +41,7 @@ await stealth(context); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist +await page.setViewportSize({ width: cfg.width, height: cfg.height }); // TODO workaround for https://github.com/vogler/free-games-claimer/issues/277 until Playwright fixes it // some debug info about the page (screen dimensions, user agent, platform) // eslint-disable-next-line no-undef diff --git a/gog.js b/gog.js index 048e04a..651afa6 100644 --- a/gog.js +++ b/gog.js @@ -25,6 +25,7 @@ handleSIGINT(context); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist +await page.setViewportSize({ width: cfg.width, height: cfg.height }); // TODO workaround for https://github.com/vogler/free-games-claimer/issues/277 until Playwright fixes it // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); const notify_games = []; diff --git a/prime-gaming.js b/prime-gaming.js index 7b7f18b..3868274 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -31,6 +31,7 @@ await stealth(context); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist +await page.setViewportSize({ width: cfg.width, height: cfg.height }); // TODO workaround for https://github.com/vogler/free-games-claimer/issues/277 until Playwright fixes it // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); const notify_games = []; diff --git a/unrealengine.js b/unrealengine.js index bfd3417..e67633e 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -36,6 +36,7 @@ await stealth(context); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist +await page.setViewportSize({ width: cfg.width, height: cfg.height }); // TODO workaround for https://github.com/vogler/free-games-claimer/issues/277 until Playwright fixes it // console.debug('userAgent:', await page.evaluate(() => navigator.userAgent)); const notify_games = []; diff --git a/xbox.js b/xbox.js index 008388c..b2406cc 100644 --- a/xbox.js +++ b/xbox.js @@ -32,6 +32,7 @@ if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist +await page.setViewportSize({ width: cfg.width, height: cfg.height }); // TODO workaround for https://github.com/vogler/free-games-claimer/issues/277 until Playwright fixes it const notify_games = []; let user; From 0ec24ef0620d3053645c11b8e41e2c8c75c34668 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sat, 30 Dec 2023 13:37:04 +0100 Subject: [PATCH 426/520] fix import paths for test/ --- test/notify.js | 4 ++-- test/sigint-enquirer-raw.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/notify.js b/test/notify.js index 3479076..6d89086 100644 --- a/test/notify.js +++ b/test/notify.js @@ -1,6 +1,6 @@ /* eslint-disable no-constant-condition */ -import { delay, html_game_list, notify } from '../util.js'; -import { cfg } from '../config.js'; +import { delay, html_game_list, notify } from '../src/util.js'; +import { cfg } from '../src/config.js'; const URL_CLAIM = 'https://gaming.amazon.com/home'; // dummy URL diff --git a/test/sigint-enquirer-raw.js b/test/sigint-enquirer-raw.js index 0459aa7..0a95892 100644 --- a/test/sigint-enquirer-raw.js +++ b/test/sigint-enquirer-raw.js @@ -1,5 +1,5 @@ // https://github.com/enquirer/enquirer/issues/372 -import { prompt } from '../util.js'; +import { prompt } from '../src/util.js'; const handleSIGINT = () => process.on('SIGINT', () => { // e.g. when killed by Ctrl-C console.log('\nInterrupted by SIGINT. Exit!'); From 28bcceb285ef7e4ccc0e4d0c1ba5a074fd29904b Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 31 Dec 2023 16:58:56 +0100 Subject: [PATCH 427/520] eg: enter date of birth if age confirmation pops up, #275 --- epic-games.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/epic-games.js b/epic-games.js index 55d9ad7..68a2dde 100644 --- a/epic-games.js +++ b/epic-games.js @@ -148,6 +148,12 @@ try { console.log(' This game contains mature content recommended only for ages 18+'); if (await page.locator('[data-testid="AgeSelect"]').count()) { console.error(' Got "To continue, please provide your date of birth" - This shouldn\'t happen due to cookie set above. Please report to https://github.com/vogler/free-games-claimer/issues/275'); + await page.locator('#month_toggle').click(); + await page.locator('#month_menu li:has-text("01")').click(); + await page.locator('#day_toggle').click(); + await page.locator('#day_menu li:has-text("01")').click(); + await page.locator('#year_toggle').click(); + await page.locator('#year_menu li:has-text("1987")').click(); } await page.click('button:has-text("Continue")', { delay: 111 }); await page.waitForTimeout(2000); From fc810af80e72d08ac5f871ea7fb512303109f1e4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 11 Jan 2024 15:58:56 +0100 Subject: [PATCH 428/520] Update README.md: add Repobeats insights --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 265bfad..754df22 100644 --- a/README.md +++ b/README.md @@ -213,6 +213,8 @@ Added notifications via [apprise](https://github.com/caronc/apprise). [![Star History Chart](https://api.star-history.com/svg?repos=vogler/free-games-claimer&type=Date)](https://star-history.com/#vogler/free-games-claimer&Date) +![Alt](https://repobeats.axiom.co/api/embed/a1c5e6e420d90e0d6b34c1285e92a69a44138faa.svg "Repobeats analytics image") + --- Logo with smaller aspect ratio (for Telegram bot etc.): 👾 - [emojipedia](https://emojipedia.org/alien-monster/) From e4e4ce703df9f018da9b848e3b60193504469f72 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 13 Feb 2024 23:45:10 +0100 Subject: [PATCH 429/520] pg: also get and store url for internal games --- prime-gaming.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 3868274..4bb0d1f 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -112,12 +112,14 @@ try { for (const card of internal) { await card.scrollIntoViewIfNeeded(); const title = await (await card.$('.item-card-details__body__primary')).innerText(); + const slug = await (await card.$('a')).getAttribute('href'); + const url = 'https://gaming.amazon.com' + slug.split('?')[0]; console.log('Current free game:', title); if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; await (await card.$('.tw-button:has-text("Claim")')).click(); - db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; - notify_games.push({ title, status: 'claimed', url: URL_CLAIM }); + db.data[user][title] ||= { title, time: datetime(), url, store: 'internal' }; + notify_games.push({ title, status: 'claimed', url }); // const img = await (await card.$('img.tw-image')).getAttribute('src'); // console.log('Image:', img); await card.screenshot({ path: screenshot('internal', `${filenamify(title)}.png`) }); From 2ea0c611e4da2729ea16fafadf576017c566cca6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 13 Feb 2024 23:48:48 +0100 Subject: [PATCH 430/520] pg: fix claiming external games, detect store early in description, delete old code --- prime-gaming.js | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 4bb0d1f..b555f67 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -138,30 +138,12 @@ try { console.log('Current free game:', title); // , url); await page.goto(url, { waitUntil: 'domcontentloaded' }); if (cfg.debug) await page.pause(); + const item_text = await page.innerText('[data-a-target="DescriptionItemDetails"]'); + const store = item_text.toLowerCase().replace(/.* on /, '').slice(0, -1); + console.log(' External store:', store); if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; - await Promise.any([page.click('.tw-button:has-text("Get game")'), page.click('.tw-button:has-text("Claim")'), page.click('.tw-button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")'), page.waitForSelector('.thank-you-title:has-text("Success")')]); // waits for navigation - - // TODO would be simpler than the below, but will block for linked stores without code - // const redeem_text = await page.textContent('text=/ code on /'); // FAQ: How do I redeem my code? - // console.log(' ', redeem_text); - // // Before July 29, 2023, redeem your offer code on GOG.com. - // // Before July 1, 2023, redeem your product code on Legacy Games. - // let store = redeem_text.toLowerCase().replace(/.* on /, '').slice(0, -1); - - let store = ''; - const store_text = await page.$('[data-a-target="hero-header-subtitle"]'); // worked fine for every store, but now no longer works for gog.com - if (store_text) { // legacy games, ? - const store_texts = await store_text.innerText(); - // Full game for PC [and MAC] on: Legacy Games, Origin, EPIC GAMES, Battle.net; alt: 3 Full PC Games on Legacy Games - store = store_texts.toLowerCase().replace(/.* on /, ''); - } else { // gog.com, ? - // $('[data-a-target="DescriptionItemDetails"]').innerText is e.g. 'Prey for PC on GOG.com.' but does not work for Legacy Games - const item_text = await page.innerText('[data-a-target="DescriptionItemDetails"]'); - store = item_text.toLowerCase().replace(/.* on /, '').slice(0, -1); - } - console.log(' External store:', store); - + await Promise.any([page.click('[data-a-target="buy-box"] .tw-button:has-text("Get game")'), page.click('[data-a-target="buy-box"] .tw-button:has-text("Claim")'), page.click('.tw-button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")'), page.waitForSelector('.thank-you-title:has-text("Success")')]); // waits for navigation db.data[user][title] ||= { title, time: datetime(), url, store }; const notify_game = { title, url }; notify_games.push(notify_game); // status is updated below From 285c7a44fd997c11706f8c667be1c83da23087b1 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Feb 2024 16:35:02 +0100 Subject: [PATCH 431/520] README.md: comment alternative starchart.cc --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 754df22..cfdddec 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,7 @@ Added notifications via [apprise](https://github.com/caronc/apprise).
[![Star History Chart](https://api.star-history.com/svg?repos=vogler/free-games-claimer&type=Date)](https://star-history.com/#vogler/free-games-claimer&Date) + ![Alt](https://repobeats.axiom.co/api/embed/a1c5e6e420d90e0d6b34c1285e92a69a44138faa.svg "Repobeats analytics image") From 61fdf566c9c2748ce06e35cd7262d314151c836d Mon Sep 17 00:00:00 2001 From: Rex Date: Fri, 1 Mar 2024 20:57:00 +0800 Subject: [PATCH 432/520] Resolve error with no default data --- xbox.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xbox.js b/xbox.js index b2406cc..67054ef 100644 --- a/xbox.js +++ b/xbox.js @@ -15,7 +15,7 @@ const URL_CLAIM = 'https://www.xbox.com/en-US/live/gold'; // #gameswithgold"; console.log(datetime(), 'started checking xbox'); -const db = await jsonDb('xbox.json'); +const db = await jsonDb('xbox.json', {}); db.data ||= {}; handleSIGINT(); From 6f06fccd461cf1cbb79dd2440c88222d4f52bfa8 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 7 Mar 2024 13:47:58 +0100 Subject: [PATCH 433/520] eslint: prefer-const --- eslint.config.js | 1 + src/util.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/eslint.config.js b/eslint.config.js index bc6447f..48b1cbc 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -24,6 +24,7 @@ export default [ // https://eslint.style/packages/js rules: { 'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], + 'prefer-const': 'error', '@stylistic/js/array-bracket-newline': ['error', 'consistent'], '@stylistic/js/array-bracket-spacing': 'error', '@stylistic/js/array-element-newline': ['error', 'consistent'], diff --git a/src/util.js b/src/util.js index f82102b..bda3f78 100644 --- a/src/util.js +++ b/src/util.js @@ -81,7 +81,7 @@ export const stealth = async context => { const evasion = await import(`puppeteer-extra-plugin-stealth/evasions/${e}/index.js`); evasion.default().onPageCreated(stealth); } - for (let evasion of stealth.callbacks) { + for (const evasion of stealth.callbacks) { await context.addInitScript(evasion.cb, evasion.a); } }; From da6964c19afbb24848c997b1d8b1f9995d360791 Mon Sep 17 00:00:00 2001 From: Targunitoth Date: Thu, 25 Apr 2024 13:51:40 +0200 Subject: [PATCH 434/520] Escape the apprise -b parameter --- src/util.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/util.js b/src/util.js index bda3f78..69cbf7a 100644 --- a/src/util.js +++ b/src/util.js @@ -116,7 +116,7 @@ export const notify = html => new Promise((resolve, reject) => { return resolve(); } // const cmd = `apprise '${cfg.notify}' ${title} -i html -b '${html}'`; // this had problems if e.g. ' was used in arg; could have `npm i shell-escape`, but instead using safer execFile which takes args as array instead of exec which spawned a shell to execute the command - const args = [cfg.notify, '-i', 'html', '-b', html]; + const args = [cfg.notify, '-i', 'html', '-b', `'${html}'`]; if (cfg.notify_title) args.push(...['-t', cfg.notify_title]); if (cfg.debug) console.debug(`apprise ${args.map(a => `'${a}'`).join(' ')}`); // this also doesn't escape, but it's just for info execFile('apprise', args, (error, stdout, stderr) => { From 3338d08a1d29584d7a209656b17266cd19b06c7a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 2 May 2024 16:58:31 +0200 Subject: [PATCH 435/520] eg: fix title for Bundles, TODO got stuck after happened for 'LISA: The Definitive Edition' - https://store.epicgames.com/en-US/bundles/lisa-the-definitive-edition --- epic-games.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/epic-games.js b/epic-games.js index 68a2dde..b703de5 100644 --- a/epic-games.js +++ b/epic-games.js @@ -159,7 +159,13 @@ try { await page.waitForTimeout(2000); } - const title = await page.locator('h1').first().innerText(); + let title; + if (await page.locator('span:text-is("About Bundle")').count()) { + // console.log(' This is a bundle containing: TODO'); + title = (await page.locator('span:has-text("Buy"):left-of([data-testid="purchase-cta-button"])').first().innerText()).replace('Buy ', ''); + } else { + title = await page.locator('h1').first().innerText(); + } const game_id = page.url().split('/').pop(); db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! console.log('Current free game:', title); @@ -191,8 +197,8 @@ try { // Accept End User License Agreement (only needed once) page.locator('input#agree').waitFor().then(async () => { - console.log('Accept End User License Agreement (only needed once)'); - await page.locator('input#agree').check(); + console.log(' Accept End User License Agreement (only needed once)'); + await page.locator('input#agree').check(); // TODO Bundle: got stuck here await page.locator('button:has-text("Accept")').click(); }).catch(_ => { }); @@ -243,7 +249,7 @@ try { // console.info(' Saved a screenshot of hcaptcha challenge to', p); // console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? }).catch(_ => { }); // may time out if not shown - await page.locator('text=Thanks for your order!').waitFor({ state: 'attached' }); + await page.locator('text=Thanks for your order!').waitFor({ state: 'attached' }); // TODO Bundle: got stuck here db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time console.log(' Claimed successfully!'); From 075106da11ce4bb250a11a47ba232072e9546f1a Mon Sep 17 00:00:00 2001 From: Tymec Date: Sat, 18 May 2024 19:45:59 +0200 Subject: [PATCH 436/520] Update config.js --- src/config.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/config.js b/src/config.js index bd41c7d..4c32d59 100644 --- a/src/config.js +++ b/src/config.js @@ -49,4 +49,6 @@ export const cfg = { // experimmental - likely to change pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores pg_claimdlc: process.env.PG_CLAIMDLC == '1', // prime-gaming: claim in-game content + // external stores + lg_email: process.env.LG_EMAIL || process.env.PG_EMAIL || process.env.EMAIL, // legacy-games: email to use for redeeming }; From 8c535f48fa8f2de63fe32cf4a000b962c3a60e6b Mon Sep 17 00:00:00 2001 From: Tymec Date: Sat, 18 May 2024 19:47:18 +0200 Subject: [PATCH 437/520] Update prime-gaming.js --- prime-gaming.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index b555f67..2bd705f 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -243,8 +243,8 @@ try { } } else if (store == 'legacy games') { await page2.fill('[name=coupon_code]', code); - await page2.fill('[name=email]', cfg.pg_email); // TODO option for sep. email? - await page2.fill('[name=email_validate]', cfg.pg_email); + await page2.fill('[name=email]', cfg.lg_email); + await page2.fill('[name=email_validate]', cfg.lg_email); await page2.uncheck('[name=newsletter_sub]'); await page2.click('[type="submit"]'); try { From c4ae3505b38f25361bf1a16ab2089fffc63e4d9f Mon Sep 17 00:00:00 2001 From: Tymec Date: Sat, 18 May 2024 19:50:13 +0200 Subject: [PATCH 438/520] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index cfdddec..1491257 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,7 @@ Available options/variables and their default values: | GOG_EMAIL | | GOG email for login. Overrides EMAIL. | | GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | | GOG_NEWSLETTER | 0 | Do not unsubscribe from newsletter after claiming a game if 1. | +| LG_EMAIL | | Legacy Games: email to use for redeeming (if not set, defaults to PG_EMAIL) | See `src/config.js` for all options. From e49fa930e0b426a497ca32e0d30cbfe707ece459 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 23 May 2024 15:54:23 +0200 Subject: [PATCH 439/520] eg: claim base game before add-on --- epic-games.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/epic-games.js b/epic-games.js index b703de5..3ead561 100644 --- a/epic-games.js +++ b/epic-games.js @@ -185,6 +185,8 @@ try { const baseUrl = 'https://store.epicgames.com' + await page.locator('a:has-text("Overview")').getAttribute('href'); console.log(' Base game:', baseUrl); // await page.click('a:has-text("Overview")'); + urls.push(baseUrl); // add base game to the list of games to claim + urls.push(url); // add add-on itself again } else { // GET console.log(' Not in library yet! Click GET.'); await page.click('[data-testid="purchase-cta-button"]', { delay: 11 }); // got stuck here without delay (or mouse move), see #75, 1ms was also enough From 9ef9798626c7f6108b0eb76cb651f8027f7a6931 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 23 May 2024 15:55:07 +0200 Subject: [PATCH 440/520] eg: detect 'Failed to challenge captcha, please try again later.' --- epic-games.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/epic-games.js b/epic-games.js index 3ead561..54ca597 100644 --- a/epic-games.js +++ b/epic-games.js @@ -251,6 +251,10 @@ try { // console.info(' Saved a screenshot of hcaptcha challenge to', p); // console.error(' Got hcaptcha challenge. To avoid it, get a link from https://www.hcaptcha.com/accessibility'); // TODO save this link in config and visit it daily to set accessibility cookie to avoid captcha challenge? }).catch(_ => { }); // may time out if not shown + iframe.locator('.payment__errors:has-text("Failed to challenge captcha, please try again later.")').waitFor().then(async () => { + console.error(' Failed to challenge captcha, please try again later.'); + await notify('epic-games: failed to challenge captcha. Please check.'); + }); await page.locator('text=Thanks for your order!').waitFor({ state: 'attached' }); // TODO Bundle: got stuck here db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time From f7c23569c7a40fa6f56d5a32d855e0b60c530a0e Mon Sep 17 00:00:00 2001 From: Vladimir Budylnikov Date: Sun, 2 Jun 2024 13:33:51 +0400 Subject: [PATCH 441/520] Update README.md I've made i howto for windows users --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cfdddec..a6e3825 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ If you want it to run regularly, you have to schedule the runs yourself: - Linux/macOS: `crontab -e` ([example](https://github.com/vogler/free-games-claimer/discussions/56)) - macOS: [launchd](https://stackoverflow.com/questions/132955/how-do-i-set-a-task-to-run-every-so-often) -- Windows: [task scheduler](https://active-directory-wp.com/docs/Usage/How_to_add_a_cron_job_on_Windows/Scheduled_tasks_and_cron_jobs_on_Windows/index.html), [other options](https://stackoverflow.com/questions/132971/what-is-the-windows-version-of-cron), or just put the command in a `.bat` file in Autostart if you restart often... +- Windows: [task scheduler](https://active-directory-wp.com/docs/Usage/How_to_add_a_cron_job_on_Windows/Scheduled_tasks_and_cron_jobs_on_Windows/index.html) ([example](https://github.com/vogler/free-games-claimer/wiki/%5BHowTo%5D-Schedule-runs-on-Windows)), [other options](https://stackoverflow.com/questions/132971/what-is-the-windows-version-of-cron), or just put the command in a `.bat` file in Autostart if you restart often... - any OS: use a process manager like [pm2](https://pm2.keymetrics.io/docs/usage/restart-strategies/) - Docker Compose `command: bash -c "node epic-games; node prime-gaming; node gog; echo sleeping; sleep 1d"` additionally add `restart: unless-stopped` to it. From cf59da5d9e74d49d684762625ae2446341115336 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 23 Jun 2024 18:02:02 +0200 Subject: [PATCH 442/520] gog: new locators, fixes #326, #334 --- gog.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/gog.js b/gog.js index 651afa6..de5d02f 100644 --- a/gog.js +++ b/gog.js @@ -93,10 +93,9 @@ try { if (!await banner.count()) { console.log('Currently no free giveaway!'); } else { - const text = await page.locator('.giveaway-banner__title').innerText(); - const title = text.match(/Claim (.*)/)[1]; - const slug = await banner.getAttribute('href'); - const url = `https://gog.com${slug}`; + const text = await page.locator('.giveaway__content-header').innerText(); + const title = text.match(/Claim (.*) and don't miss the/)[1]; + const url = await banner.locator('a').first().getAttribute('href'); console.log(`Current free game: ${title} - ${url}`); db.data[user][title] ||= { title, time: datetime(), url }; if (cfg.dryrun) process.exit(1); From f0f142733e0fd7e365a239fbc8d737a70a739cbc Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 23 Jun 2024 19:14:13 +0200 Subject: [PATCH 443/520] ncu -u: lowdb 6 -> 7 dropped support for Node 16 --- package-lock.json | 647 ++++++++++++++++++++++++++-------------------- package.json | 12 +- src/util.js | 4 +- 3 files changed, 376 insertions(+), 287 deletions(-) diff --git a/package-lock.json b/package-lock.json index 930e7bf..3d1d878 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,16 +11,16 @@ "dependencies": { "chalk": "^5.3.0", "cross-env": "^7.0.3", - "dotenv": "^16.3.1", + "dotenv": "^16.4.5", "enquirer": "^2.4.1", - "lowdb": "^6.1.1", + "lowdb": "^7.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.40.1", + "playwright-firefox": "^1.44.1", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { - "@stylistic/eslint-plugin-js": "^1.5.1", - "eslint": "^8.56.0" + "@stylistic/eslint-plugin-js": "^2.2.2", + "eslint": "^9.5.0" }, "engines": { "node": ">=15" @@ -59,16 +59,32 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "node_modules/@eslint/config-array": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.16.0.tgz", + "integrity": "sha512-/jmuSd74i4Czf1XXn7wGRWZCuyaUZ330NH1Bek0Pplatt4Sy1S5haN21SCLLdbeKslQ+S0wEJ+++v5YibSi+Lg==", "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -76,33 +92,30 @@ "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.5.0.tgz", + "integrity": "sha512-A7+AOT2ICkodvtsWnxZP4Xxk3NbZ3VMHd8oihydLRGrJgqqdEz1qSeEgXYyT/Cu8h1TWWsQRejIx48mtjZ5y1w==", "dev": true, + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", + "node_modules/@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", "dev": true, - "dependencies": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - }, + "license": "Apache-2.0", "engines": { - "node": ">=10.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -118,11 +131,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", - "dev": true + "node_modules/@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -202,23 +223,37 @@ } }, "node_modules/@stylistic/eslint-plugin-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.5.1.tgz", - "integrity": "sha512-iZF0rF+uOhAmOJYOJx1Yvmm3CZ1uz9n0SRd9dpBYHA3QAvfABUORh9LADWwZCigjHJkp2QbCZelGFJGwGz7Siw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.2.2.tgz", + "integrity": "sha512-Vj2Q1YHVvJw+ThtOvmk5Yx7wZanVrIBRUTT89horLDb4xdP9GA1um9XOYQC6j67VeUC2gjZQnz5/RVJMzaOhtw==", "dev": true, + "license": "MIT", "dependencies": { - "acorn": "^8.11.2", - "escape-string-regexp": "^4.0.0", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1" + "@types/eslint": "^8.56.10", + "acorn": "^8.11.3", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.1" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { "eslint": ">=8.40.0" } }, + "node_modules/@stylistic/eslint-plugin-js/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@types/debug": { "version": "4.1.8", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", @@ -227,22 +262,42 @@ "@types/ms": "*" } }, + "node_modules/@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, - "node_modules/@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, "node_modules/acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true, + "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -255,6 +310,7 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, + "license": "MIT", "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } @@ -264,6 +320,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -310,7 +367,8 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "dev": true, + "license": "Python-2.0" }, "node_modules/arr-union": { "version": "3.1.0", @@ -339,6 +397,7 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -452,27 +511,16 @@ "node": ">=0.10.0" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, "funding": { - "url": "https://github.com/motdotla/dotenv?sponsor=1" + "url": "https://dotenvx.com" } }, "node_modules/enquirer": { @@ -500,41 +548,38 @@ } }, "node_modules/eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.5.0.tgz", + "integrity": "sha512-+NAOZFrW/jFTS3dASCGBxX1pkFD0/fsO+hfAkJ4TyYKwgsXZbqzrw+seCYFCcPCYXvnD67tAnglU7GQTz6kcVw==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/config-array": "^0.16.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.5.0", "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.0.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.1", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -548,23 +593,24 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", + "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -598,18 +644,45 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", + "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.11.3", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.0.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -632,6 +705,7 @@ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -653,6 +727,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -661,13 +736,15 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-levenshtein": { "version": "2.0.6", @@ -685,15 +762,16 @@ } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, + "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/find-up": { @@ -713,24 +791,25 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, + "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", - "dev": true + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", + "dev": true, + "license": "ISC" }, "node_modules/for-in": { "version": "1.0.2", @@ -801,15 +880,13 @@ } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -820,12 +897,6 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -836,10 +907,11 @@ } }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -849,6 +921,7 @@ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", "dev": true, + "license": "MIT", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -955,6 +1028,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -966,13 +1040,15 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", @@ -996,6 +1072,7 @@ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -1054,14 +1131,15 @@ "dev": true }, "node_modules/lowdb": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-6.1.1.tgz", - "integrity": "sha512-HO13FCxI8SCwfj2JRXOKgXggxnmfSc+l0aJsZ5I34X3pwzG/DPBSKyKu3Zkgg/pNmx854SVgE2la0oUeh6wzNw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz", + "integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==", + "license": "MIT", "dependencies": { - "steno": "^3.1.1" + "steno": "^4.0.2" }, "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -1192,6 +1270,7 @@ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", "dev": true, + "license": "MIT", "dependencies": { "callsites": "^3.0.0" }, @@ -1225,9 +1304,10 @@ } }, "node_modules/playwright-core": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", - "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==", + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", + "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, @@ -1236,12 +1316,13 @@ } }, "node_modules/playwright-firefox": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.40.1.tgz", - "integrity": "sha512-+C5eOWZv/CvALM0yBZFSYThvUzGKusNw6soDMhTEJwDUB5i9q/yZVFkj6I8CFXM4U6Pf1q0PXHMca70HoIwnCQ==", + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.44.1.tgz", + "integrity": "sha512-ywwHQGTLM7P5r3SzVTSyRQQUK8xsCj6MrIqY9cn8SNz+GkKL4atZb1KuYDulxrfKFzZWXLJ8M+VGc0/vNWLMfA==", "hasInstallScript": true, + "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.40.1" + "playwright-core": "1.44.1" }, "bin": { "playwright": "cli.js" @@ -1264,6 +1345,7 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -1395,6 +1477,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -1499,11 +1582,12 @@ } }, "node_modules/steno": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/steno/-/steno-3.1.1.tgz", - "integrity": "sha512-B7c6EVH7oEiaMRW36SjUnktkDwp/qd4pQiduylyiqvcZEZDeX0IIFZRBZdwO/RaVo60M0wkDwC0e8yeKaR4VGg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", + "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==", + "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/typicode" @@ -1525,6 +1609,7 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" }, @@ -1570,18 +1655,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", @@ -1595,6 +1668,7 @@ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "punycode": "^2.1.0" } @@ -1653,16 +1727,27 @@ "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", "dev": true }, + "@eslint/config-array": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.16.0.tgz", + "integrity": "sha512-/jmuSd74i4Czf1XXn7wGRWZCuyaUZ330NH1Bek0Pplatt4Sy1S5haN21SCLLdbeKslQ+S0wEJ+++v5YibSi+Lg==", + "dev": true, + "requires": { + "@eslint/object-schema": "^2.1.4", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + } + }, "@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", + "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", "dev": true, "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -1671,21 +1756,16 @@ } }, "@eslint/js": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz", - "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.5.0.tgz", + "integrity": "sha512-A7+AOT2ICkodvtsWnxZP4Xxk3NbZ3VMHd8oihydLRGrJgqqdEz1qSeEgXYyT/Cu8h1TWWsQRejIx48mtjZ5y1w==", "dev": true }, - "@humanwhocodes/config-array": { - "version": "0.11.13", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz", - "integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==", - "dev": true, - "requires": { - "@humanwhocodes/object-schema": "^2.0.1", - "debug": "^4.1.1", - "minimatch": "^3.0.5" - } + "@eslint/object-schema": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", + "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "dev": true }, "@humanwhocodes/module-importer": { "version": "1.0.1", @@ -1693,10 +1773,10 @@ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true }, - "@humanwhocodes/object-schema": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz", - "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==", + "@humanwhocodes/retry": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", + "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", "dev": true }, "@nodelib/fs.scandir": { @@ -1768,15 +1848,23 @@ } }, "@stylistic/eslint-plugin-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-1.5.1.tgz", - "integrity": "sha512-iZF0rF+uOhAmOJYOJx1Yvmm3CZ1uz9n0SRd9dpBYHA3QAvfABUORh9LADWwZCigjHJkp2QbCZelGFJGwGz7Siw==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.2.2.tgz", + "integrity": "sha512-Vj2Q1YHVvJw+ThtOvmk5Yx7wZanVrIBRUTT89horLDb4xdP9GA1um9XOYQC6j67VeUC2gjZQnz5/RVJMzaOhtw==", "dev": true, "requires": { - "acorn": "^8.11.2", - "escape-string-regexp": "^4.0.0", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1" + "@types/eslint": "^8.56.10", + "acorn": "^8.11.3", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.1" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true + } } }, "@types/debug": { @@ -1787,21 +1875,37 @@ "@types/ms": "*" } }, + "@types/eslint": { + "version": "8.56.10", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", + "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true + }, "@types/ms": { "version": "0.7.31", "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, - "@ungap/structured-clone": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", - "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", - "dev": true - }, "acorn": { - "version": "8.11.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz", - "integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==", + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true }, "acorn-jsx": { @@ -1947,19 +2051,10 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, "dotenv": { - "version": "16.3.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", - "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==" + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==" }, "enquirer": { "version": "2.4.1", @@ -1977,41 +2072,37 @@ "dev": true }, "eslint": { - "version": "8.56.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz", - "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==", + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.5.0.tgz", + "integrity": "sha512-+NAOZFrW/jFTS3dASCGBxX1pkFD0/fsO+hfAkJ4TyYKwgsXZbqzrw+seCYFCcPCYXvnD67tAnglU7GQTz6kcVw==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.56.0", - "@humanwhocodes/config-array": "^0.11.13", + "@eslint/config-array": "^0.16.0", + "@eslint/eslintrc": "^3.1.0", + "@eslint/js": "9.5.0", "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.0.1", + "eslint-visitor-keys": "^4.0.0", + "espree": "^10.0.1", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -2031,13 +2122,19 @@ "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } + }, + "eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true } } }, "eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", + "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -2051,14 +2148,22 @@ "dev": true }, "espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", + "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", "dev": true, "requires": { - "acorn": "^8.9.0", + "acorn": "^8.11.3", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", + "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "dev": true + } } }, "esquery": { @@ -2119,12 +2224,12 @@ } }, "file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "requires": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" } }, "find-up": { @@ -2138,20 +2243,19 @@ } }, "flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "requires": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" } }, "flatted": { - "version": "3.2.9", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", - "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, "for-in": { @@ -2205,25 +2309,16 @@ } }, "globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "requires": { - "type-fest": "^0.20.2" - } + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, - "graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true - }, "has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2231,9 +2326,9 @@ "dev": true }, "ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true }, "import-fresh": { @@ -2399,11 +2494,11 @@ "dev": true }, "lowdb": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-6.1.1.tgz", - "integrity": "sha512-HO13FCxI8SCwfj2JRXOKgXggxnmfSc+l0aJsZ5I34X3pwzG/DPBSKyKu3Zkgg/pNmx854SVgE2la0oUeh6wzNw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/lowdb/-/lowdb-7.0.1.tgz", + "integrity": "sha512-neJAj8GwF0e8EpycYIDFqEPcx9Qz4GUho20jWFR7YiFeXzF1YMLdxB36PypcTSPMA+4+LvgyMacYhlr18Zlymw==", "requires": { - "steno": "^3.1.1" + "steno": "^4.0.2" } }, "merge-deep": { @@ -2527,16 +2622,16 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright-core": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.40.1.tgz", - "integrity": "sha512-+hkOycxPiV534c4HhpfX6yrlawqVUzITRKwHAmYfmsVreltEl6fAZJ3DPfLMOODw0H3s1Itd6MDCWmP1fl/QvQ==" + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", + "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==" }, "playwright-firefox": { - "version": "1.40.1", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.40.1.tgz", - "integrity": "sha512-+C5eOWZv/CvALM0yBZFSYThvUzGKusNw6soDMhTEJwDUB5i9q/yZVFkj6I8CFXM4U6Pf1q0PXHMca70HoIwnCQ==", + "version": "1.44.1", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.44.1.tgz", + "integrity": "sha512-ywwHQGTLM7P5r3SzVTSyRQQUK8xsCj6MrIqY9cn8SNz+GkKL4atZb1KuYDulxrfKFzZWXLJ8M+VGc0/vNWLMfA==", "requires": { - "playwright-core": "1.40.1" + "playwright-core": "1.44.1" } }, "prelude-ls": { @@ -2668,9 +2763,9 @@ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, "steno": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/steno/-/steno-3.1.1.tgz", - "integrity": "sha512-B7c6EVH7oEiaMRW36SjUnktkDwp/qd4pQiduylyiqvcZEZDeX0IIFZRBZdwO/RaVo60M0wkDwC0e8yeKaR4VGg==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", + "integrity": "sha512-yhPIQXjrlt1xv7dyPQg2P17URmXbuM5pdGkpiMB3RenprfiBlvK415Lctfe0eshk90oA7/tNq7WEiMK8RSP39A==" }, "strip-ansi": { "version": "6.0.1", @@ -2715,12 +2810,6 @@ "prelude-ls": "^1.2.1" } }, - "type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true - }, "universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", diff --git a/package.json b/package.json index 6c954cd..b2eee9f 100644 --- a/package.json +++ b/package.json @@ -17,20 +17,20 @@ }, "type": "module", "engines": { - "node": ">=15" + "node": ">=17" }, "dependencies": { "chalk": "^5.3.0", "cross-env": "^7.0.3", - "dotenv": "^16.3.1", + "dotenv": "^16.4.5", "enquirer": "^2.4.1", - "lowdb": "^6.1.1", + "lowdb": "^7.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.40.1", + "playwright-firefox": "^1.44.1", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { - "@stylistic/eslint-plugin-js": "^1.5.1", - "eslint": "^8.56.0" + "@stylistic/eslint-plugin-js": "^2.2.2", + "eslint": "^9.5.0" } } diff --git a/src/util.js b/src/util.js index bda3f78..20c2f97 100644 --- a/src/util.js +++ b/src/util.js @@ -11,8 +11,8 @@ export const dataDir = s => path.resolve(__dirname, '..', 'data', s); export const resolve = (...a) => a.length && a[0] == '0' ? null : path.resolve(...a); // json database -import { JSONPreset } from 'lowdb/node'; -export const jsonDb = (file, defaultData) => JSONPreset(dataDir(file), defaultData); +import { JSONFilePreset } from 'lowdb/node'; +export const jsonDb = (file, defaultData) => JSONFilePreset(dataDir(file), defaultData); export const delay = ms => new Promise(resolve => setTimeout(resolve, ms)); // date and time as UTC (no timezone offset) in nicely readable and sortable format, e.g., 2022-10-06 12:05:27.313 From 30b1835bdbf850eae389a0010945ca607cf61f6a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 23 Jun 2024 19:16:01 +0200 Subject: [PATCH 444/520] pg: reverse games: oldest/bottom to newest/top --- prime-gaming.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/prime-gaming.js b/prime-gaming.js index b555f67..ca3fcee 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -107,6 +107,9 @@ try { // can't use .all() since the list of elements via locator will change after click while we iterate over it const internal = await games.locator('.item-card__action:has([data-a-target="FGWPOffer"])').elementHandles(); const external = await games.locator('.item-card__action:has([data-a-target="ExternalOfferClaim"])').all(); + // bottom to top: oldest to newest games + internal.reverse(); + external.reverse(); console.log('Number of free unclaimed games (Prime Gaming):', internal.length); // claim games in internal store for (const card of internal) { From f920aa26d0d89404a97a715c14f8f7b9400beda5 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 23 Jun 2024 19:18:30 +0200 Subject: [PATCH 445/520] pg: checkTimeLeft via PG_TIMELEFT=1 --- prime-gaming.js | 19 +++++++++++++++++++ src/config.js | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index ca3fcee..d8caaeb 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -110,6 +110,23 @@ try { // bottom to top: oldest to newest games internal.reverse(); external.reverse(); + const checkTimeLeft = async url => { + // console.log(' Checking time left for game:', url); + const check = async p => { + console.log(' ', await p.locator('.availability-date').innerText()); + const dueDateOrg = await p.locator('.availability-date .tw-bold').innerText(); + const dueDate = datetime(new Date(Date.parse(dueDateOrg + ' 17:00'))); + console.log(' Due date:', dueDate); + }; + if (page.url() == url) { + await check(page); + } else { + const p = await context.newPage(); + await p.goto(url, { waitUntil: 'domcontentloaded' }); + await check(p); + p.close(); + } + }; console.log('Number of free unclaimed games (Prime Gaming):', internal.length); // claim games in internal store for (const card of internal) { @@ -118,6 +135,7 @@ try { const slug = await (await card.$('a')).getAttribute('href'); const url = 'https://gaming.amazon.com' + slug.split('?')[0]; console.log('Current free game:', title); + if (cfg.pg_timeLeft) await checkTimeLeft(url); if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; await (await card.$('.tw-button:has-text("Claim")')).click(); @@ -144,6 +162,7 @@ try { const item_text = await page.innerText('[data-a-target="DescriptionItemDetails"]'); const store = item_text.toLowerCase().replace(/.* on /, '').slice(0, -1); console.log(' External store:', store); + if (cfg.pg_timeLeft) await checkTimeLeft(url); if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; await Promise.any([page.click('[data-a-target="buy-box"] .tw-button:has-text("Get game")'), page.click('[data-a-target="buy-box"] .tw-button:has-text("Claim")'), page.click('.tw-button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")'), page.waitForSelector('.thank-you-title:has-text("Success")')]); // waits for navigation diff --git a/src/config.js b/src/config.js index bd41c7d..56e42d2 100644 --- a/src/config.js +++ b/src/config.js @@ -46,7 +46,8 @@ export const cfg = { xbox_email: process.env.XBOX_EMAIL || process.env.EMAIL, xbox_password: process.env.XBOX_PASSWORD || process.env.PASSWORD, xbox_otpkey: process.env.XBOX_OTPKEY, - // experimmental - likely to change + // experimmental pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores pg_claimdlc: process.env.PG_CLAIMDLC == '1', // prime-gaming: claim in-game content + pg_timeLeft: process.env.PG_TIMELEFT == '1', // prime-gaming: list time left to claim }; From 43bef9b23c93104345fa2e9b52c1db6ef231bbba Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 23 Jun 2024 20:44:03 +0200 Subject: [PATCH 446/520] pg: redeem microsoft store and xbox, WIP, #315, #5 --- prime-gaming.js | 43 +++++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 12 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index d8caaeb..1f20a47 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -155,6 +155,7 @@ try { // await (await card.$('text=Claim')).click(); // goes to URL of game, no need to wait external_info.push({ title, url }); } + // external_info = [ { title: 'Fallout 76 (XBOX)', url: 'https://gaming.amazon.com/fallout-76-xbox-fgwp/dp/amzn1.pg.item.9fe17d7b-b6c2-4f58-b494-cc4e79528d0b?ingress=amzn&ref_=SM_Fallout76XBOX_S01_FGWP_CRWN' } ]; for (const { title, url } of external_info) { console.log('Current free game:', title); // , url); await page.goto(url, { waitUntil: 'domcontentloaded' }); @@ -185,7 +186,8 @@ try { const redeem = { // 'origin': 'https://www.origin.com/redeem', // TODO still needed or now only via account linking? 'gog.com': 'https://www.gog.com/redeem', - 'microsoft games': 'https://redeem.microsoft.com', + 'microsoft store': 'https://account.microsoft.com/billing/redeem', + xbox: 'https://account.microsoft.com/billing/redeem', 'legacy games': 'https://www.legacygames.com/primedeal', }; if (store in redeem) { // did not work for linked origin: && !await page.locator('div:has-text("Successfully Claimed")').count() @@ -240,27 +242,44 @@ try { console.log(' Unknown Response 2 - please report in https://github.com/vogler/free-games-claimer/issues/5'); } } - } else if (store == 'microsoft games') { - console.error(` Redeem on ${store} not yet implemented!`); + } else if (store == 'microsoft store' || store == 'xbox') { + console.error(` Redeem on ${store} is experimental!`); + // await page2.pause(); if (page2.url().startsWith('https://login.')) { - console.error(' Not logged in! Use the browser to login manually.'); + console.error(' Not logged in! Use the browser to login manually. Waiting for 60s.'); + await page2.waitForTimeout(60 * 1000); redeem_action = 'redeem (login)'; } else { - const r = page2.waitForResponse(r => r.url().startsWith('https://purchase.mp.microsoft.com/')); - await page2.fill('[name=tokenString]', code); + const iframe = page2.frameLocator('#redeem-iframe'); + const input = iframe.locator('[name=tokenString]'); + await input.waitFor(); + await input.fill(code); + const r = page2.waitForResponse(r => r.url().startsWith('https://cart.production.store-web.dynamics.com/v1.0/Redeem/PrepareRedeem')); // console.log(await page2.locator('.redeem_code_error').innerText()); const rt = await (await r).text(); - console.debug(` Response: ${rt}`); // {"code":"NotFound","data":[],"details":[],"innererror":{"code":"TokenNotFound",... - const reason = JSON.parse(rt).code; - if (reason == 'NotFound') { + const j = JSON.parse(rt); + const reason = j?.events?.cart.length && j.events.cart[0]?.data?.reason; + if (reason == 'TokenNotFound') { redeem_action = 'redeem (not found)'; console.error(' Code was not found!'); + } else if (j?.productInfos?.length && j.productInfos[0]?.redeemable) { + await iframe.locator('button:has-text("Next")').click(); + await iframe.locator('button:has-text("Confirm")').click(); + const r = page2.waitForResponse(r => r.url().startsWith('https://cart.production.store-web.dynamics.com/v1.0/Redeem/RedeemToken')); + const j = JSON.parse(await (await r).text()); + if (j?.events?.cart.length && j.events.cart[0]?.data?.reason == 'UserAlreadyOwnsContent') { + redeem_action = 'already redeemed'; + console.error(' error: UserAlreadyOwnsContent'); + } else if (true) { // TODO what's returned on success? + redeem_action = 'redeemed'; + db.data[user][title].status = 'claimed and redeemed?'; + console.log(' Redeemed successfully? Please report if not in https://github.com/vogler/free-games-claimer/issues/5'); + } } else { // TODO find out other responses - await page2.click('#nextButton'); - redeem_action = 'redeemed?'; + redeem_action = 'unknown'; + console.debug(` Response: ${rt}`); console.log(' Redeemed successfully? Please report your Response from above (if it is new) in https://github.com/vogler/free-games-claimer/issues/5'); - db.data[user][title].status = 'claimed and redeemed?'; } } } else if (store == 'legacy games') { From 601e893714731767ca5ce7ef6ca46c1e3330ed60 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 23 Jun 2024 20:50:00 +0200 Subject: [PATCH 447/520] README: link example how to set env vars on Windows, closes #314 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a6e3825..a2da668 100644 --- a/README.md +++ b/README.md @@ -103,7 +103,7 @@ You can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@ If you are using [docker compose](https://docs.docker.com/compose/environment-variables/) (or Portainer etc.), you can put options in the `environment:` section. ##### Without Docker -On Linux/macOS you can prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. +On Linux/macOS you can prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. On Windows you have to use `set`, [example](https://github.com/vogler/free-games-claimer/issues/314). You can also put options in `data/config.env` which will be loaded by [dotenv](https://github.com/motdotla/dotenv). ### Notifications From 076738e3012ff9f90b75e9c6161f63314c96d6a1 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 25 Jun 2024 13:09:10 +0200 Subject: [PATCH 448/520] gog: fail if WIDTH<1280 due to hidden username, closes #335 --- gog.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/gog.js b/gog.js index de5d02f..40d8fad 100644 --- a/gog.js +++ b/gog.js @@ -10,6 +10,11 @@ console.log(datetime(), 'started checking gog'); const db = await jsonDb('gog.json', {}); +if (cfg.width < 1280) { // otherwise 'Sign in' and #menuUsername are hidden (but attached to DOM), see https://github.com/vogler/free-games-claimer/issues/335 + console.error(`Window width is set to ${cfg.width} but needs to be at least 1280 for GOG!`); + process.exit(1); +} + // https://playwright.dev/docs/auth#multi-factor-authentication const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, From c0eb6dbb0bb6c64e268ae70b9e37586c92a59366 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 25 Jun 2024 13:24:55 +0200 Subject: [PATCH 449/520] ncu -u: playwright 1.44.1 -> 1.45.0 --- package-lock.json | 36 ++++++++++++++++++------------------ package.json | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3d1d878..e6f66b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "enquirer": "^2.4.1", "lowdb": "^7.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.44.1", + "playwright-firefox": "^1.45.0", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { @@ -23,7 +23,7 @@ "eslint": "^9.5.0" }, "engines": { - "node": ">=15" + "node": ">=17" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1304,31 +1304,31 @@ } }, "node_modules/playwright-core": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", - "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==", + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", + "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/playwright-firefox": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.44.1.tgz", - "integrity": "sha512-ywwHQGTLM7P5r3SzVTSyRQQUK8xsCj6MrIqY9cn8SNz+GkKL4atZb1KuYDulxrfKFzZWXLJ8M+VGc0/vNWLMfA==", + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.45.0.tgz", + "integrity": "sha512-JmGESfFR8xTjAYQzECYO00yBbSSnu4dBImsrmJVeOXTvT+i9p1dpVUaxKz6lTFMI/xzYROqB4E4Km8NBiOgslw==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.44.1" + "playwright-core": "1.45.0" }, "bin": { "playwright": "cli.js" }, "engines": { - "node": ">=16" + "node": ">=18" } }, "node_modules/prelude-ls": { @@ -2622,16 +2622,16 @@ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, "playwright-core": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.44.1.tgz", - "integrity": "sha512-wh0JWtYTrhv1+OSsLPgFzGzt67Y7BE/ZS3jEqgGBlp2ppp1ZDj8c+9IARNW4dwf1poq5MgHreEM2KV/GuR4cFA==" + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", + "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==" }, "playwright-firefox": { - "version": "1.44.1", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.44.1.tgz", - "integrity": "sha512-ywwHQGTLM7P5r3SzVTSyRQQUK8xsCj6MrIqY9cn8SNz+GkKL4atZb1KuYDulxrfKFzZWXLJ8M+VGc0/vNWLMfA==", + "version": "1.45.0", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.45.0.tgz", + "integrity": "sha512-JmGESfFR8xTjAYQzECYO00yBbSSnu4dBImsrmJVeOXTvT+i9p1dpVUaxKz6lTFMI/xzYROqB4E4Km8NBiOgslw==", "requires": { - "playwright-core": "1.44.1" + "playwright-core": "1.45.0" } }, "prelude-ls": { diff --git a/package.json b/package.json index b2eee9f..2c64f46 100644 --- a/package.json +++ b/package.json @@ -26,7 +26,7 @@ "enquirer": "^2.4.1", "lowdb": "^7.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.44.1", + "playwright-firefox": "^1.45.0", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { From 55226933c0eb01a0f0b258732e1bb3fc67849200 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 25 Jun 2024 15:30:02 +0200 Subject: [PATCH 450/520] filenamify datetime recordHar for Windows : -> ., fix #336 --- epic-games.js | 2 +- gog.js | 2 +- prime-gaming.js | 2 +- unrealengine.js | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/epic-games.js b/epic-games.js index 54ca597..5487445 100644 --- a/epic-games.js +++ b/epic-games.js @@ -25,7 +25,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { // 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 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-${datetime()}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools + 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 diff --git a/gog.js b/gog.js index 40d8fad..0688626 100644 --- a/gog.js +++ b/gog.js @@ -21,7 +21,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { viewport: { width: cfg.width, height: cfg.height }, locale: 'en-US', // ignore OS locale to be sure to have english text for locators -> done via /en in URL 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/gog-${datetime()}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools + recordHar: cfg.record ? { path: `data/record/gog-${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 }); diff --git a/prime-gaming.js b/prime-gaming.js index 6e8cb03..91d3fe2 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -19,7 +19,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { viewport: { width: cfg.width, height: cfg.height }, 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/pg-${datetime()}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools + recordHar: cfg.record ? { path: `data/record/pg-${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 }); diff --git a/unrealengine.js b/unrealengine.js index e67633e..2bb8ee9 100644 --- a/unrealengine.js +++ b/unrealengine.js @@ -25,7 +25,7 @@ const context = await firefox.launchPersistentContext(cfg.dir.browser, { // userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0 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/ue-${datetime()}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools + recordHar: cfg.record ? { path: `data/record/ue-${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 }); From 9cf5d2f7f21157566dad636b75febb831d14b333 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 25 Jun 2024 15:50:02 +0200 Subject: [PATCH 451/520] pg: include code in redeem_url for gog, closes #330 --- prime-gaming.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 91d3fe2..5e3e283 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -196,7 +196,9 @@ try { if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); // full text: Click here to enter your redemption code. } - console.log(' URL to redeem game:', redeem[store]); + let redeem_url = redeem[store]; + if (store == 'gog.com') redeem_url += '/' + code; // to log and notify, but can't use for goto below (captcha) + console.log(' URL to redeem game:', redeem_url); db.data[user][title].code = code; let redeem_action = 'redeem'; if (cfg.pg_redeem) { // try to redeem keys on external stores @@ -305,7 +307,7 @@ try { if (cfg.debug) await page2.pause(); await page2.close(); } - notify_game.status = `${redeem_action} ${code} on ${store}`; + notify_game.status = `${redeem_action} ${code} on ${store}`; } else { notify_game.status = `claimed on ${store}`; db.data[user][title].status = 'claimed'; From c4be7ece21702fb73bc7ed61bafd7abc6133a911 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 25 Jun 2024 18:45:21 +0200 Subject: [PATCH 452/520] xbox: fixup #307 --- xbox.js | 1 - 1 file changed, 1 deletion(-) diff --git a/xbox.js b/xbox.js index 67054ef..fd78c66 100644 --- a/xbox.js +++ b/xbox.js @@ -16,7 +16,6 @@ const URL_CLAIM = 'https://www.xbox.com/en-US/live/gold'; // #gameswithgold"; console.log(datetime(), 'started checking xbox'); const db = await jsonDb('xbox.json', {}); -db.data ||= {}; handleSIGINT(); From 9af36e902c0634d8fc19fdb8a75267b0887ccf47 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 26 Jun 2024 17:10:04 +0200 Subject: [PATCH 453/520] eg: fix login: password now together with email again, detect login error, closes #338, closes #337 --- epic-games.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/epic-games.js b/epic-games.js index 5487445..70807b8 100644 --- a/epic-games.js +++ b/epic-games.js @@ -20,7 +20,7 @@ if (cfg.time) console.time('startup'); const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, viewport: { width: cfg.width, height: cfg.height }, - userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated? + 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 @@ -90,8 +90,6 @@ try { if (!email) await notifyBrowserLogin(); else { // await page.click('text=Sign in with Epic Games'); - await page.fill('#email', email); - await page.click('button[type="submit"]'); page.waitForSelector('.h_captcha_challenge iframe').then(async () => { console.error('Got a captcha during login (likely due to too many attempts)! You may solve it in the browser, get a new IP or try again in a few hours.'); await notify('epic-games: got captcha during login. Please check.'); @@ -99,10 +97,20 @@ try { page.waitForSelector('p:has-text("Incorrect response.")').then(async () => { console.error('Incorrect repsonse for captcha!'); }).catch(_ => { }); + await page.fill('#email', email); + // await page.click('button[type="submit"]'); login was split in two steps for some time, now email and password are on the same form again const password = email && (cfg.eg_password || await prompt({ type: 'password', message: 'Enter password' })); if (!password) await notifyBrowserLogin(); - await page.fill('#password', password); - await page.click('button[type="submit"]'); + else { + await page.fill('#password', password); + await page.click('button[type="submit"]'); + } + const error = page.locator('#form-error-message'); + error.waitFor().then(async () => { + console.error('Login error:', await error.innerText()); + await context.close(); // finishes potential recording + process.exit(1); + }).catch(_ => { }); // handle MFA, but don't await it page.waitForURL('**/id/login/mfa**').then(async () => { console.log('Enter the security code to continue - This appears to be a new device, browser or location. A security code has been sent to your email address at ...'); From d85cd8d20ccb27834bfaafd98f8be95fecb7a3d5 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Jun 2024 10:54:46 +0200 Subject: [PATCH 454/520] rm xbox.js, no more games with gold, closes #286, closes #339 TODO integrate login code for microsoft games in prime-gaming --- src/config.js | 4 - xbox.js | 250 -------------------------------------------------- 2 files changed, 254 deletions(-) delete mode 100644 xbox.js diff --git a/src/config.js b/src/config.js index 9624471..03414a0 100644 --- a/src/config.js +++ b/src/config.js @@ -42,10 +42,6 @@ export const cfg = { gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD, gog_newsletter: process.env.GOG_NEWSLETTER == '1', // do not unsubscribe from newsletter after claiming a game // OTP only via GOG_EMAIL, can't add app... - // auth xbox - xbox_email: process.env.XBOX_EMAIL || process.env.EMAIL, - xbox_password: process.env.XBOX_PASSWORD || process.env.PASSWORD, - xbox_otpkey: process.env.XBOX_OTPKEY, // experimmental pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores lg_email: process.env.LG_EMAIL || process.env.PG_EMAIL || process.env.EMAIL, // prime-gaming: external: legacy-games: email to use for redeeming diff --git a/xbox.js b/xbox.js deleted file mode 100644 index fd78c66..0000000 --- a/xbox.js +++ /dev/null @@ -1,250 +0,0 @@ -import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra -import { authenticator } from 'otplib'; -import { - datetime, - handleSIGINT, - html_game_list, - jsonDb, - notify, - prompt, -} from './src/util.js'; -import { cfg } from './src/config.js'; - -// ### SETUP -const URL_CLAIM = 'https://www.xbox.com/en-US/live/gold'; // #gameswithgold"; - -console.log(datetime(), 'started checking xbox'); - -const db = await jsonDb('xbox.json', {}); - -handleSIGINT(); - -// https://playwright.dev/docs/auth#multi-factor-authentication -const context = await firefox.launchPersistentContext(cfg.dir.browser, { - headless: cfg.headless, - viewport: { width: cfg.width, height: cfg.height }, - locale: 'en-US', // ignore OS locale to be sure to have english text for locators -> done via /en in URL -}); - -if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); - -const page = context.pages().length - ? context.pages()[0] - : await context.newPage(); // should always exist -await page.setViewportSize({ width: cfg.width, height: cfg.height }); // TODO workaround for https://github.com/vogler/free-games-claimer/issues/277 until Playwright fixes it - -const notify_games = []; -let user; - -main(); - -async function main() { - try { - await performLogin(); - await getAndSaveUser(); - await redeemFreeGames(); - } catch (error) { - console.error(error); - process.exitCode ||= 1; - if (error.message && process.exitCode != 130) notify(`xbox failed: ${error.message.split('\n')[0]}`); - } finally { - await db.write(); // write out json db - if (notify_games.filter(g => g.status != 'existed').length) { - // don't notify if all were already claimed - notify(`xbox (${user}):
${html_game_list(notify_games)}`); - } - await context.close(); - } -} - -async function performLogin() { - await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever - - const signInLocator = page - .getByRole('link', { - name: 'Sign in to your account', - }) - .first(); - const usernameLocator = page - .getByRole('button', { - name: 'Account manager for', - }) - .first(); - - await Promise.any([signInLocator.waitFor(), usernameLocator.waitFor()]); - - if (await usernameLocator.isVisible()) { - return; // logged in using saved cookie - } else if (await signInLocator.isVisible()) { - console.error('Not signed in anymore.'); - await signInLocator.click(); - await signInToXbox(); - } else { - console.error('lost! where am i?'); - } -} - -async function signInToXbox() { - page.waitForLoadState('domcontentloaded'); - if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in - console.info(`Login timeout is ${cfg.login_timeout / 1000} seconds!`); - - // ### FETCH EMAIL/PASS - if (cfg.xbox_email && cfg.xbox_password) console.info('Using email and password from environment.'); - else console.info( - 'Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).', - ); - const email = cfg.xbox_email || await prompt({ message: 'Enter email' }); - const password = - email && - (cfg.xbox_password || - await prompt({ - type: 'password', - message: 'Enter password', - })); - // ### FILL IN EMAIL/PASS - if (email && password) { - const usernameLocator = page - .getByPlaceholder('Email, phone, or Skype') - .first(); - const passwordLocator = page.getByPlaceholder('Password').first(); - - await Promise.any([ - usernameLocator.waitFor(), - passwordLocator.waitFor(), - ]); - - // username may already be saved from before, if so, skip to filling in password - if (await page.getByPlaceholder('Email, phone, or Skype').isVisible()) { - await usernameLocator.fill(email); - await page.getByRole('button', { name: 'Next' }).click(); - } - - await passwordLocator.fill(password); - await page.getByRole('button', { name: 'Sign in' }).click(); - - // handle MFA, but don't await it - page.locator('input[name="otc"]') - .waitFor() - .then(async () => { - console.log('Two-Step Verification - Enter security code'); - console.log( - await page - .locator('div[data-bind="text: description"]') - .innerText(), - ); - const otp = - cfg.xbox_otpkey && - authenticator.generate(cfg.xbox_otpkey) || - await prompt({ - type: 'text', - message: 'Enter two-factor sign in code', - validate: n => n.toString().length == 6 || - 'The code must be 6 digits!', - }); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them - await page.type('input[name="otc"]', otp.toString()); - await page - .getByLabel('Don\'t ask me again on this device') - .check(); // Trust this Browser - await page.getByRole('button', { name: 'Verify' }).click(); - }) - .catch(_ => {}); - - // Trust this browser, but don't await it - page.getByLabel('Don\'t show this again') - .waitFor() - .then(async () => { - await page.getByLabel('Don\'t show this again').check(); - await page.getByRole('button', { name: 'Yes' }).click(); - }) - .catch(_ => {}); - } else { - console.log('Waiting for you to login in the browser.'); - await notify( - 'xbox: no longer signed in and not enough options set for automatic login.', - ); - if (cfg.headless) { - console.log( - 'Run `SHOW=1 node xbox` to login in the opened browser.', - ); - await context.close(); - process.exit(1); - } - } - - // ### VERIFY SIGNED IN - await page.waitForURL(`${URL_CLAIM}**`); - - if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); -} - -async function getAndSaveUser() { - user = await page.locator('#mectrl_currentAccount_primary').innerHTML(); - console.log(`Signed in as '${user}'`); - db.data[user] ||= {}; -} - -async function redeemFreeGames() { - const monthlyGamesLocator = await page.locator('.f-size-large').all(); - - const monthlyGamesPageLinks = await Promise.all( - monthlyGamesLocator.map( - async el => await el.locator('a').getAttribute('href'), - ), - ); - console.log('Free games:', monthlyGamesPageLinks); - - for (const url of monthlyGamesPageLinks) { - await page.goto(url); - - const title = await page.locator('h1').first().innerText(); - const game_id = page.url().split('/').pop(); - db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! - console.log('Current free game:', title); - const notify_game = { title, url, status: 'failed' }; - notify_games.push(notify_game); // status is updated below - - // SELECTORS - const getBtnLocator = page.getByText('GET', { exact: true }).first(); - const installToLocator = page - .getByText('INSTALL TO', { exact: true }) - .first(); - - await Promise.any([ - getBtnLocator.waitFor(), - installToLocator.waitFor(), - ]); - - if (await installToLocator.isVisible()) { - console.log(' Already in library! Nothing to claim.'); - notify_game.status = 'existed'; - db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed - } else if (await getBtnLocator.isVisible()) { - console.log(' Not in library yet! Click GET.'); - await getBtnLocator.click(); - - // wait for popup - await page - .locator('iframe[name="purchase-sdk-hosted-iframe"]') - .waitFor(); - const popupLocator = page.frameLocator( - '[name=purchase-sdk-hosted-iframe]', - ); - - const finalGetBtnLocator = popupLocator.getByText('GET'); - await finalGetBtnLocator.waitFor(); - await finalGetBtnLocator.click(); - - await page.getByText('Thank you for your purchase.').waitFor(); - notify_game.status = 'claimed'; - db.data[user][game_id].status = 'claimed'; - db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time - console.log(' Claimed successfully!'); - } - - // notify_game.status = db.data[user][game_id].status; // claimed or failed - - // const p = path.resolve(cfg.dir.screenshots, playstation-plus', `${game_id}.png`); - // if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long... - } -} From e154b74e4d3a927f230d39fd63b374f92f37f7ef Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Jun 2024 14:09:37 +0200 Subject: [PATCH 455/520] eg: TODO handle base game for add-ons via function --- epic-games.js | 1 + 1 file changed, 1 insertion(+) diff --git a/epic-games.js b/epic-games.js index 70807b8..9aeb6ab 100644 --- a/epic-games.js +++ b/epic-games.js @@ -193,6 +193,7 @@ try { const baseUrl = 'https://store.epicgames.com' + await page.locator('a:has-text("Overview")').getAttribute('href'); console.log(' Base game:', baseUrl); // await page.click('a:has-text("Overview")'); + // TODO handle this via function call for base game above since this will never terminate if DRYRUN=1 urls.push(baseUrl); // add base game to the list of games to claim urls.push(url); // add add-on itself again } else { // GET From 00275d825baa6f7ddc04f74b091b4639e7948212 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Jun 2024 15:40:45 +0200 Subject: [PATCH 456/520] eg: forgot to catch timeout for captcha detection --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 9aeb6ab..04a091e 100644 --- a/epic-games.js +++ b/epic-games.js @@ -263,7 +263,7 @@ try { iframe.locator('.payment__errors:has-text("Failed to challenge captcha, please try again later.")').waitFor().then(async () => { console.error(' Failed to challenge captcha, please try again later.'); await notify('epic-games: failed to challenge captcha. Please check.'); - }); + }).catch(_ => { }); await page.locator('text=Thanks for your order!').waitFor({ state: 'attached' }); // TODO Bundle: got stuck here db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time From aee72327eaf06600ae43c80799188b58d9074ddf Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Jun 2024 15:41:58 +0200 Subject: [PATCH 457/520] eg: disable webgl since it leaks running virtualized, #183 --- epic-games.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 04a091e..d0b9a03 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,7 +1,7 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { authenticator } from 'otplib'; import path from 'path'; -import { existsSync, writeFileSync } from 'fs'; +import { existsSync, writeFileSync, appendFileSync } from 'fs'; import { resolve, jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './src/util.js'; import { cfg } from './src/config.js'; @@ -16,6 +16,14 @@ 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, { headless: cfg.headless, From 17f1ee41c975e1dc9d8a7e5abef254a17bf07349 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Jun 2024 23:32:15 +0200 Subject: [PATCH 458/520] eg: don't exit on login error since it may be 'Incorrect response' for captcha https://github.com/vogler/free-games-claimer/issues/183#issuecomment-2195691372 --- epic-games.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index d0b9a03..9d492a0 100644 --- a/epic-games.js +++ b/epic-games.js @@ -103,7 +103,7 @@ try { await notify('epic-games: got captcha during login. Please check.'); }).catch(_ => { }); page.waitForSelector('p:has-text("Incorrect response.")').then(async () => { - console.error('Incorrect repsonse for captcha!'); + console.error('Incorrect response for captcha!'); }).catch(_ => { }); await page.fill('#email', email); // await page.click('button[type="submit"]'); login was split in two steps for some time, now email and password are on the same form again @@ -116,8 +116,7 @@ try { const error = page.locator('#form-error-message'); error.waitFor().then(async () => { console.error('Login error:', await error.innerText()); - await context.close(); // finishes potential recording - process.exit(1); + console.log('Please login in the browser!'); }).catch(_ => { }); // handle MFA, but don't await it page.waitForURL('**/id/login/mfa**').then(async () => { From b8f7068a873d47ccc3c657c2c82aa1bc718cc455 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 10 Jul 2024 13:27:48 +0200 Subject: [PATCH 459/520] pg: scrollUntilStable to wait in loop to load all games since one scroll may not be enough --- prime-gaming.js | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 5e3e283..eeface0 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -97,12 +97,25 @@ try { process.exit(1); } + const waitUntilStable = async (f, act) => { + let v; + while (true) { + const v2 = await f(); + if (v == v2) break; + v = v2; + await act(); + } + }; + const scrollUntilStable = async f => waitUntilStable(f, async () => { + await page.keyboard.press('End'); // scroll to bottom to show all games + await page.waitForLoadState('networkidle'); // wait for all games to be loaded + await page.waitForTimeout(2000); // TODO networkidle wasn't enough to load all already collected games + }); + await page.click('button[data-type="Game"]'); - await page.keyboard.press('End'); // scroll to bottom to show all games - await page.waitForLoadState('networkidle'); // wait for all games to be loaded - await page.waitForTimeout(2000); // TODO networkidle wasn't enough to load all already collected games const games = page.locator('div[data-a-target="offer-list-FGWP_FULL"]'); await games.waitFor(); + await scrollUntilStable(() => games.locator('.item-card__action').count()); console.log('Number of already claimed games (total):', await games.locator('p:has-text("Collected")').count()); // can't use .all() since the list of elements via locator will change after click while we iterate over it const internal = await games.locator('.item-card__action:has([data-a-target="FGWPOffer"])').elementHandles(); @@ -324,8 +337,7 @@ try { if (notify_games.length) { // make screenshot of all games if something was claimed const p = screenshot(`${filenamify(datetime())}.png`); // await page.screenshot({ path: p, fullPage: true }); // fullPage does not make a difference since scroll not on body but on some element - await page.keyboard.press('End'); // scroll to bottom to show all games - await page.waitForTimeout(1000); // wait for fade in animation + await scrollUntilStable(() => games.locator('.item-card__action').count()); const viewportSize = page.viewportSize(); // current viewport size await page.setViewportSize({ ...viewportSize, height: 3000 }); // increase height, otherwise element screenshot is cut off at the top and bottom await games.screenshot({ path: p }); // screenshot of all claimed games @@ -339,17 +351,7 @@ try { await loot.waitFor(); process.stdout.write('Loading all DLCs on page...'); - let n1 = 0; - let n2 = 0; - do { - n1 = n2; - n2 = await loot.locator('[data-a-target="item-card"]').count(); - // console.log(n2); - process.stdout.write(` ${n2}`); - await page.keyboard.press('End'); // scroll to bottom to show all dlcs - await page.waitForLoadState('networkidle'); // did not wait for dlcs to be loaded - await page.waitForTimeout(1000); - } while (n2 > n1); + scrollUntilStable(() => loot.locator('[data-a-target="item-card"]').count()) console.log('\nNumber of already claimed DLC:', await loot.locator('p:has-text("Collected")').count()); From a39d737999c509b9065ee07dd7a806e6579ff5e6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 19 Jul 2024 14:35:40 +0200 Subject: [PATCH 460/520] aliexpress: collect daily coins via desktop website All load in webview in android app. Other games have no desktop version and mobile version uses canvas and only refers to download app with both stealth and fingerprint-injector. Also stuck on loading screen when using firefox instead of chrome. --- aliexpress.js | 105 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/config.js | 3 ++ 2 files changed, 108 insertions(+) create mode 100644 aliexpress.js diff --git a/aliexpress.js b/aliexpress.js new file mode 100644 index 0000000..c44c673 --- /dev/null +++ b/aliexpress.js @@ -0,0 +1,105 @@ +import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra +import { datetime, filenamify, prompt, handleSIGINT, stealth } from './src/util.js'; +import { cfg } from './src/config.js'; + +const context = await firefox.launchPersistentContext(cfg.dir.browser, { + headless: cfg.headless, + viewport: { width: cfg.width, height: cfg.height }, + locale: 'en-US', // ignore OS locale to be sure to have english text for locators -> done via /en in URL + 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/gog-${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 +}); +handleSIGINT(context); +await stealth(context); + +context.setDefaultTimeout(cfg.debug ? 0 : cfg.timeout); + +const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist + +const auth = async (url) => { + console.log('auth', url); + await page.goto(url, { waitUntil: 'domcontentloaded' }); + // redirects to https://login.aliexpress.com/?return_url=https%3A%2F%2Fwww.aliexpress.com%2Fp%2Fcoin-pc-index%2Findex.html + await Promise.any([page.waitForURL(/.*login.aliexpress.com.*/).then(async () => { + // manual login + console.error('Not logged in! Will wait for 120s for you to login...'); + // await page.waitForTimeout(120*1000); + // or try automated + page.locator('span:has-text("Switch account")').click().catch(_ => {}); // sometimes no longer logged in, but previous user/email is pre-selected -> in this case we want to go back to the classic login + const login = page.locator('.login-container'); + const email = cfg.ae_email || await prompt({ message: 'Enter email' }); + const emailInput = login.locator('input[label="Email or phone number"]'); + await emailInput.fill(email); + await emailInput.blur(); // otherwise Continue button stays disabled + const continueButton = login.locator('button:has-text("Continue")'); + await continueButton.click({ force: true }); // normal click waits for button to no longer be covered by their suggestion menu, so we have to force click somewhere for the menu to close and then click + await continueButton.click(); + const password = email && (cfg.ae_password || await prompt({ type: 'password', message: 'Enter password' })); + await login.locator('input[label="Password"]').fill(password); + await login.locator('button:has-text("Sign in")').click(); + const error = login.locator('.error-text'); + error.waitFor().then(async _ => console.error('Login error:', await error.innerText())); + await page.waitForURL(url); + // await page.addLocatorHandler(page.getByRole('button', { name: 'Accept cookies' }), btn => btn.click()); + page.getByRole('button', { name: 'Accept cookies' }).click().then(_ => console.log('Accepted cookies')).catch(_ => { }); + }), page.locator('#nav-user-account').waitFor()]).catch(_ => {}); + + // await page.locator('#nav-user-account').hover(); + // console.log('Logged in as:', await page.locator('.welcome-name').innerText()); +}; + +// copied URLs from AliExpress app on tablet which has menu for the used webview +const urls = { + // works with desktop view, but stuck at 100% loading in mobile view: + coins: 'https://www.aliexpress.com/p/coin-pc-index/index.html', + // only work with mobile view: + grow: 'https://m.aliexpress.com/p/ae_fruit/index.html', // firefox: stuck at 60% loading, chrome: loads, but canvas + gogo: 'https://m.aliexpress.com/p/gogo-match-cc/index.html', // closes firefox?! + // only show notification to install the app + euro: 'https://m.aliexpress.com/p/european-cup/index.html', // doesn't load + merge: 'https://m.aliexpress.com/p/merge-market/index.html', +}; + +const coins = async () => { + // await auth(urls.coins); + await Promise.any([page.locator('.checkin-button').click(), page.locator('.addcoin').waitFor()]); + console.log('Coins:', await page.locator('.mycoin-content-right-money').innerText()); + console.log('Streak:', await page.locator('.title-box').innerText()); + console.log('Tomorrow:', await page.locator('.addcoin').innerText()); +}; + +const grow = async () => { + await page.pause(); +}; + +const gogo = async () => { + await page.pause(); +}; + +const euro = async () => { + await page.pause(); +}; + +const merge = async () => { + await page.pause(); +}; + +try { + // await coins(); + await [ + coins, + // grow, + // gogo, + // euro, + // merge, + ].reduce((a, f) => a.then(async _ => { await auth(urls[f.name]); await f(); console.log() }), Promise.resolve()); + + // await page.pause(); +} catch (error) { + process.exitCode ||= 1; + console.error('--- Exception:'); + console.error(error); // .toString()? +} +if (page.video()) console.log('Recorded video:', await page.video().path()); +await context.close(); diff --git a/src/config.js b/src/config.js index 03414a0..4a384b8 100644 --- a/src/config.js +++ b/src/config.js @@ -41,6 +41,9 @@ export const cfg = { gog_email: process.env.GOG_EMAIL || process.env.EMAIL, gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD, gog_newsletter: process.env.GOG_NEWSLETTER == '1', // do not unsubscribe from newsletter after claiming a game + // auth AliExpress + ae_email: process.env.AE_EMAIL || process.env.EMAIL, + ae_password: process.env.AE_PASSWORD || process.env.PASSWORD, // OTP only via GOG_EMAIL, can't add app... // experimmental pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores From d9e91d22c9f01114f905b8edf6e7322bb56639be Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 1 Aug 2024 15:46:41 +0200 Subject: [PATCH 461/520] eg: fix changed button locators --- epic-games.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/epic-games.js b/epic-games.js index 9d492a0..ff8f3e0 100644 --- a/epic-games.js +++ b/epic-games.js @@ -156,7 +156,8 @@ try { for (const url of urls) { if (cfg.time) console.time('claim game'); await page.goto(url); // , { waitUntil: 'domcontentloaded' }); - const btnText = await page.locator('//button[@data-testid="purchase-cta-button"][not(contains(.,"Loading"))]').first().innerText(); // barrier to block until page is loaded + const purcahseBtn = page.locator('aside button').first(); + const btnText = (await purcahseBtn.innerText()).toLowerCase(); // barrier to block until page is loaded // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { @@ -187,12 +188,12 @@ try { const notify_game = { title, url, status: 'failed' }; notify_games.push(notify_game); // status is updated below - if (btnText.toLowerCase() == 'in library') { + if (btnText == 'in library') { console.log(' Already in library! Nothing to claim.'); notify_game.status = 'existed'; db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed if (db.data[user][game_id].status.startsWith('failed')) db.data[user][game_id].status = 'manual'; // was failed but now it's claimed - } else if (btnText.toLowerCase() == 'requires base game') { + } else if (btnText == 'requires base game') { console.log(' Requires base game! Nothing to claim.'); notify_game.status = 'requires base game'; db.data[user][game_id].status ||= 'failed:requires-base-game'; @@ -205,7 +206,7 @@ try { urls.push(url); // add add-on itself again } else { // GET console.log(' Not in library yet! Click GET.'); - await page.click('[data-testid="purchase-cta-button"]', { delay: 11 }); // got stuck here without delay (or mouse move), see #75, 1ms was also enough + await purcahseBtn.click({ delay: 11 }); // got stuck here without delay (or mouse move), see #75, 1ms was also enough // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoided by Windows userAgent? page.click('button:has-text("Continue")').catch(_ => { }); // needed since change from Chromium to Firefox? @@ -252,7 +253,7 @@ try { await iframe.locator('button:has-text("Place Order"):not(:has(.payment-loading--loading))').click({ delay: 11 }); // I Agree button is only shown for EU accounts! https://github.com/vogler/free-games-claimer/pull/7#issuecomment-1038964872 - const btnAgree = iframe.locator('button:has-text("I Agree")'); + const btnAgree = iframe.locator('button:has-text("I Accept")'); btnAgree.waitFor().then(() => btnAgree.click()).catch(_ => { }); // EU: wait for and click 'I Agree' try { // context.setDefaultTimeout(100 * 1000); // give time to solve captcha, iframe goes blank after 60s? From 8605b7037707addbd98643bb3e454fedb1951917 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 2 Aug 2024 12:57:37 +0200 Subject: [PATCH 462/520] pg: fix internal/external locators, closes #355, waitUntilStable 2s -> 5s --- prime-gaming.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index eeface0..b902712 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -101,6 +101,7 @@ try { let v; while (true) { const v2 = await f(); + console.log('waitUntilStable', v2); if (v == v2) break; v = v2; await act(); @@ -109,7 +110,7 @@ try { const scrollUntilStable = async f => waitUntilStable(f, async () => { await page.keyboard.press('End'); // scroll to bottom to show all games await page.waitForLoadState('networkidle'); // wait for all games to be loaded - await page.waitForTimeout(2000); // TODO networkidle wasn't enough to load all already collected games + await page.waitForTimeout(5000); // TODO networkidle wasn't enough to load all already collected games }); await page.click('button[data-type="Game"]'); @@ -118,8 +119,8 @@ try { await scrollUntilStable(() => games.locator('.item-card__action').count()); console.log('Number of already claimed games (total):', await games.locator('p:has-text("Collected")').count()); // can't use .all() since the list of elements via locator will change after click while we iterate over it - const internal = await games.locator('.item-card__action:has([data-a-target="FGWPOffer"])').elementHandles(); - const external = await games.locator('.item-card__action:has([data-a-target="ExternalOfferClaim"])').all(); + const internal = await games.locator('.item-card__action:has(button[data-a-target="FGWPOffer"])').elementHandles(); + const external = await games.locator('.item-card__action:has(a[data-a-target="FGWPOffer"])').all(); // bottom to top: oldest to newest games internal.reverse(); external.reverse(); From eac11e8949c398c870b63d0e3d0d56f6c74e0692 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 2 Aug 2024 13:06:28 +0200 Subject: [PATCH 463/520] ci/docker: force tag main as latest since many people pull it --- .github/workflows/docker.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index a54117c..34c0285 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -34,6 +34,9 @@ jobs: else echo "IMAGE_TAG=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV fi + # TODO the above didn't tag main as latest... probably not available in env.BRANCH right away + echo "${{ env.BRANCH }}" + echo "IMAGE_TAG=latest" >> $GITHUB_ENV - name: Set up QEMU uses: docker/setup-qemu-action@v3 From 23a611a2f11569ed20c189d0d51d45ffa93de403 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 2 Aug 2024 13:16:54 +0200 Subject: [PATCH 464/520] ci/docker: fix: tag main as latest --- .github/workflows/docker.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 34c0285..5e5adba 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -27,16 +27,14 @@ jobs: - name: Set environment variables run: | - echo "BRANCH=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + BRANCH="${GITHUB_REF#refs/heads/}" + echo "BRANCH=$BRANCH" >> $GITHUB_ENV echo "NOW=$(date -R)" >> $GITHUB_ENV # date -Iseconds; date +'%Y-%m-%dT%H:%M:%S' - if [[ "${{ env.BRANCH }}" == "main" ]]; then + if [[ "$BRANCH" == "main" ]]; then echo "IMAGE_TAG=latest" >> $GITHUB_ENV else - echo "IMAGE_TAG=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + echo "IMAGE_TAG=$BRANCH" >> $GITHUB_ENV fi - # TODO the above didn't tag main as latest... probably not available in env.BRANCH right away - echo "${{ env.BRANCH }}" - echo "IMAGE_TAG=latest" >> $GITHUB_ENV - name: Set up QEMU uses: docker/setup-qemu-action@v3 From c87deb393eec26b78d7a30bded8859c183ebb278 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 2 Aug 2024 14:02:33 +0200 Subject: [PATCH 465/520] pg: DLC: forgot to await scrollUntilStable, closes #356 --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index b902712..88ae542 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -352,7 +352,7 @@ try { await loot.waitFor(); process.stdout.write('Loading all DLCs on page...'); - scrollUntilStable(() => loot.locator('[data-a-target="item-card"]').count()) + await scrollUntilStable(() => loot.locator('[data-a-target="item-card"]').count()) console.log('\nNumber of already claimed DLC:', await loot.locator('p:has-text("Collected")').count()); From eafb2316f71d0cc548c4263be74c1818114ffc5b Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 28 Aug 2024 02:31:47 +0200 Subject: [PATCH 466/520] pg: login now has Continue button after email --- prime-gaming.js | 1 + 1 file changed, 1 insertion(+) diff --git a/prime-gaming.js b/prime-gaming.js index 88ae542..7d0d0d2 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -53,6 +53,7 @@ try { const password = email && (cfg.pg_password || await prompt({ type: 'password', message: 'Enter password' })); if (email && password) { await page.fill('[name=email]', email); + await page.click('input[type="submit"]'); await page.fill('[name=password]', password); await page.check('[name=rememberMe]'); await page.click('input[type="submit"]'); From c8e06404906d44395a00fdbdfecd208f2c052a38 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Aug 2024 12:46:28 +0200 Subject: [PATCH 467/520] eg: #371: dump HTML in case of EULA popup to find new locator https://github.com/vogler/free-games-claimer/issues/371 --- epic-games.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index ff8f3e0..fbf5732 100644 --- a/epic-games.js +++ b/epic-games.js @@ -215,8 +215,10 @@ try { page.click('button:has-text("Yes, buy now")').catch(_ => { }); // Accept End User License Agreement (only needed once) - page.locator('input#agree').waitFor().then(async () => { + page.locator(':has-text("end user license agreement")').waitFor().then(async () => { console.log(' Accept End User License Agreement (only needed once)'); + console.log(page.innerHTML); + console.log('Please report the HTML above here: https://github.com/vogler/free-games-claimer/issues/371'); await page.locator('input#agree').check(); // TODO Bundle: got stuck here await page.locator('button:has-text("Accept")').click(); }).catch(_ => { }); From 1f4af79e0eff56dc42bd3a8611c530a0ec606aaf Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Aug 2024 13:23:45 +0200 Subject: [PATCH 468/520] pg: fix loading all games by 2*PageDown + checking height instead of End + checking count..., #357 --- prime-gaming.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 7d0d0d2..adfdbd4 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -108,16 +108,22 @@ try { await act(); } }; - const scrollUntilStable = async f => waitUntilStable(f, async () => { - await page.keyboard.press('End'); // scroll to bottom to show all games + const scrollUntilStable = async f => await waitUntilStable(f, async () => { + // await page.keyboard.press('End'); // scroll to bottom to show all games + // loading all games became flaky; see https://github.com/vogler/free-games-claimer/issues/357 + await page.keyboard.press('PageDown'); // scrolling to straight to the bottom started to skip loading some games await page.waitForLoadState('networkidle'); // wait for all games to be loaded - await page.waitForTimeout(5000); // TODO networkidle wasn't enough to load all already collected games + await page.waitForTimeout(3000); // TODO networkidle wasn't enough to load all already collected games + // do it again since once wasn't enough... + await page.keyboard.press('PageDown'); + await page.waitForTimeout(3000); }); await page.click('button[data-type="Game"]'); const games = page.locator('div[data-a-target="offer-list-FGWP_FULL"]'); await games.waitFor(); - await scrollUntilStable(() => games.locator('.item-card__action').count()); + // await scrollUntilStable(() => games.locator('.item-card__action').count()); // number of games + await scrollUntilStable(() => page.evaluate(() => document.querySelector('.tw-full-width').scrollHeight)); // height may change during loading while number of games is still the same? console.log('Number of already claimed games (total):', await games.locator('p:has-text("Collected")').count()); // can't use .all() since the list of elements via locator will change after click while we iterate over it const internal = await games.locator('.item-card__action:has(button[data-a-target="FGWPOffer"])').elementHandles(); From 7279ba06e8b50b3cbafbbb51d440b8aaff6bddd7 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Aug 2024 19:48:28 +0200 Subject: [PATCH 469/520] eg: change back to purchase-cta-button from 'aside button', fixes #374 --- epic-games.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index fbf5732..f15d393 100644 --- a/epic-games.js +++ b/epic-games.js @@ -156,8 +156,8 @@ try { for (const url of urls) { if (cfg.time) console.time('claim game'); await page.goto(url); // , { waitUntil: 'domcontentloaded' }); - const purcahseBtn = page.locator('aside button').first(); - const btnText = (await purcahseBtn.innerText()).toLowerCase(); // barrier to block until page is loaded + const purchaseBtn = page.locator('button[data-testid="purchase-cta-button"]').first(); + const btnText = (await purchaseBtn.innerText()).toLowerCase(); // barrier to block until page is loaded // click Continue if 'This game contains mature content recommended only for ages 18+' if (await page.locator('button:has-text("Continue")').count() > 0) { @@ -179,6 +179,7 @@ try { if (await page.locator('span:text-is("About Bundle")').count()) { // console.log(' This is a bundle containing: TODO'); title = (await page.locator('span:has-text("Buy"):left-of([data-testid="purchase-cta-button"])').first().innerText()).replace('Buy ', ''); + // h1 first didn't exist for bundles but now it does... However h1 would e.g. be 'Fallout® Classic Collection' instead of 'Fallout Classic Collection' } else { title = await page.locator('h1').first().innerText(); } @@ -206,7 +207,7 @@ try { urls.push(url); // add add-on itself again } else { // GET console.log(' Not in library yet! Click GET.'); - await purcahseBtn.click({ delay: 11 }); // got stuck here without delay (or mouse move), see #75, 1ms was also enough + await purchaseBtn.click({ delay: 11 }); // got stuck here without delay (or mouse move), see #75, 1ms was also enough // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoided by Windows userAgent? page.click('button:has-text("Continue")').catch(_ => { }); // needed since change from Chromium to Firefox? From 292aadae3c5f4b7ec084dec28129e26312d8a48a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Aug 2024 20:06:59 +0200 Subject: [PATCH 470/520] eg: list games included in a bundle --- epic-games.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index f15d393..155e9ee 100644 --- a/epic-games.js +++ b/epic-games.js @@ -176,16 +176,22 @@ try { } let title; + let bundle_includes; if (await page.locator('span:text-is("About Bundle")').count()) { - // console.log(' This is a bundle containing: TODO'); title = (await page.locator('span:has-text("Buy"):left-of([data-testid="purchase-cta-button"])').first().innerText()).replace('Buy ', ''); // h1 first didn't exist for bundles but now it does... However h1 would e.g. be 'Fallout® Classic Collection' instead of 'Fallout Classic Collection' + try { + bundle_includes = await Promise.all((await page.locator('.product-card-top-row h5').all()).map(b => b.innerText())); + } catch (e) { + console.error('Failed to get "Bundle Includes":', e); + } } else { title = await page.locator('h1').first().innerText(); } const game_id = page.url().split('/').pop(); db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! console.log('Current free game:', title); + if (bundle_includes) console.log(' This bundle includes:', bundle_includes); const notify_game = { title, url, status: 'failed' }; notify_games.push(notify_game); // status is updated below @@ -220,7 +226,7 @@ try { console.log(' Accept End User License Agreement (only needed once)'); console.log(page.innerHTML); console.log('Please report the HTML above here: https://github.com/vogler/free-games-claimer/issues/371'); - await page.locator('input#agree').check(); // TODO Bundle: got stuck here + await page.locator('input#agree').check(); // TODO Bundle: got stuck here; likely unrelated to bundle and locator just changed: https://github.com/vogler/free-games-claimer/issues/371 await page.locator('button:has-text("Accept")').click(); }).catch(_ => { }); From fef5b97e3b70d3387233e612d8ed6709437d9cd4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 29 Aug 2024 20:23:43 +0200 Subject: [PATCH 471/520] eg: wait for purchaseBtn, without it didn't detect bundle as already claimed --- epic-games.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/epic-games.js b/epic-games.js index 155e9ee..d8784f8 100644 --- a/epic-games.js +++ b/epic-games.js @@ -157,6 +157,7 @@ try { if (cfg.time) console.time('claim game'); await page.goto(url); // , { waitUntil: 'domcontentloaded' }); const purchaseBtn = page.locator('button[data-testid="purchase-cta-button"]').first(); + await purchaseBtn.waitFor(); const btnText = (await purchaseBtn.innerText()).toLowerCase(); // barrier to block until page is loaded // click Continue if 'This game contains mature content recommended only for ages 18+' @@ -212,7 +213,7 @@ try { urls.push(baseUrl); // add base game to the list of games to claim urls.push(url); // add add-on itself again } else { // GET - console.log(' Not in library yet! Click GET.'); + console.log(' Not in library yet! Click', btnText); await purchaseBtn.click({ delay: 11 }); // got stuck here without delay (or mouse move), see #75, 1ms was also enough // click Continue if 'Device not supported. This product is not compatible with your current device.' - avoided by Windows userAgent? @@ -281,7 +282,7 @@ try { console.error(' Failed to challenge captcha, please try again later.'); await notify('epic-games: failed to challenge captcha. Please check.'); }).catch(_ => { }); - await page.locator('text=Thanks for your order!').waitFor({ state: 'attached' }); // TODO Bundle: got stuck here + await page.locator('text=Thanks for your order!').waitFor({ state: 'attached' }); // TODO Bundle: got stuck here, but normal game now as well db.data[user][game_id].status = 'claimed'; db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time console.log(' Claimed successfully!'); From c8cf7362fa2ff92386eee4dc847784d95d7f5195 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Sun, 1 Sep 2024 01:20:45 +0200 Subject: [PATCH 472/520] eg: wait for purchaseBtn to have some text, fixes #375 --- epic-games.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index d8784f8..f853380 100644 --- a/epic-games.js +++ b/epic-games.js @@ -156,7 +156,7 @@ try { for (const url of urls) { if (cfg.time) console.time('claim game'); await page.goto(url); // , { waitUntil: 'domcontentloaded' }); - const purchaseBtn = page.locator('button[data-testid="purchase-cta-button"]').first(); + const purchaseBtn = page.locator('button[data-testid="purchase-cta-button"] >> :has-text("e"), :has-text("i")').first(); // when loading, the button text is empty -> need to wait for some text {'get', 'in library', 'requires base game'} -> just wait for e or i to not be too specific; :text-matches("\w+") somehow didn't work - https://github.com/vogler/free-games-claimer/issues/375 await purchaseBtn.waitFor(); const btnText = (await purchaseBtn.innerText()).toLowerCase(); // barrier to block until page is loaded From c5a7a10ca35de6cc54cd16c424ef81af8609a30d Mon Sep 17 00:00:00 2001 From: Samuel Rounce Date: Sat, 28 Sep 2024 18:34:39 +0100 Subject: [PATCH 473/520] fix: docker-entrypoint.sh uses safe shebang Shebangs should use /usr/bin/env to locate the interpreter. Setting a path directly to the interpreter itself tends to be brittle and prone to breaking. --- docker-entrypoint.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index 679acc6..5837164 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eo pipefail # exit on error, error on any fail in pipe (not just last cmd); add -x to print each cmd; see gist bash_strict_mode.md From d5072a62c090ab637af98da524f0a37268f25638 Mon Sep 17 00:00:00 2001 From: AgentTechnoman <46273498+AgentTechnoman@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:46:18 -0700 Subject: [PATCH 474/520] Fix gog error per @jordyamc https://github.com/vogler/free-games-claimer/issues/398#issuecomment-2487274414 --- gog.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/gog.js b/gog.js index 0688626..c4d5078 100644 --- a/gog.js +++ b/gog.js @@ -99,7 +99,8 @@ try { console.log('Currently no free giveaway!'); } else { const text = await page.locator('.giveaway__content-header').innerText(); - const title = text.match(/Claim (.*) and don't miss the/)[1]; + const match_all = text.match(/Claim (.*) and don't miss the|Success! (.*) was added to/); + const title = match_all[1] ? match_all[1] : match_all[2]; const url = await banner.locator('a').first().getAttribute('href'); console.log(`Current free game: ${title} - ${url}`); db.data[user][title] ||= { title, time: datetime(), url }; From b3ab8f7830ba6a2f6785812b086134798c55b4da Mon Sep 17 00:00:00 2001 From: NeoMod Date: Wed, 1 Jan 2025 15:31:04 +0100 Subject: [PATCH 475/520] Fixed missing game link in notification for epic-games on captcha halt This fixes the "game link" missing from notification when checking Epic Games Store and encountering a captcha, as per #259 should have been but instead wasn't. Issue was identified in #402 comment by "vttc08" (https://github.com/vogler/free-games-claimer/issues/402#issuecomment-2510818082) I also added the missing notification for "Game Already in Library" for Epic Games Store, since I felt it is useful because it provides an easy way to know if the script is working or not during scheduled usage. This also provided the opportunity to check if the game url link was handled properly outside "Discord": I have added a commented option where one could activate the "Already in Library" notification that will also provide the game url link for easy verification. --- epic-games.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index f853380..5ac6839 100644 --- a/epic-games.js +++ b/epic-games.js @@ -198,6 +198,8 @@ try { if (btnText == 'in library') { console.log(' Already in library! Nothing to claim.'); + //await notify(`Game Already in Library! Follow is a test link:.\n Game link ${url}`); // Decomment this line if you want to also test if a link is beeing sent correctly in a notification. The "already in library" notification was missing, I find it usefull. + await notify(`Game Already in Library!`); // decomment the previous line and comment this one if you also want to test for proper link-handling via notification. notify_game.status = 'existed'; db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed if (db.data[user][game_id].status.startsWith('failed')) db.data[user][game_id].status = 'manual'; // was failed but now it's claimed @@ -271,7 +273,7 @@ try { captcha.waitFor().then(async () => { // don't await, since element may not be shown // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.'); - await notify(`epic-games: got captcha challenge right before claim of ${title}. Use VNC to solve it manually.`); // TODO could even create purchase URL, see https://github.com/vogler/free-games-claimer/pull/130 + await notify(`epic-games: got captcha challenge.\n Game link ${url}`);// FIXED: Game link was not sent, probably error with html formatting? Anyway, the link is automatically parsed. TODO could even create purchase URL, see https://github.com/vogler/free-games-claimer/pull/130 // await page.waitForTimeout(2000); // const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); // await captcha.screenshot({ path: p }); From 9e87ce58ac2899e58d771c6894e6d3f2a166d75d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 13:17:13 +0000 Subject: [PATCH 476/520] Add renovate.json --- renovate.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..5db72dd --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "config:recommended" + ] +} From 4a018c20e930b1945872804af67265253d50790f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 13:22:40 +0000 Subject: [PATCH 477/520] fix(deps): update dependency dotenv to v16.4.7 --- package-lock.json | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6f66b2..30fd170 100644 --- a/package-lock.json +++ b/package-lock.json @@ -512,10 +512,9 @@ } }, "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", - "license": "BSD-2-Clause", + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", "engines": { "node": ">=12" }, @@ -2052,9 +2051,9 @@ "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, "dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==" + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==" }, "enquirer": { "version": "2.4.1", From fed03428f3efff42ddd1fcc4a0adc2b25a405c6b Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 5 Mar 2025 15:07:37 +0100 Subject: [PATCH 478/520] try super-linter https://github.com/marketplace/actions/super-linter#get-started --- .github/workflows/lint.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 .github/workflows/lint.yml diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..792d1d4 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,33 @@ +--- +name: Lint + +on: # yamllint disable-line rule:truthy + push: null + pull_request: null + +permissions: {} + +jobs: + build: + name: Lint + runs-on: ubuntu-latest + + permissions: + contents: read + packages: read + # To report GitHub Actions status checks + statuses: write + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + # super-linter needs the full git history to get the + # list of files that changed across commits + fetch-depth: 0 + + - name: Super-linter + uses: super-linter/super-linter@v7.3.0 # x-release-please-version + env: + # To report GitHub Actions status checks + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From d76c03ed6aff7d2fe4eac4d44903e982aef9dcfc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:08:28 +0000 Subject: [PATCH 479/520] chore(deps): update dependency eslint to v9.21.0 --- package-lock.json | 534 ++++++++++++++++++++-------------------------- 1 file changed, 226 insertions(+), 308 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6f66b2..1205edc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -51,35 +51,45 @@ } }, "node_modules/@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true, "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/config-array": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.16.0.tgz", - "integrity": "sha512-/jmuSd74i4Czf1XXn7wGRWZCuyaUZ330NH1Bek0Pplatt4Sy1S5haN21SCLLdbeKslQ+S0wEJ+++v5YibSi+Lg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", "dev": true, - "license": "Apache-2.0", "dependencies": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", - "minimatch": "^3.0.5" + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.15" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", "dev": true, - "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -99,25 +109,58 @@ } }, "node_modules/@eslint/js": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.5.0.tgz", - "integrity": "sha512-A7+AOT2ICkodvtsWnxZP4Xxk3NbZ3VMHd8oihydLRGrJgqqdEz1qSeEgXYyT/Cu8h1TWWsQRejIx48mtjZ5y1w==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", + "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", "dev": true, - "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@eslint/plugin-kit": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "dev": true, + "dependencies": { + "@eslint/core": "^0.12.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -145,41 +188,6 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, "node_modules/@otplib/core": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", @@ -274,11 +282,10 @@ } }, "node_modules/@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "dev": true, - "license": "MIT" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", + "dev": true }, "node_modules/@types/json-schema": { "version": "7.0.15", @@ -293,11 +300,10 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "node_modules/acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true, - "license": "MIT", "bin": { "acorn": "bin/acorn" }, @@ -469,9 +475,9 @@ } }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -548,28 +554,31 @@ } }, "node_modules/eslint": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.5.0.tgz", - "integrity": "sha512-+NAOZFrW/jFTS3dASCGBxX1pkFD0/fsO+hfAkJ4TyYKwgsXZbqzrw+seCYFCcPCYXvnD67tAnglU7GQTz6kcVw==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", + "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", "dev": true, - "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/config-array": "^0.16.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.5.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.2", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.0", + "@eslint/js": "9.21.0", + "@eslint/plugin-kit": "^0.2.7", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", - "@nodelib/fs.walk": "^1.2.8", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.1", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.1", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -579,15 +588,11 @@ "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" @@ -597,14 +602,21 @@ }, "funding": { "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -628,6 +640,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true, + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/eslint/node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -645,11 +670,10 @@ } }, "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -658,15 +682,14 @@ } }, "node_modules/espree": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", - "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, - "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.11.3", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" + "eslint-visitor-keys": "^4.2.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -676,11 +699,10 @@ } }, "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -752,15 +774,6 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "node_modules/fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "dependencies": { - "reusify": "^1.0.4" - } - }, "node_modules/file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -990,15 +1003,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -1452,26 +1456,6 @@ } } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -1482,16 +1466,6 @@ "node": ">=4" } }, - "node_modules/reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true, - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -1506,29 +1480,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "queue-microtask": "^1.2.2" - } - }, "node_modules/shallow-clone": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", @@ -1629,12 +1580,6 @@ "node": ">=8" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, "node_modules/thirty-two": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", @@ -1722,26 +1667,35 @@ } }, "@eslint-community/regexpp": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz", - "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "dev": true }, "@eslint/config-array": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.16.0.tgz", - "integrity": "sha512-/jmuSd74i4Czf1XXn7wGRWZCuyaUZ330NH1Bek0Pplatt4Sy1S5haN21SCLLdbeKslQ+S0wEJ+++v5YibSi+Lg==", + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", "dev": true, "requires": { - "@eslint/object-schema": "^2.1.4", + "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", - "minimatch": "^3.0.5" + "minimatch": "^3.1.2" + } + }, + "@eslint/core": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.15" } }, "@eslint/eslintrc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", - "integrity": "sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -1756,17 +1710,43 @@ } }, "@eslint/js": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.5.0.tgz", - "integrity": "sha512-A7+AOT2ICkodvtsWnxZP4Xxk3NbZ3VMHd8oihydLRGrJgqqdEz1qSeEgXYyT/Cu8h1TWWsQRejIx48mtjZ5y1w==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", + "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", "dev": true }, "@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true }, + "@eslint/plugin-kit": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "dev": true, + "requires": { + "@eslint/core": "^0.12.0", + "levn": "^0.4.1" + } + }, + "@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true + }, + "@humanfs/node": { + "version": "0.16.6", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", + "dev": true, + "requires": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.3.0" + } + }, "@humanwhocodes/module-importer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", @@ -1779,32 +1759,6 @@ "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", "dev": true }, - "@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, - "requires": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - } - }, - "@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true - }, - "@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "requires": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - } - }, "@otplib/core": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", @@ -1886,9 +1840,9 @@ } }, "@types/estree": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, "@types/json-schema": { @@ -1903,9 +1857,9 @@ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" }, "acorn": { - "version": "8.11.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", - "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "version": "8.14.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", "dev": true }, "acorn-jsx": { @@ -2023,9 +1977,9 @@ } }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -2072,27 +2026,31 @@ "dev": true }, "eslint": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.5.0.tgz", - "integrity": "sha512-+NAOZFrW/jFTS3dASCGBxX1pkFD0/fsO+hfAkJ4TyYKwgsXZbqzrw+seCYFCcPCYXvnD67tAnglU7GQTz6kcVw==", + "version": "9.21.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", + "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/config-array": "^0.16.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.5.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.19.2", + "@eslint/core": "^0.12.0", + "@eslint/eslintrc": "^3.3.0", + "@eslint/js": "9.21.0", + "@eslint/plugin-kit": "^0.2.7", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", - "@nodelib/fs.walk": "^1.2.8", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.1", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.1", + "eslint-scope": "^8.2.0", + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -2102,17 +2060,19 @@ "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "dependencies": { + "@humanwhocodes/retry": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", + "dev": true + }, "chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -2124,17 +2084,17 @@ } }, "eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true } } }, "eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -2148,20 +2108,20 @@ "dev": true }, "espree": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.0.1.tgz", - "integrity": "sha512-MWkrWZbJsL2UwnjxTX3gG8FneachS/Mwg7tdGXce011sJd5b0JG54vat5KHnfSBODZ3Wvzd2WnjxyzsRoVv+ww==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", "dev": true, "requires": { - "acorn": "^8.11.3", + "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.0.0" + "eslint-visitor-keys": "^4.2.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true } } @@ -2214,15 +2174,6 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, - "fastq": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", - "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", - "dev": true, - "requires": { - "reusify": "^1.0.4" - } - }, "file-entry-cache": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", @@ -2386,12 +2337,6 @@ "is-extglob": "^2.1.1" } }, - "is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true - }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -2688,24 +2633,12 @@ "puppeteer-extra-plugin-user-data-dir": "^2.4.1" } }, - "queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true - }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true }, - "reusify": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", - "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", - "dev": true - }, "rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -2714,15 +2647,6 @@ "glob": "^7.1.3" } }, - "run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "requires": { - "queue-microtask": "^1.2.2" - } - }, "shallow-clone": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", @@ -2790,12 +2714,6 @@ "has-flag": "^4.0.0" } }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true - }, "thirty-two": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", From 041605dcf70911c637da8d225146e30dad3acace Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:08:35 +0000 Subject: [PATCH 480/520] fix(deps): update dependency chalk to v5.4.1 --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index e6f66b2..03b750e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -403,9 +403,9 @@ } }, "node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -1978,9 +1978,9 @@ "dev": true }, "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==" + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==" }, "clone-deep": { "version": "0.2.4", From d6666aed034ef84bfdc2b3240d70df29d5b30ece Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 5 Mar 2025 15:17:34 +0100 Subject: [PATCH 481/520] super-linter: use slim version --- .github/workflows/lint.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 792d1d4..951e26d 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -1,4 +1,4 @@ ---- +# https://github.com/marketplace/actions/super-linter#get-started name: Lint on: # yamllint disable-line rule:truthy @@ -27,7 +27,7 @@ jobs: fetch-depth: 0 - name: Super-linter - uses: super-linter/super-linter@v7.3.0 # x-release-please-version + uses: super-linter/super-linter/slim@v7.3.0 # x-release-please-version env: # To report GitHub Actions status checks GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From bf2216f73175c74b764a278f48ae9b7331d954ce Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 5 Mar 2025 15:27:00 +0100 Subject: [PATCH 482/520] super-linter: TODO fix-lint-issues --- .github/workflows/lint.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 951e26d..ab3ad59 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,7 +8,7 @@ on: # yamllint disable-line rule:truthy permissions: {} jobs: - build: + lint: name: Lint runs-on: ubuntu-latest @@ -31,3 +31,5 @@ jobs: env: # To report GitHub Actions status checks GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # TODO automatically fix linting issues and commit them for PRs + # fix-lint-issues: # https://github.com/marketplace/actions/super-linter#github-actions-workflow-example-pull-request From 6aa0f9b8177c2962d217bcd6e54caf84b3dded95 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 5 Mar 2025 15:34:03 +0100 Subject: [PATCH 483/520] super-linter: problem matchers included? --- .github/workflows/lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index ab3ad59..0ed6b5b 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -28,6 +28,7 @@ jobs: - name: Super-linter uses: super-linter/super-linter/slim@v7.3.0 # x-release-please-version + # TODO need to create problem matchers for each linter? https://github.com/rhysd/actionlint/blob/v1.7.7/docs/usage.md#problem-matchers env: # To report GitHub Actions status checks GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From d620ee2731ba66ae293ee69c87559744311a6a37 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:46:12 +0000 Subject: [PATCH 484/520] chore(deps): update dependency @stylistic/eslint-plugin-js to v2.13.0 --- package-lock.json | 59 +++++++++++++---------------------------------- 1 file changed, 16 insertions(+), 43 deletions(-) diff --git a/package-lock.json b/package-lock.json index 364a707..3870b15 100644 --- a/package-lock.json +++ b/package-lock.json @@ -231,16 +231,13 @@ } }, "node_modules/@stylistic/eslint-plugin-js": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.2.2.tgz", - "integrity": "sha512-Vj2Q1YHVvJw+ThtOvmk5Yx7wZanVrIBRUTT89horLDb4xdP9GA1um9XOYQC6j67VeUC2gjZQnz5/RVJMzaOhtw==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.13.0.tgz", + "integrity": "sha512-GPPDK4+fcbsQD58a3abbng2Dx+jBoxM5cnYjBM4T24WFZRZdlNSKvR19TxP8CPevzMOodQ9QVzNeqWvMXzfJRA==", "dev": true, - "license": "MIT", "dependencies": { - "@types/eslint": "^8.56.10", - "acorn": "^8.11.3", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.1" + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -250,11 +247,10 @@ } }, "node_modules/@stylistic/eslint-plugin-js/node_modules/eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true, - "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, @@ -270,17 +266,6 @@ "@types/ms": "*" } }, - "node_modules/@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", @@ -1801,21 +1786,19 @@ } }, "@stylistic/eslint-plugin-js": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.2.2.tgz", - "integrity": "sha512-Vj2Q1YHVvJw+ThtOvmk5Yx7wZanVrIBRUTT89horLDb4xdP9GA1um9XOYQC6j67VeUC2gjZQnz5/RVJMzaOhtw==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.13.0.tgz", + "integrity": "sha512-GPPDK4+fcbsQD58a3abbng2Dx+jBoxM5cnYjBM4T24WFZRZdlNSKvR19TxP8CPevzMOodQ9QVzNeqWvMXzfJRA==", "dev": true, "requires": { - "@types/eslint": "^8.56.10", - "acorn": "^8.11.3", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.0.1" + "eslint-visitor-keys": "^4.2.0", + "espree": "^10.3.0" }, "dependencies": { "eslint-visitor-keys": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz", - "integrity": "sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", "dev": true } } @@ -1828,16 +1811,6 @@ "@types/ms": "*" } }, - "@types/eslint": { - "version": "8.56.10", - "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", - "integrity": "sha512-Shavhk87gCtY2fhXDctcfS3e6FdxWkCx1iUZ9eEUbh7rTqlZT0/IzOkCOVt0fCjcFuZ9FPYfuezTBImfHCDBGQ==", - "dev": true, - "requires": { - "@types/estree": "*", - "@types/json-schema": "*" - } - }, "@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", From cb22abfb2fbc98664da57a33d6ee9195edd51246 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:53:57 +0000 Subject: [PATCH 485/520] chore(deps): update dependency @stylistic/eslint-plugin-js to v4 --- package-lock.json | 16 ++++++++-------- package.json | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 40345b6..e579b4f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { - "@stylistic/eslint-plugin-js": "^2.2.2", + "@stylistic/eslint-plugin-js": "^4.0.0", "eslint": "^9.5.0" }, "engines": { @@ -231,9 +231,9 @@ } }, "node_modules/@stylistic/eslint-plugin-js": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.13.0.tgz", - "integrity": "sha512-GPPDK4+fcbsQD58a3abbng2Dx+jBoxM5cnYjBM4T24WFZRZdlNSKvR19TxP8CPevzMOodQ9QVzNeqWvMXzfJRA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-4.2.0.tgz", + "integrity": "sha512-MiJr6wvyzMYl/wElmj8Jns8zH7Q1w8XoVtm+WM6yDaTrfxryMyb8n0CMxt82fo42RoLIfxAEtM6tmQVxqhk0/A==", "dev": true, "dependencies": { "eslint-visitor-keys": "^4.2.0", @@ -243,7 +243,7 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { - "eslint": ">=8.40.0" + "eslint": ">=9.0.0" } }, "node_modules/@stylistic/eslint-plugin-js/node_modules/eslint-visitor-keys": { @@ -1786,9 +1786,9 @@ } }, "@stylistic/eslint-plugin-js": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-2.13.0.tgz", - "integrity": "sha512-GPPDK4+fcbsQD58a3abbng2Dx+jBoxM5cnYjBM4T24WFZRZdlNSKvR19TxP8CPevzMOodQ9QVzNeqWvMXzfJRA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-4.2.0.tgz", + "integrity": "sha512-MiJr6wvyzMYl/wElmj8Jns8zH7Q1w8XoVtm+WM6yDaTrfxryMyb8n0CMxt82fo42RoLIfxAEtM6tmQVxqhk0/A==", "dev": true, "requires": { "eslint-visitor-keys": "^4.2.0", diff --git a/package.json b/package.json index 2c64f46..e1e3340 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { - "@stylistic/eslint-plugin-js": "^2.2.2", + "@stylistic/eslint-plugin-js": "^4.0.0", "eslint": "^9.5.0" } } From e75975b273a368f99306d57daa5c3b07f4a4443b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:54:01 +0000 Subject: [PATCH 486/520] chore(deps): update docker/build-push-action action to v6 --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 5e5adba..071585e 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -57,7 +57,7 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Build and push - uses: docker/build-push-action@v5 + uses: docker/build-push-action@v6 # if: github.event_name != 'pull_request' # still want to build image with: context: . From 3626fc17626f386614c1189f6a20809331065a74 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 5 Mar 2025 16:17:48 +0100 Subject: [PATCH 487/520] fixup #417 --- epic-games.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/epic-games.js b/epic-games.js index 5ac6839..9aa4482 100644 --- a/epic-games.js +++ b/epic-games.js @@ -198,8 +198,7 @@ try { if (btnText == 'in library') { console.log(' Already in library! Nothing to claim.'); - //await notify(`Game Already in Library! Follow is a test link:.\n Game link ${url}`); // Decomment this line if you want to also test if a link is beeing sent correctly in a notification. The "already in library" notification was missing, I find it usefull. - await notify(`Game Already in Library!`); // decomment the previous line and comment this one if you also want to test for proper link-handling via notification. + await notify(`Game already in library: ${url}`); notify_game.status = 'existed'; db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed if (db.data[user][game_id].status.startsWith('failed')) db.data[user][game_id].status = 'manual'; // was failed but now it's claimed @@ -273,7 +272,9 @@ try { captcha.waitFor().then(async () => { // don't await, since element may not be shown // console.info(' Got hcaptcha challenge! NopeCHA extension will likely solve it.') console.error(' Got hcaptcha challenge! Lost trust due to too many login attempts? You can solve the captcha in the browser or get a new IP address.'); - await notify(`epic-games: got captcha challenge.\n Game link ${url}`);// FIXED: Game link was not sent, probably error with html formatting? Anyway, the link is automatically parsed. TODO could even create purchase URL, see https://github.com/vogler/free-games-claimer/pull/130 + // await notify(`epic-games: got captcha challenge right before claim of ${title}. Use VNC to solve it manually.`); // TODO not all apprise services understand HTML: https://github.com/vogler/free-games-claimer/pull/417 + await notify(`epic-games: got captcha challenge for.\nGame link: ${url}`); + // TODO could even create purchase URL, see https://github.com/vogler/free-games-claimer/pull/130 // await page.waitForTimeout(2000); // const p = path.resolve(cfg.dir.screenshots, 'epic-games', 'captcha', `${filenamify(datetime())}.png`); // await captcha.screenshot({ path: p }); From ea69c76b5016e17cfb58d49764990b6459c99e20 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 11 Mar 2025 10:14:42 +0100 Subject: [PATCH 488/520] eg: fix #449: only notify once if 'Game already in library' --- epic-games.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/epic-games.js b/epic-games.js index 9aa4482..f03350f 100644 --- a/epic-games.js +++ b/epic-games.js @@ -190,6 +190,7 @@ try { title = await page.locator('h1').first().innerText(); } const game_id = page.url().split('/').pop(); + const existedInDb = db.data[user][game_id]; db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! console.log('Current free game:', title); if (bundle_includes) console.log(' This bundle includes:', bundle_includes); @@ -198,7 +199,7 @@ try { if (btnText == 'in library') { console.log(' Already in library! Nothing to claim.'); - await notify(`Game already in library: ${url}`); + if (!existedInDb) await notify(`Game already in library: ${url}`); notify_game.status = 'existed'; db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed if (db.data[user][game_id].status.startsWith('failed')) db.data[user][game_id].status = 'manual'; // was failed but now it's claimed From 8b7018f54c6427532ae65b99829d9cd5a1826b92 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Mar 2025 16:26:40 +0100 Subject: [PATCH 489/520] fix actionlint errors (docker.yml) Double quote to prevent globbing and word splitting --- .github/workflows/docker.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 071585e..6371f4c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -28,12 +28,12 @@ jobs: name: Set environment variables run: | BRANCH="${GITHUB_REF#refs/heads/}" - echo "BRANCH=$BRANCH" >> $GITHUB_ENV - echo "NOW=$(date -R)" >> $GITHUB_ENV # date -Iseconds; date +'%Y-%m-%dT%H:%M:%S' + echo "BRANCH=$BRANCH" >> "$GITHUB_ENV" + echo "NOW=$(date -R)" >> "$GITHUB_ENV" # date -Iseconds; date +'%Y-%m-%dT%H:%M:%S' if [[ "$BRANCH" == "main" ]]; then - echo "IMAGE_TAG=latest" >> $GITHUB_ENV + echo "IMAGE_TAG=latest" >> "$GITHUB_ENV" else - echo "IMAGE_TAG=$BRANCH" >> $GITHUB_ENV + echo "IMAGE_TAG=$BRANCH" >> "$GITHUB_ENV" fi - name: Set up QEMU From cfe42db805d7082f0f32d095f52e502164f4fc94 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Mar 2025 16:53:27 +0100 Subject: [PATCH 490/520] pg: fix logo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2431c55..e3da438 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Claims free games periodically on - [Epic Games Store](https://www.epicgames.com/store/free-games) -- [Amazon Prime Gaming](https://gaming.amazon.com) +- [Amazon Prime Gaming](https://gaming.amazon.com) - [GOG](https://www.gog.com) - [Unreal Engine (Assets)](https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910) ([experimental](https://github.com/vogler/free-games-claimer/issues/44), same login as Epic Games) From 787522473fee1b17f89e02da9c729a8fd6d5a1f8 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Mar 2025 16:53:41 +0100 Subject: [PATCH 491/520] readme: logos: vertical-align: middle --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e3da438..cd215eb 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ # free-games-claimer Claims free games periodically on -- [Epic Games Store](https://www.epicgames.com/store/free-games) -- [Amazon Prime Gaming](https://gaming.amazon.com) -- [GOG](https://www.gog.com) -- [Unreal Engine (Assets)](https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910) ([experimental](https://github.com/vogler/free-games-claimer/issues/44), same login as Epic Games) +- [Epic Games Store](https://www.epicgames.com/store/free-games) +- [Amazon Prime Gaming](https://gaming.amazon.com) +- [GOG](https://www.gog.com) +- [Unreal Engine (Assets)](https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910) ([experimental](https://github.com/vogler/free-games-claimer/issues/44), same login as Epic Games) Pull requests welcome :) From 041a77d9f6b882a16dcc1186c0bb4f410acb4d7a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Mar 2025 17:01:18 +0100 Subject: [PATCH 492/520] readme: logos: align: middle - CSS is stripped --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cd215eb..e84224c 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ # free-games-claimer Claims free games periodically on -- [Epic Games Store](https://www.epicgames.com/store/free-games) -- [Amazon Prime Gaming](https://gaming.amazon.com) -- [GOG](https://www.gog.com) -- [Unreal Engine (Assets)](https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910) ([experimental](https://github.com/vogler/free-games-claimer/issues/44), same login as Epic Games) +- [Epic Games Store](https://www.epicgames.com/store/free-games) +- [Amazon Prime Gaming](https://gaming.amazon.com) +- [GOG](https://www.gog.com) +- [Unreal Engine (Assets)](https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910) ([experimental](https://github.com/vogler/free-games-claimer/issues/44), same login as Epic Games) Pull requests welcome :) From 90e3400072303f0def90991740323a63dae33afc Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 27 Mar 2025 17:11:30 +0100 Subject: [PATCH 493/520] readme: logos: use GitHub asset upload --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index e84224c..17ec854 100644 --- a/README.md +++ b/README.md @@ -6,10 +6,10 @@ # free-games-claimer Claims free games periodically on -- [Epic Games Store](https://www.epicgames.com/store/free-games) -- [Amazon Prime Gaming](https://gaming.amazon.com) -- [GOG](https://www.gog.com) -- [Unreal Engine (Assets)](https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910) ([experimental](https://github.com/vogler/free-games-claimer/issues/44), same login as Epic Games) +- [Epic Games Store](https://www.epicgames.com/store/free-games) +- [Amazon Prime Gaming](https://gaming.amazon.com) +- [GOG](https://www.gog.com) +- [Unreal Engine (Assets)](https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910) ([experimental](https://github.com/vogler/free-games-claimer/issues/44), same login as Epic Games) Pull requests welcome :) From 92cb32ff2dabd622252aa20bfbda63c953ed31a8 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 3 Apr 2025 15:33:00 +0200 Subject: [PATCH 494/520] ae: try fingerprint-injector --- aliexpress.js | 29 +++- package-lock.json | 398 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 1 + 3 files changed, 420 insertions(+), 8 deletions(-) diff --git a/aliexpress.js b/aliexpress.js index c44c673..e2439a4 100644 --- a/aliexpress.js +++ b/aliexpress.js @@ -2,16 +2,35 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdate import { datetime, filenamify, prompt, handleSIGINT, stealth } from './src/util.js'; import { cfg } from './src/config.js'; +// using https://github.com/apify/fingerprint-suite worked, but has no launchPersistentContext... +// from https://github.com/apify/fingerprint-suite/issues/162 +import { FingerprintInjector } from 'fingerprint-injector'; +import { FingerprintGenerator } from 'fingerprint-generator'; + +const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({ + devices: ["mobile"], + operatingSystems: ["android"], +}); + const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless, - viewport: { width: cfg.width, height: cfg.height }, + // viewport: { width: cfg.width, height: cfg.height }, locale: 'en-US', // ignore OS locale to be sure to have english text for locators -> done via /en in URL 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/gog-${filenamify(datetime())}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools + recordHar: cfg.record ? { path: `data/record/aliexpress-${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 + userAgent: fingerprint.navigator.userAgent, + viewport: { + width: fingerprint.screen.width, + height: fingerprint.screen.height, + }, + extraHTTPHeaders: { + 'accept-language': headers['accept-language'], + }, }); handleSIGINT(context); -await stealth(context); +// await stealth(context); +await new FingerprintInjector().attachFingerprintToPlaywright(context, { fingerprint, headers }); context.setDefaultTimeout(cfg.debug ? 0 : cfg.timeout); @@ -88,11 +107,11 @@ const merge = async () => { try { // await coins(); await [ - coins, + // coins, // grow, // gogo, // euro, - // merge, + merge, ].reduce((a, f) => a.then(async _ => { await auth(urls[f.name]); await f(); console.log() }), Promise.resolve()); // await page.pause(); diff --git a/package-lock.json b/package-lock.json index e579b4f..dd57aa8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "cross-env": "^7.0.3", "dotenv": "^16.4.5", "enquirer": "^2.4.1", + "fingerprint-injector": "^2.1.52", "lowdb": "^7.0.1", "otplib": "^12.0.1", "playwright-firefox": "^1.45.0", @@ -230,6 +231,18 @@ "@otplib/plugin-thirty-two": "^12.0.1" } }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, "node_modules/@stylistic/eslint-plugin-js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-4.2.0.tgz", @@ -306,6 +319,15 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==", + "license": "MIT", + "engines": { + "node": ">=12.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -383,16 +405,67 @@ "concat-map": "0.0.1" } }, + "node_modules/browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, "license": "MIT", "engines": { "node": ">=6" } }, + "node_modules/caniuse-lite": { + "version": "1.0.30001709", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001709.tgz", + "integrity": "sha512-NgL3vUTnDrPCZ3zTahp4fsugQ4dc7EKTSzwQDPEel6DMoMnfH2jhry9n2Zm8onbSR+f/QtKHFOA+iAQu4kbtWA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, "node_modules/chalk": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", @@ -502,6 +575,21 @@ "node": ">=0.10.0" } }, + "node_modules/dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "license": "MIT", + "dependencies": { + "is-obj": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", @@ -513,6 +601,12 @@ "url": "https://dotenvx.com" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.130", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.130.tgz", + "integrity": "sha512-Ou2u7L9j2XLZbhqzyX0jWDj6gA8D3jIfVzt4rikLf3cGBa0VdReuFimBKS9tQJA4+XpeCxj1NoWlfBXzbMa9IA==", + "license": "ISC" + }, "node_modules/enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -525,6 +619,15 @@ "node": ">=8.6" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -787,6 +890,45 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/fingerprint-generator": { + "version": "2.1.63", + "resolved": "https://registry.npmjs.org/fingerprint-generator/-/fingerprint-generator-2.1.63.tgz", + "integrity": "sha512-9ud3dO2aD0wKc9/zEAletQZ/iPuGqTwmkA7Y33OeOv/N+OvTM6OStxXbNLCsGSD7jUYe4ei/TK9uXwiQntbZGA==", + "license": "Apache-2.0", + "dependencies": { + "generative-bayesian-network": "^2.1.63", + "header-generator": "^2.1.63", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fingerprint-injector": { + "version": "2.1.63", + "resolved": "https://registry.npmjs.org/fingerprint-injector/-/fingerprint-injector-2.1.63.tgz", + "integrity": "sha512-OZpxOKi4iyU2hzqXuJDddb5ayfLgWJvnaDwWAExC5/P+XuXbAMf9cLIOAEQ3B2SnAbXUXZw6k8vQqeg6vSrWIA==", + "license": "Apache-2.0", + "dependencies": { + "fingerprint-generator": "^2.1.63", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "peerDependencies": { + "playwright": "^1.22.2", + "puppeteer": ">= 9.x" + }, + "peerDependenciesMeta": { + "playwright": { + "optional": true + }, + "puppeteer": { + "optional": true + } + } + }, "node_modules/flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -845,6 +987,16 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "node_modules/generative-bayesian-network": { + "version": "2.1.63", + "resolved": "https://registry.npmjs.org/generative-bayesian-network/-/generative-bayesian-network-2.1.63.tgz", + "integrity": "sha512-nH1t4R9nlWSmvFoI4DEcpXd0+yoGZcySVuUBkXhR09/Mf7O9AWFmR8lWqVGIoxpBS0WRNpV/qj4swKGGQAxAPQ==", + "license": "Apache-2.0", + "dependencies": { + "adm-zip": "^0.5.9", + "tslib": "^2.4.0" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -903,6 +1055,21 @@ "node": ">=8" } }, + "node_modules/header-generator": { + "version": "2.1.63", + "resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.63.tgz", + "integrity": "sha512-dicAWZb/zXmI3fPuCw1Ra1sTTC9x9cc5EOhwugmI/keXnRc3BzxDQTGHge+MeAdqhpo2ZOFTEV6u08lXJUA2uQ==", + "license": "Apache-2.0", + "dependencies": { + "browserslist": "^4.21.1", + "generative-bayesian-network": "^2.1.63", + "ow": "^0.28.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -987,6 +1154,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -1112,6 +1288,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -1188,6 +1371,12 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", + "license": "MIT" + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -1223,6 +1412,25 @@ "@otplib/preset-v11": "^12.0.1" } }, + "node_modules/ow": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.28.2.tgz", + "integrity": "sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==", + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^4.2.0", + "callsites": "^3.1.0", + "dot-prop": "^6.0.1", + "lodash.isequal": "^4.5.0", + "vali-date": "^1.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -1291,6 +1499,12 @@ "node": ">=8" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, "node_modules/playwright-core": { "version": "1.45.0", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", @@ -1572,6 +1786,12 @@ "node": ">=0.2.6" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -1592,6 +1812,36 @@ "node": ">= 10.0.0" } }, + "node_modules/update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -1602,6 +1852,15 @@ "punycode": "^2.1.0" } }, + "node_modules/vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1785,6 +2044,11 @@ "@otplib/plugin-thirty-two": "^12.0.1" } }, + "@sindresorhus/is": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", + "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" + }, "@stylistic/eslint-plugin-js": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin-js/-/eslint-plugin-js-4.2.0.tgz", @@ -1841,6 +2105,11 @@ "dev": true, "requires": {} }, + "adm-zip": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.16.tgz", + "integrity": "sha512-TGw5yVi4saajsSEgz25grObGHEUaDrniwvA2qwSC060KfqGPdglhvPMA2lPIoxs3PQIItj2iag35fONcQqgUaQ==" + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -1897,11 +2166,26 @@ "concat-map": "0.0.1" } }, + "browserslist": { + "version": "4.24.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", + "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "requires": { + "caniuse-lite": "^1.0.30001688", + "electron-to-chromium": "^1.5.73", + "node-releases": "^2.0.19", + "update-browserslist-db": "^1.1.1" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "caniuse-lite": { + "version": "1.0.30001709", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001709.tgz", + "integrity": "sha512-NgL3vUTnDrPCZ3zTahp4fsugQ4dc7EKTSzwQDPEel6DMoMnfH2jhry9n2Zm8onbSR+f/QtKHFOA+iAQu4kbtWA==" }, "chalk": { "version": "5.4.1", @@ -1977,11 +2261,24 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, + "dot-prop": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", + "integrity": "sha512-tE7ztYzXHIeyvc7N+hR3oi7FIbf/NIjVP9hmAt3yMXzrQ072/fpjGLx2GxNxGxUl5V73MEqYzioOMoVhGMJ5cA==", + "requires": { + "is-obj": "^2.0.0" + } + }, "dotenv": { "version": "16.4.7", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==" }, + "electron-to-chromium": { + "version": "1.5.130", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.130.tgz", + "integrity": "sha512-Ou2u7L9j2XLZbhqzyX0jWDj6gA8D3jIfVzt4rikLf3cGBa0VdReuFimBKS9tQJA4+XpeCxj1NoWlfBXzbMa9IA==" + }, "enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -1991,6 +2288,11 @@ "strip-ansi": "^6.0.1" } }, + "escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2165,6 +2467,25 @@ "path-exists": "^4.0.0" } }, + "fingerprint-generator": { + "version": "2.1.63", + "resolved": "https://registry.npmjs.org/fingerprint-generator/-/fingerprint-generator-2.1.63.tgz", + "integrity": "sha512-9ud3dO2aD0wKc9/zEAletQZ/iPuGqTwmkA7Y33OeOv/N+OvTM6OStxXbNLCsGSD7jUYe4ei/TK9uXwiQntbZGA==", + "requires": { + "generative-bayesian-network": "^2.1.63", + "header-generator": "^2.1.63", + "tslib": "^2.4.0" + } + }, + "fingerprint-injector": { + "version": "2.1.63", + "resolved": "https://registry.npmjs.org/fingerprint-injector/-/fingerprint-injector-2.1.63.tgz", + "integrity": "sha512-OZpxOKi4iyU2hzqXuJDddb5ayfLgWJvnaDwWAExC5/P+XuXbAMf9cLIOAEQ3B2SnAbXUXZw6k8vQqeg6vSrWIA==", + "requires": { + "fingerprint-generator": "^2.1.63", + "tslib": "^2.4.0" + } + }, "flat-cache": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", @@ -2209,6 +2530,15 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "generative-bayesian-network": { + "version": "2.1.63", + "resolved": "https://registry.npmjs.org/generative-bayesian-network/-/generative-bayesian-network-2.1.63.tgz", + "integrity": "sha512-nH1t4R9nlWSmvFoI4DEcpXd0+yoGZcySVuUBkXhR09/Mf7O9AWFmR8lWqVGIoxpBS0WRNpV/qj4swKGGQAxAPQ==", + "requires": { + "adm-zip": "^0.5.9", + "tslib": "^2.4.0" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2248,6 +2578,17 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "header-generator": { + "version": "2.1.63", + "resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.63.tgz", + "integrity": "sha512-dicAWZb/zXmI3fPuCw1Ra1sTTC9x9cc5EOhwugmI/keXnRc3BzxDQTGHge+MeAdqhpo2ZOFTEV6u08lXJUA2uQ==", + "requires": { + "browserslist": "^4.21.1", + "generative-bayesian-network": "^2.1.63", + "ow": "^0.28.1", + "tslib": "^2.4.0" + } + }, "ignore": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", @@ -2309,6 +2650,11 @@ "is-extglob": "^2.1.1" } }, + "is-obj": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-2.0.0.tgz", + "integrity": "sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==" + }, "is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -2404,6 +2750,11 @@ "p-locate": "^5.0.0" } }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -2463,6 +2814,11 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node-releases": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", + "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2495,6 +2851,18 @@ "@otplib/preset-v11": "^12.0.1" } }, + "ow": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/ow/-/ow-0.28.2.tgz", + "integrity": "sha512-dD4UpyBh/9m4X2NVjA+73/ZPBRF+uF4zIMFvvQsabMiEK8x41L3rQ8EENOi35kyyoaJwNxEeJcP6Fj1H4U409Q==", + "requires": { + "@sindresorhus/is": "^4.2.0", + "callsites": "^3.1.0", + "dot-prop": "^6.0.1", + "lodash.isequal": "^4.5.0", + "vali-date": "^1.0.0" + } + }, "p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -2538,6 +2906,11 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, + "picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" + }, "playwright-core": { "version": "1.45.0", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", @@ -2691,6 +3064,11 @@ "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==" }, + "tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -2705,6 +3083,15 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" }, + "update-browserslist-db": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", + "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==", + "requires": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + } + }, "uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -2714,6 +3101,11 @@ "punycode": "^2.1.0" } }, + "vali-date": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", + "integrity": "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==" + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index e1e3340..95027a2 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "cross-env": "^7.0.3", "dotenv": "^16.4.5", "enquirer": "^2.4.1", + "fingerprint-injector": "^2.1.52", "lowdb": "^7.0.1", "otplib": "^12.0.1", "playwright-firefox": "^1.45.0", From 0fefcc47b7271eff6997ff06aa58fcc90f39e682 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 15 Apr 2025 17:08:34 +0200 Subject: [PATCH 495/520] pg: skipBasedOnTime if days left > PG_TIMELEFT --- prime-gaming.js | 36 +++++++++++++++++++----------------- src/config.js | 2 +- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index adfdbd4..7ef528b 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -131,23 +131,25 @@ try { // bottom to top: oldest to newest games internal.reverse(); external.reverse(); - const checkTimeLeft = async url => { - // console.log(' Checking time left for game:', url); - const check = async p => { - console.log(' ', await p.locator('.availability-date').innerText()); - const dueDateOrg = await p.locator('.availability-date .tw-bold').innerText(); - const dueDate = datetime(new Date(Date.parse(dueDateOrg + ' 17:00'))); - console.log(' Due date:', dueDate); - }; - if (page.url() == url) { - await check(page); - } else { - const p = await context.newPage(); + const sameOrNewPage = async url => new Promise(async (resolve, _reject) => { + const isNew = page.url() != url; + let p = page; + if (isNew) { + p = await context.newPage(); await p.goto(url, { waitUntil: 'domcontentloaded' }); - await check(p); - p.close(); } - }; + resolve([p, isNew]); + }); + const skipBasedOnTime = async url => { + // console.log(' Checking time left for game:', url); + const [p, isNew] = await sameOrNewPage(url); + const dueDateOrg = await p.locator('.availability-date .tw-bold').innerText(); + const dueDate = new Date(Date.parse(dueDateOrg + ' 17:00')); + const daysLeft = (dueDate.getTime() - Date.now())/1000/60/60/24; + console.log(' ', await p.locator('.availability-date').innerText(), '->', daysLeft.toFixed(2)); + if (isNew) await p.close(); + return daysLeft > cfg.pg_timeLeft; + } console.log('Number of free unclaimed games (Prime Gaming):', internal.length); // claim games in internal store for (const card of internal) { @@ -156,7 +158,7 @@ try { const slug = await (await card.$('a')).getAttribute('href'); const url = 'https://gaming.amazon.com' + slug.split('?')[0]; console.log('Current free game:', title); - if (cfg.pg_timeLeft) await checkTimeLeft(url); + if (cfg.pg_timeLeft && await skipBasedOnTime(url)) continue; if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; await (await card.$('.tw-button:has-text("Claim")')).click(); @@ -184,7 +186,7 @@ try { const item_text = await page.innerText('[data-a-target="DescriptionItemDetails"]'); const store = item_text.toLowerCase().replace(/.* on /, '').slice(0, -1); console.log(' External store:', store); - if (cfg.pg_timeLeft) await checkTimeLeft(url); + if (cfg.pg_timeLeft && await skipBasedOnTime(url)) continue; if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; await Promise.any([page.click('[data-a-target="buy-box"] .tw-button:has-text("Get game")'), page.click('[data-a-target="buy-box"] .tw-button:has-text("Claim")'), page.click('.tw-button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")'), page.waitForSelector('.thank-you-title:has-text("Success")')]); // waits for navigation diff --git a/src/config.js b/src/config.js index 4a384b8..a8b1817 100644 --- a/src/config.js +++ b/src/config.js @@ -49,5 +49,5 @@ export const cfg = { pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores lg_email: process.env.LG_EMAIL || process.env.PG_EMAIL || process.env.EMAIL, // prime-gaming: external: legacy-games: email to use for redeeming pg_claimdlc: process.env.PG_CLAIMDLC == '1', // prime-gaming: claim in-game content - pg_timeLeft: process.env.PG_TIMELEFT == '1', // prime-gaming: list time left to claim + pg_timeLeft: Number(process.env.PG_TIMELEFT), // prime-gaming: check time left to claim and skip game if there are more than PG_TIMELEFT days left to claim it }; From 04d1b7ea9e95af8f3e569397c53bb9ff591ddb4a Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 15 Apr 2025 17:22:21 +0200 Subject: [PATCH 496/520] pg: fix auto-login; rememberMe was removed --- prime-gaming.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index 7ef528b..3077717 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -55,7 +55,7 @@ try { await page.fill('[name=email]', email); await page.click('input[type="submit"]'); await page.fill('[name=password]', password); - await page.check('[name=rememberMe]'); + // await page.check('[name=rememberMe]'); // no longer exists await page.click('input[type="submit"]'); page.waitForURL('**/ap/signin**').then(async () => { // check for wrong credentials const error = await page.locator('.a-alert-content').first().innerText(); From 726db4527be872e01ffa9b0a66df02c188fa3cd4 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 15 Apr 2025 17:26:27 +0200 Subject: [PATCH 497/520] Current free game title in blue --- epic-games.js | 3 ++- gog.js | 3 ++- prime-gaming.js | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/epic-games.js b/epic-games.js index f03350f..4db1e75 100644 --- a/epic-games.js +++ b/epic-games.js @@ -1,5 +1,6 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra 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'; @@ -192,7 +193,7 @@ try { const game_id = page.url().split('/').pop(); const existedInDb = db.data[user][game_id]; db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only! - console.log('Current free game:', title); + console.log('Current free game:', chalk.blue(title)); if (bundle_includes) console.log(' This bundle includes:', bundle_includes); const notify_game = { title, url, status: 'failed' }; notify_games.push(notify_game); // status is updated below diff --git a/gog.js b/gog.js index c4d5078..6269fc2 100644 --- a/gog.js +++ b/gog.js @@ -1,4 +1,5 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra +import chalk from 'chalk'; import { resolve, jsonDb, datetime, filenamify, prompt, notify, html_game_list, handleSIGINT } from './src/util.js'; import { cfg } from './src/config.js'; @@ -102,7 +103,7 @@ try { const match_all = text.match(/Claim (.*) and don't miss the|Success! (.*) was added to/); const title = match_all[1] ? match_all[1] : match_all[2]; const url = await banner.locator('a').first().getAttribute('href'); - console.log(`Current free game: ${title} - ${url}`); + console.log(`Current free game: ${chalk.blue(title)} - ${url}`); db.data[user][title] ||= { title, time: datetime(), url }; if (cfg.dryrun) process.exit(1); // await page.locator('#giveaway:not(.is-loading)').waitFor(); // otherwise screenshot is sometimes with loading indicator instead of game title; #TODO fix, skipped due to timeout, see #240 diff --git a/prime-gaming.js b/prime-gaming.js index 3077717..a425ebe 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -150,14 +150,14 @@ try { if (isNew) await p.close(); return daysLeft > cfg.pg_timeLeft; } - console.log('Number of free unclaimed games (Prime Gaming):', internal.length); + console.log('\nNumber of free unclaimed games (Prime Gaming):', internal.length); // claim games in internal store for (const card of internal) { await card.scrollIntoViewIfNeeded(); const title = await (await card.$('.item-card-details__body__primary')).innerText(); const slug = await (await card.$('a')).getAttribute('href'); const url = 'https://gaming.amazon.com' + slug.split('?')[0]; - console.log('Current free game:', title); + console.log('Current free game:', chalk.blue(title)); if (cfg.pg_timeLeft && await skipBasedOnTime(url)) continue; if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; @@ -168,7 +168,7 @@ try { // console.log('Image:', img); await card.screenshot({ path: screenshot('internal', `${filenamify(title)}.png`) }); } - console.log('Number of free unclaimed games (external stores):', external.length); + console.log('\nNumber of free unclaimed games (external stores):', external.length); // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft const external_info = []; for (const card of external) { // need to get data incl. URLs in this loop and then navigate in another, otherwise .all() would update after coming back and .elementHandles() like above would lead to error due to page navigation: elementHandle.$: Protocol error (Page.adoptNode) @@ -180,7 +180,7 @@ try { } // external_info = [ { title: 'Fallout 76 (XBOX)', url: 'https://gaming.amazon.com/fallout-76-xbox-fgwp/dp/amzn1.pg.item.9fe17d7b-b6c2-4f58-b494-cc4e79528d0b?ingress=amzn&ref_=SM_Fallout76XBOX_S01_FGWP_CRWN' } ]; for (const { title, url } of external_info) { - console.log('Current free game:', title); // , url); + console.log('Current free game:', chalk.blue(title)); // , url); await page.goto(url, { waitUntil: 'domcontentloaded' }); if (cfg.debug) await page.pause(); const item_text = await page.innerText('[data-a-target="DescriptionItemDetails"]'); From dee47d22184614a2856c354a65cc5cfc694e9d46 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 15 Apr 2025 17:27:21 +0200 Subject: [PATCH 498/520] pg: redeem gog: detect captcha --- prime-gaming.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/prime-gaming.js b/prime-gaming.js index a425ebe..dada15f 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -258,10 +258,14 @@ try { const r2 = page2.waitForResponse(r => r.request().method() == 'POST' && r.url().startsWith('https://redeem.gog.com/')); await page2.click('[type="submit"]'); // click Redeem const r2t = await (await r2).text(); + const reason2 = JSON.parse(r2t).reason; if (r2t == '{}') { redeem_action = 'redeemed'; console.log(' Redeemed successfully.'); db.data[user][title].status = 'claimed and redeemed'; + } else if (reason2?.includes('captcha')) { + redeem_action = 'redeem (got captcha)'; + console.error(' Got captcha; could not redeem!'); } else { console.debug(` Response 2: ${r2t}`); console.log(' Unknown Response 2 - please report in https://github.com/vogler/free-games-claimer/issues/5'); @@ -271,7 +275,7 @@ try { console.error(` Redeem on ${store} is experimental!`); // await page2.pause(); if (page2.url().startsWith('https://login.')) { - console.error(' Not logged in! Use the browser to login manually. Waiting for 60s.'); + console.error(' Not logged in! Please redeem the code above manually. You can now login in the browser for next time. Waiting for 60s.'); await page2.waitForTimeout(60 * 1000); redeem_action = 'redeem (login)'; } else { @@ -308,6 +312,7 @@ try { } } } else if (store == 'legacy games') { + // await page2.pause(); await page2.fill('[name=coupon_code]', code); await page2.fill('[name=email]', cfg.lg_email); await page2.fill('[name=email_validate]', cfg.lg_email); From 3e13e9fba858aeb5e5eff6144e0a6ddc28350c10 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 20:29:21 +0000 Subject: [PATCH 499/520] chore(deps): update super-linter/super-linter action to v7.4.0 --- .github/workflows/lint.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0ed6b5b..02ca3cb 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,7 +27,7 @@ jobs: fetch-depth: 0 - name: Super-linter - uses: super-linter/super-linter/slim@v7.3.0 # x-release-please-version + uses: super-linter/super-linter/slim@v7.4.0 # x-release-please-version # TODO need to create problem matchers for each linter? https://github.com/rhysd/actionlint/blob/v1.7.7/docs/usage.md#problem-matchers env: # To report GitHub Actions status checks From 5cfe57870a21382a6ce3607934f21f1368115016 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Tue, 13 May 2025 23:43:29 +0200 Subject: [PATCH 500/520] note on git describe for version --- src/version.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/version.js b/src/version.js index 7f875cf..bfcd12a 100644 --- a/src/version.js +++ b/src/version.js @@ -42,6 +42,11 @@ const gh = await (await fetch('https://api.github.com/repos/vogler/free-games-cl log('Local commit:', sha, new Date(date)); log('Online commit:', gh.sha, new Date(gh.commit.committer.date)); +// git describe --all --long --dirty +// --> heads/main-0-gdee47d2-dirty +// git describe --tags --long --dirty +// --> v1.7-35-gdee47d2-dirty + if (sha == gh.sha) { log('Running the latest version!'); } else { From 97ef14f5146ff27686a9c9c430aa82629e22c479 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 14 May 2025 00:18:22 +0200 Subject: [PATCH 501/520] uncommited steam-games.js from 2024-09-24 - WIP just saves all games and their stats --- steam-games.js | 73 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 steam-games.js diff --git a/steam-games.js b/steam-games.js new file mode 100644 index 0000000..9b307fc --- /dev/null +++ b/steam-games.js @@ -0,0 +1,73 @@ +import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra +import { jsonDb, prompt } from './src/util.js'; +import { cfg } from './src/config.js'; + +const db = await jsonDb('steam-games.json', {}); + +const user = cfg.steam_id || await prompt({ message: 'Enter Steam community id ("View my profile", then copy from URL)' }); + +// using https://github.com/apify/fingerprint-suite worked, but has no launchPersistentContext... +// from https://github.com/apify/fingerprint-suite/issues/162 +import { FingerprintInjector } from 'fingerprint-injector'; +import { FingerprintGenerator } from 'fingerprint-generator'; + +const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({ + devices: ["desktop"], + operatingSystems: ["windows"], +}); + +const context = await firefox.launchPersistentContext(cfg.dir.browser, { + headless: cfg.headless, + // viewport: { width: cfg.width, height: cfg.height }, + locale: 'en-US', // ignore OS locale to be sure to have english text for locators -> done via /en in URL + userAgent: fingerprint.navigator.userAgent, + viewport: { + width: fingerprint.screen.width, + height: fingerprint.screen.height, + }, + extraHTTPHeaders: { + 'accept-language': headers['accept-language'], + }, +}); +// await stealth(context); +await new FingerprintInjector().attachFingerprintToPlaywright(context, { fingerprint, headers }); + +context.setDefaultTimeout(cfg.debug ? 0 : cfg.timeout); + +const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist + +try { + await page.goto(`https://steamcommunity.com/id/${user}/games?tab=all`); + const games = page.locator('div[data-featuretarget="gameslist-root"] > div.Panel > div.Panel > div'); + await games.last().waitFor(); + await page.keyboard.press('End'); + await page.waitForLoadState('networkidle'); + console.log('All Games:', await games.count()); + for (const game of await games.all()) { + const title = await game.locator('span a').innerText(); + let time, last, achievements, size; + const ltime = game.locator('span:has-text("total played")'); + if (await ltime.count()) time = (await ltime.first().innerText()).split('\n')[1]; + const llast = game.locator('span:has-text("last played")'); + if (await llast.count()) last = (await llast.first().innerText()).split('\n')[1]; + const lachievements = game.locator('a:has-text("achievements") + span'); + if (await lachievements.count()) achievements = (await lachievements.first().innerText()).split('\n'); + const lsize = game.locator('span:has(+ button)'); + if (await lsize.count()) size = await lsize.first().innerText(); + const url = await game.locator('a').first().getAttribute('href'); + const img = await game.locator('img').first().getAttribute('src'); + const stat = { title, time, last, achievements, size, url, img }; + console.log(stat); + db.data[title] = stat; + } + + // await page.pause(); +} catch (error) { + process.exitCode ||= 1; + console.error('--- Exception:'); + console.error(error); // .toString()? +} finally { + await db.write(); // write out json db +} +if (page.video()) console.log('Recorded video:', await page.video().path()); +await context.close(); From 6aea18836dd47683bba7e6ca54ebc09c1397f5b6 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 14 May 2025 00:30:39 +0200 Subject: [PATCH 502/520] enquirer: esc is fine, but after first prompt SIGINT will be ignore; onRawSIGINT keeps process running -> switch to inquirer --- test/sigint-enquirer-raw-keeps-running.js | 21 +++++++++++++++++++++ test/sigint-enquirer-raw.js | 17 +++++++++-------- test/sigint-enquirer-simple.js | 5 +++++ 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 test/sigint-enquirer-raw-keeps-running.js diff --git a/test/sigint-enquirer-raw-keeps-running.js b/test/sigint-enquirer-raw-keeps-running.js new file mode 100644 index 0000000..23d9983 --- /dev/null +++ b/test/sigint-enquirer-raw-keeps-running.js @@ -0,0 +1,21 @@ +// open issue: prevents handleSIGINT() to work if prompt is cancelled with Ctrl-C instead of Escape: https://github.com/enquirer/enquirer/issues/372 +function onRawSIGINT(fn) { + const { stdin, stdout } = process; + stdin.setRawMode(true); + stdin.resume(); + stdin.on('data', data => { + const key = data.toString('utf-8'); + if (key === '\u0003') { // ctrl + c + fn(); + } else { + stdout.write(key); + } + }); +} +console.log(1) +onRawSIGINT(() => { + console.log('raw'); process.exit(1); +}); +console.log(2) + +// onRawSIGINT workaround for enquirer keeps the process from exiting here... diff --git a/test/sigint-enquirer-raw.js b/test/sigint-enquirer-raw.js index 0a95892..e6b538d 100644 --- a/test/sigint-enquirer-raw.js +++ b/test/sigint-enquirer-raw.js @@ -1,10 +1,10 @@ // https://github.com/enquirer/enquirer/issues/372 -import { prompt } from '../src/util.js'; +import { prompt, handleSIGINT } from '../src/util.js'; -const handleSIGINT = () => process.on('SIGINT', () => { // e.g. when killed by Ctrl-C - console.log('\nInterrupted by SIGINT. Exit!'); - process.exitCode = 130; -}); +// const handleSIGINT = () => process.on('SIGINT', () => { // e.g. when killed by Ctrl-C +// console.log('\nInterrupted by SIGINT. Exit!'); +// process.exitCode = 130; +// }); handleSIGINT(); function onRawSIGINT(fn) { @@ -20,15 +20,16 @@ function onRawSIGINT(fn) { } }); } -onRawSIGINT(() => { - console.log('raw'); process.exit(1); -}); +// onRawSIGINT(() => { +// console.log('raw'); process.exit(1); +// }); console.log('hello'); console.error('hello error'); try { let i = 'foo'; i = await prompt(); // SIGINT no longer handled if this is executed + i = await prompt(); // SIGINT no longer handled if this is executed // handleSIGINT(); console.log('value:', i); setTimeout(() => console.log('timeout 3s'), 3000); diff --git a/test/sigint-enquirer-simple.js b/test/sigint-enquirer-simple.js index 13e7d02..25f13ee 100644 --- a/test/sigint-enquirer-simple.js +++ b/test/sigint-enquirer-simple.js @@ -13,3 +13,8 @@ await enquirer.prompt({ name: 'username', message: 'What is your username?', }); +await enquirer.prompt({ + type: 'input', + name: 'username', + message: 'What is your username 2?', +}); From a5ce5ec81615cb4d344a31553a2b23028e9ac451 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 14 May 2025 00:46:37 +0200 Subject: [PATCH 503/520] fix CodeQL alerts 5-7 https://github.com/vogler/free-games-claimer/security/code-scanning/5 https://github.com/vogler/free-games-claimer/security/code-scanning/6 https://github.com/vogler/free-games-claimer/security/code-scanning/7 --- .github/workflows/docker.yml | 3 +++ .github/workflows/sonar.yml | 7 ++++++- aliexpress.js | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 6371f4c..722206a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,6 +17,9 @@ on: branches: - "main" # only PRs against main +permissions: + contents: read + jobs: docker: runs-on: ubuntu-latest diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index eb4ad62..29f81c6 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -1,3 +1,5 @@ +name: Sonar + on: # Trigger analysis when pushing in main or pull requests, and when creating a pull request. push: @@ -5,7 +7,10 @@ on: - main pull_request: types: [opened, synchronize, reopened] -name: Sonar + +permissions: + contents: read + jobs: sonarcloud: runs-on: ubuntu-latest diff --git a/aliexpress.js b/aliexpress.js index e2439a4..52e75a0 100644 --- a/aliexpress.js +++ b/aliexpress.js @@ -40,7 +40,7 @@ const auth = async (url) => { console.log('auth', url); await page.goto(url, { waitUntil: 'domcontentloaded' }); // redirects to https://login.aliexpress.com/?return_url=https%3A%2F%2Fwww.aliexpress.com%2Fp%2Fcoin-pc-index%2Findex.html - await Promise.any([page.waitForURL(/.*login.aliexpress.com.*/).then(async () => { + await Promise.any([page.waitForURL(/.*login\.aliexpress.com.*/).then(async () => { // manual login console.error('Not logged in! Will wait for 120s for you to login...'); // await page.waitForTimeout(120*1000); From 7c8682ac91b2c2a193dad4190972d2d1d5ccc592 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 14 May 2025 01:31:04 +0200 Subject: [PATCH 504/520] build docker image for each branch/PR? --- .github/workflows/docker.yml | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 722206a..360e72a 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -2,24 +2,20 @@ name: Build and push Docker image (amd64, arm64 to hub.docker.com and ghcr.io) on: workflow_dispatch: # allow manual trigger - # https://github.com/orgs/community/discussions/26276 - push: - branches: - - "main" - - "v*" - tags: - - "v*" - paths: # ignore changes to certain files + push: # build for each branch + # branches: ["main"] + paths: # ignore changes to .md files - '**' - '!*.md' # - '!.github/**' - pull_request: # runs when opened/reopned or when the head branch is updated, see https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request - branches: - - "main" # only PRs against main + pull_request: # runs when opened/reopned or when the head branch is updated permissions: contents: read +env: + BRANCH: ${{ github.head_ref || github.ref_name }} # head_ref/base_ref are only set for PRs, for branches ref_name will be used + jobs: docker: runs-on: ubuntu-latest @@ -30,13 +26,11 @@ jobs: - name: Set environment variables run: | - BRANCH="${GITHUB_REF#refs/heads/}" - echo "BRANCH=$BRANCH" >> "$GITHUB_ENV" - echo "NOW=$(date -R)" >> "$GITHUB_ENV" # date -Iseconds; date +'%Y-%m-%dT%H:%M:%S' + echo "NOW=$(date -R)" >> $GITHUB_ENV # date -Iseconds; date +'%Y-%m-%dT%H:%M:%S' if [[ "$BRANCH" == "main" ]]; then - echo "IMAGE_TAG=latest" >> "$GITHUB_ENV" + echo "IMAGE_TAG=latest" >> $GITHUB_ENV else - echo "IMAGE_TAG=$BRANCH" >> "$GITHUB_ENV" + echo "IMAGE_TAG=$BRANCH" >> $GITHUB_ENV fi - name: Set up QEMU @@ -47,7 +41,7 @@ jobs: - name: Login to Docker Hub uses: docker/login-action@v3 - if: github.event_name != 'pull_request' # TODO if DOCKERHUB_* are set? + if: ${{ secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }} with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -61,16 +55,15 @@ jobs: - name: Build and push uses: docker/build-push-action@v6 - # if: github.event_name != 'pull_request' # still want to build image + if: ${{ env.IMAGE_TAG != '' }} with: context: . - push: ${{ github.event_name != 'pull_request' }} # TODO push for forks? + push: ${{ secrets.DOCKERHUB_USERNAME != '' }} build-args: | COMMIT=${{ github.sha }} BRANCH=${{ env.BRANCH }} NOW=${{ env.NOW }} - platforms: linux/amd64,linux/arm64 # ,linux/arm/v7 - # TODO docker tag only if DOCKERHUB_* are set? + platforms: linux/amd64,linux/arm64 tags: | ${{ secrets.DOCKERHUB_USERNAME }}/free-games-claimer:${{env.IMAGE_TAG}} ghcr.io/${{ github.actor }}/free-games-claimer:${{env.IMAGE_TAG}} From 4ce0d44b793a2298374a4cb4ffc6014f56dee366 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 14 May 2025 01:44:14 +0200 Subject: [PATCH 505/520] can't check secrets in if expr... --- .github/workflows/docker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 360e72a..26ce96d 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -41,7 +41,7 @@ jobs: - name: Login to Docker Hub uses: docker/login-action@v3 - if: ${{ secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }} + # if: ${{ secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != '' }} # does not work: Unrecognized named-value: 'secrets' - https://www.cloudtruth.com/blog/skipping-jobs-in-github-actions-when-secrets-are-unavailable-securely-inject-configuration-secrets-into-github with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} From bf0c9032cc4fc5a27e5366c0770992cc01a79e4b Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Wed, 14 May 2025 01:59:37 +0200 Subject: [PATCH 506/520] need 'packages: write' permission for ghcr.io --- .github/workflows/docker.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 26ce96d..2c42006 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -12,6 +12,7 @@ on: permissions: contents: read + packages: write env: BRANCH: ${{ github.head_ref || github.ref_name }} # head_ref/base_ref are only set for PRs, for branches ref_name will be used From 0a4cac35717722b51b4d795f3a4fd8c3eaa2da16 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Thu, 15 May 2025 23:51:49 +0200 Subject: [PATCH 507/520] ncu -u --- package-lock.json | 1998 ++++++++++++++++++++++++++++++++++++++++----- package.json | 12 +- 2 files changed, 1814 insertions(+), 196 deletions(-) diff --git a/package-lock.json b/package-lock.json index dd57aa8..9ab1fff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,19 +9,19 @@ "version": "1.4.0", "license": "AGPL-3.0-only", "dependencies": { - "chalk": "^5.3.0", + "chalk": "^5.4.1", "cross-env": "^7.0.3", - "dotenv": "^16.4.5", + "dotenv": "^16.5.0", "enquirer": "^2.4.1", - "fingerprint-injector": "^2.1.52", + "fingerprint-injector": "^2.1.66", "lowdb": "^7.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.45.0", + "playwright-firefox": "^1.52.0", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { - "@stylistic/eslint-plugin-js": "^4.0.0", - "eslint": "^9.5.0" + "@stylistic/eslint-plugin-js": "^4.2.0", + "eslint": "^9.26.0" }, "engines": { "node": ">=17" @@ -61,10 +61,11 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "dev": true, + "license": "Apache-2.0", "dependencies": { "@eslint/object-schema": "^2.1.6", "debug": "^4.3.1", @@ -74,11 +75,22 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "node_modules/@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", + "dev": true, + "license": "Apache-2.0", "dependencies": { "@types/json-schema": "^7.0.15" }, @@ -87,10 +99,11 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", - "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, + "license": "MIT", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", @@ -110,10 +123,11 @@ } }, "node_modules/@eslint/js": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", - "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", + "version": "9.26.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", + "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", "dev": true, + "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } @@ -123,17 +137,19 @@ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", "dev": true, + "license": "Apache-2.0", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, "node_modules/@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "dev": true, + "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.13.0", "levn": "^0.4.1" }, "engines": { @@ -189,6 +205,28 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@modelcontextprotocol/sdk": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.3.tgz", + "integrity": "sha512-rmOWVRUbUJD7iSvJugjUbFZshTAuJ48MXoZ80Osx1GM0K/H1w7rSEvmw8m6vdWxNASgtaHIhAgre4H/E9GJiYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@otplib/core": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", @@ -272,9 +310,10 @@ } }, "node_modules/@types/debug": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", - "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "license": "MIT", "dependencies": { "@types/ms": "*" } @@ -293,9 +332,24 @@ "license": "MIT" }, "node_modules/@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } }, "node_modules/acorn": { "version": "8.14.1", @@ -387,6 +441,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -396,6 +451,27 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -406,9 +482,9 @@ } }, "node_modules/browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "funding": [ { "type": "opencollective", @@ -425,10 +501,10 @@ ], "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" @@ -437,6 +513,47 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -447,9 +564,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001709", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001709.tgz", - "integrity": "sha512-NgL3vUTnDrPCZ3zTahp4fsugQ4dc7EKTSzwQDPEel6DMoMnfH2jhry9n2Zm8onbSR+f/QtKHFOA+iAQu4kbtWA==", + "version": "1.0.30001717", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", + "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==", "funding": [ { "type": "opencollective", @@ -481,6 +598,7 @@ "version": "0.2.4", "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", "integrity": "sha512-we+NuQo2DHhSl+DP6jlUiAhyAjBQrYnpOk15rN6c6JSPScjiCLh8IbSU+VTcph6YS3o7mASE8a0+gbZ7ChLpgg==", + "license": "MIT", "dependencies": { "for-own": "^0.1.3", "is-plain-object": "^2.0.1", @@ -515,6 +633,63 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -546,11 +721,12 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -571,10 +747,21 @@ "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/dot-prop": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", @@ -591,9 +778,10 @@ } }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", + "license": "BSD-2-Clause", "engines": { "node": ">=12" }, @@ -601,12 +789,44 @@ "url": "https://dotenvx.com" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true, + "license": "MIT" + }, "node_modules/electron-to-chromium": { - "version": "1.5.130", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.130.tgz", - "integrity": "sha512-Ou2u7L9j2XLZbhqzyX0jWDj6gA8D3jIfVzt4rikLf3cGBa0VdReuFimBKS9tQJA4+XpeCxj1NoWlfBXzbMa9IA==", + "version": "1.5.150", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.150.tgz", + "integrity": "sha512-rOOkP2ZUMx1yL4fCxXQKDHQ8ZXwisb2OycOQVKHgvB3ZI4CvehOd4y2tfnnLDieJ3Zs1RL1Dlp3cMkyIn7nnXA==", "license": "ISC" }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/enquirer": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", @@ -619,6 +839,39 @@ "node": ">=8.6" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -628,6 +881,13 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true, + "license": "MIT" + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -641,21 +901,24 @@ } }, "node_modules/eslint": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", - "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", + "version": "9.26.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz", + "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", "dev": true, + "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.21.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.13.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.26.0", + "@eslint/plugin-kit": "^0.2.8", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", + "@modelcontextprotocol/sdk": "^1.8.0", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -663,7 +926,7 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", + "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", @@ -679,7 +942,8 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3" + "optionator": "^0.9.3", + "zod": "^3.24.2" }, "bin": { "eslint": "bin/eslint.js" @@ -700,10 +964,11 @@ } }, "node_modules/eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -841,6 +1106,98 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eventsource-parser": "^3.0.1" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/eventsource-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -874,6 +1231,24 @@ "node": ">=16.0.0" } }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -891,13 +1266,13 @@ } }, "node_modules/fingerprint-generator": { - "version": "2.1.63", - "resolved": "https://registry.npmjs.org/fingerprint-generator/-/fingerprint-generator-2.1.63.tgz", - "integrity": "sha512-9ud3dO2aD0wKc9/zEAletQZ/iPuGqTwmkA7Y33OeOv/N+OvTM6OStxXbNLCsGSD7jUYe4ei/TK9uXwiQntbZGA==", + "version": "2.1.66", + "resolved": "https://registry.npmjs.org/fingerprint-generator/-/fingerprint-generator-2.1.66.tgz", + "integrity": "sha512-2CvoY+OPcCOWkoIMQim80uNH+ED1+2rM9QXIcSih7ovBMLOmyr3Sp9IOtfccd05QlGDzulU2M9Oav8jOgTlCBA==", "license": "Apache-2.0", "dependencies": { - "generative-bayesian-network": "^2.1.63", - "header-generator": "^2.1.63", + "generative-bayesian-network": "^2.1.66", + "header-generator": "^2.1.66", "tslib": "^2.4.0" }, "engines": { @@ -905,12 +1280,12 @@ } }, "node_modules/fingerprint-injector": { - "version": "2.1.63", - "resolved": "https://registry.npmjs.org/fingerprint-injector/-/fingerprint-injector-2.1.63.tgz", - "integrity": "sha512-OZpxOKi4iyU2hzqXuJDddb5ayfLgWJvnaDwWAExC5/P+XuXbAMf9cLIOAEQ3B2SnAbXUXZw6k8vQqeg6vSrWIA==", + "version": "2.1.66", + "resolved": "https://registry.npmjs.org/fingerprint-injector/-/fingerprint-injector-2.1.66.tgz", + "integrity": "sha512-h5llsoG0xoDeEo2czjzvo1niEU0xgCMwhs5/jtAxiBf7IiP/wW1Is3DJMEB+4V4PwIvqNQqLlnD07X23D7tErA==", "license": "Apache-2.0", "dependencies": { - "fingerprint-generator": "^2.1.63", + "fingerprint-generator": "^2.1.66", "tslib": "^2.4.0" }, "engines": { @@ -954,6 +1329,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -962,6 +1338,7 @@ "version": "0.1.5", "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", "integrity": "sha512-SKmowqGTJoPzLO1T0BBJpkfp3EMacCMOuH40hOUbrbzElVktk4DioXVM99QkLCyKoiuOmyjgcWMpVz2xjE7LZw==", + "license": "MIT", "dependencies": { "for-in": "^1.0.1" }, @@ -969,10 +1346,31 @@ "node": ">=0.10.0" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", @@ -985,22 +1383,74 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/generative-bayesian-network": { - "version": "2.1.63", - "resolved": "https://registry.npmjs.org/generative-bayesian-network/-/generative-bayesian-network-2.1.63.tgz", - "integrity": "sha512-nH1t4R9nlWSmvFoI4DEcpXd0+yoGZcySVuUBkXhR09/Mf7O9AWFmR8lWqVGIoxpBS0WRNpV/qj4swKGGQAxAPQ==", + "version": "2.1.66", + "resolved": "https://registry.npmjs.org/generative-bayesian-network/-/generative-bayesian-network-2.1.66.tgz", + "integrity": "sha512-gbBsyaaEJj/LHp3473TQrMDdcKiRzI8Sn2CbcG/lwONZkp0n9/ChC1mjzcbZQtHHCuqjn+JouSSbzLeepUMbuw==", "license": "Apache-2.0", "dependencies": { "adm-zip": "^0.5.9", "tslib": "^2.4.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1041,10 +1491,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" }, "node_modules/has-flag": { "version": "4.0.0", @@ -1055,14 +1519,40 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/header-generator": { - "version": "2.1.63", - "resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.63.tgz", - "integrity": "sha512-dicAWZb/zXmI3fPuCw1Ra1sTTC9x9cc5EOhwugmI/keXnRc3BzxDQTGHge+MeAdqhpo2ZOFTEV6u08lXJUA2uQ==", + "version": "2.1.66", + "resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.66.tgz", + "integrity": "sha512-g0jd79o0CyzyK0Jega4pAG1eJhykhPNfBLpOnUINtX2YkToVvRSBZ+B2wtmIjqwKHXK8DNWxylKuXnZmLs1yMQ==", "license": "Apache-2.0", "dependencies": { "browserslist": "^4.21.1", - "generative-bayesian-network": "^2.1.63", + "generative-bayesian-network": "^2.1.66", "ow": "^0.28.1", "tslib": "^2.4.0" }, @@ -1070,10 +1560,40 @@ "node": ">=16.0.0" } }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -1081,9 +1601,9 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "license": "MIT", "dependencies": { @@ -1110,6 +1630,8 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1118,17 +1640,30 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.10" + } }, "node_modules/is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "license": "MIT" }, "node_modules/is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1167,6 +1702,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "license": "MIT", "dependencies": { "isobject": "^3.0.1" }, @@ -1174,6 +1710,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -1183,6 +1726,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1224,6 +1768,7 @@ "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -1245,6 +1790,7 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "license": "MIT", "dependencies": { "is-buffer": "^1.1.5" }, @@ -1256,6 +1802,7 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1316,10 +1863,31 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/merge-deep": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", "integrity": "sha512-qtmzAS6t6grwEkNrunqTBdn0qKwFgNWvlxUbAV8es9M7Ot1EbyApytCnvE0jALPa46ZpKDUo527kKiaWplmlFA==", + "license": "MIT", "dependencies": { "arr-union": "^3.1.0", "clone-deep": "^0.2.4", @@ -1329,6 +1897,42 @@ "node": ">=0.10.0" } }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1344,6 +1948,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/mixin-object/-/mixin-object-2.0.1.tgz", "integrity": "sha512-ALGF1Jt9ouehcaXaHhn6t1yGWRqGaHkPFndtFVHfZXOvkIZ/yoGaSi0AHVTafb3ZBGg4dr/bDwnaEKqCXzchMA==", + "license": "MIT", "dependencies": { "for-in": "^0.1.3", "is-extendable": "^0.1.1" @@ -1356,14 +1961,16 @@ "version": "0.1.8", "resolved": "https://registry.npmjs.org/for-in/-/for-in-0.1.8.tgz", "integrity": "sha512-F0to7vbBSHP8E3l6dCjxNOLuSFAACIxFy3UehTUlG7svlXi37HHsDkyVcHo0Pq8QwrE+pXvWSVX3ZT1T9wAZ9g==", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" }, "node_modules/natural-compare": { "version": "1.4.0", @@ -1371,16 +1978,63 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==", "license": "MIT" }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", "dependencies": { "wrappy": "1" } @@ -1474,6 +2128,16 @@ "node": ">=6" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1487,6 +2151,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1499,16 +2164,36 @@ "node": ">=8" } }, + "node_modules/path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16.20.0" + } + }, "node_modules/playwright-core": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", - "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==", "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" @@ -1518,13 +2203,13 @@ } }, "node_modules/playwright-firefox": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.45.0.tgz", - "integrity": "sha512-JmGESfFR8xTjAYQzECYO00yBbSSnu4dBImsrmJVeOXTvT+i9p1dpVUaxKz6lTFMI/xzYROqB4E4Km8NBiOgslw==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.52.0.tgz", + "integrity": "sha512-x4niD4HHffaYNmz0XwgXqvf7NUhCmAbcpa+DX16kAacd+6T8cwf+W/+Q0S+VLsYpQ2BJowR2HuJanO1Ge1Phzg==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "playwright-core": "1.45.0" + "playwright-core": "1.52.0" }, "bin": { "playwright": "cli.js" @@ -1542,6 +2227,20 @@ "node": ">= 0.8.0" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -1556,6 +2255,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin/-/puppeteer-extra-plugin-3.2.3.tgz", "integrity": "sha512-6RNy0e6pH8vaS3akPIKGg28xcryKscczt4wIl0ePciZENGE2yoaQJNd17UiEbdmh5/6WW6dPcfRWT9lxBwCi2Q==", + "license": "MIT", "dependencies": { "@types/debug": "^4.1.0", "debug": "^4.1.1", @@ -1581,6 +2281,7 @@ "version": "2.11.2", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-stealth/-/puppeteer-extra-plugin-stealth-2.11.2.tgz", "integrity": "sha512-bUemM5XmTj9i2ZerBzsk2AN5is0wHMNE6K0hXBzBXOzP5m5G3Wl0RHhiqKeHToe/uIH8AoZiGhc1tCkLZQPKTQ==", + "license": "MIT", "dependencies": { "debug": "^4.1.1", "puppeteer-extra-plugin": "^3.2.3", @@ -1606,6 +2307,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-data-dir/-/puppeteer-extra-plugin-user-data-dir-2.4.1.tgz", "integrity": "sha512-kH1GnCcqEDoBXO7epAse4TBPJh9tEpVEK/vkedKfjOVOhZAvLkHGc9swMs5ChrJbRnf8Hdpug6TJlEuimXNQ+g==", + "license": "MIT", "dependencies": { "debug": "^4.1.1", "fs-extra": "^10.0.0", @@ -1632,6 +2334,7 @@ "version": "2.4.1", "resolved": "https://registry.npmjs.org/puppeteer-extra-plugin-user-preferences/-/puppeteer-extra-plugin-user-preferences-2.4.1.tgz", "integrity": "sha512-i1oAZxRbc1bk8MZufKCruCEC3CCafO9RKMkkodZltI4OqibLFXF3tj6HZ4LZ9C5vCXZjYcDWazgtY69mnmrQ9A==", + "license": "MIT", "dependencies": { "debug": "^4.1.1", "deepmerge": "^4.2.2", @@ -1654,6 +2357,48 @@ } } }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -1668,6 +2413,8 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", "dependencies": { "glob": "^7.1.3" }, @@ -1678,10 +2425,102 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true, + "license": "MIT" + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true, + "license": "ISC" + }, "node_modules/shallow-clone": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", "integrity": "sha512-J1zdXCky5GmNnuauESROVu31MQSnLoYvlyEn6j2Ztk6Q5EHFIhxkMhYcv6vuDzl2XEzoRr856QwzMgWM/TmZgw==", + "license": "MIT", "dependencies": { "is-extendable": "^0.1.1", "kind-of": "^2.0.1", @@ -1696,6 +2535,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-2.0.1.tgz", "integrity": "sha512-0u8i1NZ/mg0b+W3MGGw5I7+6Eib2nx72S/QvXa0hYjEkjTknYmEYQJwGu3mLC0BrhtJjtQafTkyRUQ75Kx0LVg==", + "license": "MIT", "dependencies": { "is-buffer": "^1.0.2" }, @@ -1707,6 +2547,7 @@ "version": "0.2.7", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-0.2.7.tgz", "integrity": "sha512-gkX52wvU/R8DVMMt78ATVPFMJqfW8FPz1GZ1sVHBVQHmu/WvhIWE4cE1GBzhJNFicDeYhnwp6Rl35BcAIM3YOQ==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1730,6 +2571,92 @@ "node": ">=8" } }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/steno": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", @@ -1786,6 +2713,16 @@ "node": ">=0.2.6" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -1804,14 +2741,40 @@ "node": ">= 0.8.0" } }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "license": "MIT", "engines": { "node": ">= 10.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz", @@ -1861,6 +2824,16 @@ "node": ">=0.10.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1878,7 +2851,8 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" }, "node_modules/yocto-queue": { "version": "0.1.0", @@ -1891,6 +2865,26 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", + "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "dev": true, + "license": "ISC", + "peerDependencies": { + "zod": "^3.24.1" + } } }, "dependencies": { @@ -1916,9 +2910,9 @@ "dev": true }, "@eslint/config-array": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", - "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", + "version": "0.20.0", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.20.0.tgz", + "integrity": "sha512-fxlS1kkIjx8+vy2SjuCB94q3htSNrufYTXubwiBFeaQHbH6Ipi43gFJq2zCMt6PHhImH3Xmr0NksKDvchWlpQQ==", "dev": true, "requires": { "@eslint/object-schema": "^2.1.6", @@ -1926,19 +2920,25 @@ "minimatch": "^3.1.2" } }, + "@eslint/config-helpers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.2.2.tgz", + "integrity": "sha512-+GPzk8PlG0sPpzdU5ZvIRMPidzAnZDl/s9L+y13iodqvb8leL53bTannOrQ/Im7UkpsmFU5Ily5U60LWixnmLg==", + "dev": true + }, "@eslint/core": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", - "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz", + "integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==", "dev": true, "requires": { "@types/json-schema": "^7.0.15" } }, "@eslint/eslintrc": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", - "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz", + "integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==", "dev": true, "requires": { "ajv": "^6.12.4", @@ -1953,9 +2953,9 @@ } }, "@eslint/js": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", - "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", + "version": "9.26.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", + "integrity": "sha512-I9XlJawFdSMvWjDt6wksMCrgns5ggLNfFwFvnShsleWruvXM514Qxk8V246efTw+eo9JABvVz+u3q2RiAowKxQ==", "dev": true }, "@eslint/object-schema": { @@ -1965,12 +2965,12 @@ "dev": true }, "@eslint/plugin-kit": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", - "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz", + "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==", "dev": true, "requires": { - "@eslint/core": "^0.12.0", + "@eslint/core": "^0.13.0", "levn": "^0.4.1" } }, @@ -2002,6 +3002,24 @@ "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", "dev": true }, + "@modelcontextprotocol/sdk": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.3.tgz", + "integrity": "sha512-rmOWVRUbUJD7iSvJugjUbFZshTAuJ48MXoZ80Osx1GM0K/H1w7rSEvmw8m6vdWxNASgtaHIhAgre4H/E9GJiYQ==", + "dev": true, + "requires": { + "content-type": "^1.0.5", + "cors": "^2.8.5", + "cross-spawn": "^7.0.5", + "eventsource": "^3.0.2", + "express": "^5.0.1", + "express-rate-limit": "^7.5.0", + "pkce-challenge": "^5.0.0", + "raw-body": "^3.0.0", + "zod": "^3.23.8", + "zod-to-json-schema": "^3.24.1" + } + }, "@otplib/core": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/@otplib/core/-/core-12.0.1.tgz", @@ -2068,9 +3086,9 @@ } }, "@types/debug": { - "version": "4.1.8", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", - "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", "requires": { "@types/ms": "*" } @@ -2088,9 +3106,19 @@ "dev": true }, "@types/ms": { - "version": "0.7.31", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", - "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", + "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==" + }, + "accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "dev": true, + "requires": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + } }, "acorn": { "version": "8.14.1", @@ -2157,6 +3185,23 @@ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, + "body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "dev": true, + "requires": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -2167,14 +3212,40 @@ } }, "browserslist": { - "version": "4.24.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.4.tgz", - "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==", + "version": "4.24.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.5.tgz", + "integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==", "requires": { - "caniuse-lite": "^1.0.30001688", - "electron-to-chromium": "^1.5.73", + "caniuse-lite": "^1.0.30001716", + "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", - "update-browserslist-db": "^1.1.1" + "update-browserslist-db": "^1.1.3" + } + }, + "bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "dev": true + }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" } }, "callsites": { @@ -2183,9 +3254,9 @@ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, "caniuse-lite": { - "version": "1.0.30001709", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001709.tgz", - "integrity": "sha512-NgL3vUTnDrPCZ3zTahp4fsugQ4dc7EKTSzwQDPEel6DMoMnfH2jhry9n2Zm8onbSR+f/QtKHFOA+iAQu4kbtWA==" + "version": "1.0.30001717", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001717.tgz", + "integrity": "sha512-auPpttCq6BDEG8ZAuHJIplGw6GODhjw+/11e7IjpnYCxZcW/ONgPs0KVBJ0d1bY3e2+7PRe5RCLyP+PfwVgkYw==" }, "chalk": { "version": "5.4.1", @@ -2224,6 +3295,43 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, + "content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "dev": true, + "requires": { + "safe-buffer": "5.2.1" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "dev": true + }, + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "dev": true + }, + "cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "dev": true + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dev": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cross-env": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-env/-/cross-env-7.0.3.tgz", @@ -2243,11 +3351,11 @@ } }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "deep-is": { @@ -2261,6 +3369,12 @@ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" }, + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + }, "dot-prop": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", @@ -2270,14 +3384,37 @@ } }, "dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==" + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==" + }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "dev": true }, "electron-to-chromium": { - "version": "1.5.130", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.130.tgz", - "integrity": "sha512-Ou2u7L9j2XLZbhqzyX0jWDj6gA8D3jIfVzt4rikLf3cGBa0VdReuFimBKS9tQJA4+XpeCxj1NoWlfBXzbMa9IA==" + "version": "1.5.150", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.150.tgz", + "integrity": "sha512-rOOkP2ZUMx1yL4fCxXQKDHQ8ZXwisb2OycOQVKHgvB3ZI4CvehOd4y2tfnnLDieJ3Zs1RL1Dlp3cMkyIn7nnXA==" + }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true }, "enquirer": { "version": "2.4.1", @@ -2288,11 +3425,38 @@ "strip-ansi": "^6.0.1" } }, + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true + }, + "es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true + }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0" + } + }, "escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "dev": true + }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2300,21 +3464,23 @@ "dev": true }, "eslint": { - "version": "9.21.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", - "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", + "version": "9.26.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.26.0.tgz", + "integrity": "sha512-Hx0MOjPh6uK9oq9nVsATZKE/Wlbai7KFjfCuw9UHaguDW3x+HF0O5nIi3ud39TWgrTjTO5nHxmL3R1eANinWHQ==", "dev": true, "requires": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.19.2", - "@eslint/core": "^0.12.0", - "@eslint/eslintrc": "^3.3.0", - "@eslint/js": "9.21.0", - "@eslint/plugin-kit": "^0.2.7", + "@eslint/config-array": "^0.20.0", + "@eslint/config-helpers": "^0.2.1", + "@eslint/core": "^0.13.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.26.0", + "@eslint/plugin-kit": "^0.2.8", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", + "@modelcontextprotocol/sdk": "^1.8.0", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", @@ -2322,7 +3488,7 @@ "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.2.0", + "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", @@ -2338,7 +3504,8 @@ "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.3" + "optionator": "^0.9.3", + "zod": "^3.24.2" }, "dependencies": { "@humanwhocodes/retry": { @@ -2366,9 +3533,9 @@ } }, "eslint-scope": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", - "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.3.0.tgz", + "integrity": "sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==", "dev": true, "requires": { "esrecurse": "^4.3.0", @@ -2430,6 +3597,69 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true + }, + "eventsource": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", + "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", + "dev": true, + "requires": { + "eventsource-parser": "^3.0.1" + } + }, + "eventsource-parser": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.2.tgz", + "integrity": "sha512-6RxOBZ/cYgd8usLwsEl+EC09Au/9BcmCKYF2/xbml6DNczf7nv0MQb+7BA2F+li6//I+28VNlQR37XfQtcAJuA==", + "dev": true + }, + "express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "dev": true, + "requires": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + } + }, + "express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "dev": true, + "requires": {} + }, "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2457,6 +3687,20 @@ "flat-cache": "^4.0.0" } }, + "finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "dev": true, + "requires": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + } + }, "find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2468,21 +3712,21 @@ } }, "fingerprint-generator": { - "version": "2.1.63", - "resolved": "https://registry.npmjs.org/fingerprint-generator/-/fingerprint-generator-2.1.63.tgz", - "integrity": "sha512-9ud3dO2aD0wKc9/zEAletQZ/iPuGqTwmkA7Y33OeOv/N+OvTM6OStxXbNLCsGSD7jUYe4ei/TK9uXwiQntbZGA==", + "version": "2.1.66", + "resolved": "https://registry.npmjs.org/fingerprint-generator/-/fingerprint-generator-2.1.66.tgz", + "integrity": "sha512-2CvoY+OPcCOWkoIMQim80uNH+ED1+2rM9QXIcSih7ovBMLOmyr3Sp9IOtfccd05QlGDzulU2M9Oav8jOgTlCBA==", "requires": { - "generative-bayesian-network": "^2.1.63", - "header-generator": "^2.1.63", + "generative-bayesian-network": "^2.1.66", + "header-generator": "^2.1.66", "tslib": "^2.4.0" } }, "fingerprint-injector": { - "version": "2.1.63", - "resolved": "https://registry.npmjs.org/fingerprint-injector/-/fingerprint-injector-2.1.63.tgz", - "integrity": "sha512-OZpxOKi4iyU2hzqXuJDddb5ayfLgWJvnaDwWAExC5/P+XuXbAMf9cLIOAEQ3B2SnAbXUXZw6k8vQqeg6vSrWIA==", + "version": "2.1.66", + "resolved": "https://registry.npmjs.org/fingerprint-injector/-/fingerprint-injector-2.1.66.tgz", + "integrity": "sha512-h5llsoG0xoDeEo2czjzvo1niEU0xgCMwhs5/jtAxiBf7IiP/wW1Is3DJMEB+4V4PwIvqNQqLlnD07X23D7tErA==", "requires": { - "fingerprint-generator": "^2.1.63", + "fingerprint-generator": "^2.1.66", "tslib": "^2.4.0" } }, @@ -2515,6 +3759,18 @@ "for-in": "^1.0.1" } }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "dev": true + }, + "fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true + }, "fs-extra": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", @@ -2530,15 +3786,49 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, "generative-bayesian-network": { - "version": "2.1.63", - "resolved": "https://registry.npmjs.org/generative-bayesian-network/-/generative-bayesian-network-2.1.63.tgz", - "integrity": "sha512-nH1t4R9nlWSmvFoI4DEcpXd0+yoGZcySVuUBkXhR09/Mf7O9AWFmR8lWqVGIoxpBS0WRNpV/qj4swKGGQAxAPQ==", + "version": "2.1.66", + "resolved": "https://registry.npmjs.org/generative-bayesian-network/-/generative-bayesian-network-2.1.66.tgz", + "integrity": "sha512-gbBsyaaEJj/LHp3473TQrMDdcKiRzI8Sn2CbcG/lwONZkp0n9/ChC1mjzcbZQtHHCuqjn+JouSSbzLeepUMbuw==", "requires": { "adm-zip": "^0.5.9", "tslib": "^2.4.0" } }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, "glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -2567,6 +3857,12 @@ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true + }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -2578,27 +3874,64 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, "header-generator": { - "version": "2.1.63", - "resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.63.tgz", - "integrity": "sha512-dicAWZb/zXmI3fPuCw1Ra1sTTC9x9cc5EOhwugmI/keXnRc3BzxDQTGHge+MeAdqhpo2ZOFTEV6u08lXJUA2uQ==", + "version": "2.1.66", + "resolved": "https://registry.npmjs.org/header-generator/-/header-generator-2.1.66.tgz", + "integrity": "sha512-g0jd79o0CyzyK0Jega4pAG1eJhykhPNfBLpOnUINtX2YkToVvRSBZ+B2wtmIjqwKHXK8DNWxylKuXnZmLs1yMQ==", "requires": { "browserslist": "^4.21.1", - "generative-bayesian-network": "^2.1.63", + "generative-bayesian-network": "^2.1.66", "ow": "^0.28.1", "tslib": "^2.4.0" } }, + "http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dev": true, + "requires": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + } + }, + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, "ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true }, "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dev": true, "requires": { "parent-module": "^1.0.0", @@ -2625,6 +3958,12 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "dev": true + }, "is-buffer": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", @@ -2663,6 +4002,12 @@ "isobject": "^3.0.1" } }, + "is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "dev": true + }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -2769,6 +4114,18 @@ "steno": "^4.0.2" } }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true + }, + "media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "dev": true + }, "merge-deep": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/merge-deep/-/merge-deep-3.0.3.tgz", @@ -2779,6 +4136,27 @@ "kind-of": "^3.0.2" } }, + "merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "dev": true + }, + "mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true + }, + "mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "dev": true, + "requires": { + "mime-db": "^1.54.0" + } + }, "minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -2804,9 +4182,9 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "natural-compare": { "version": "1.4.0", @@ -2814,11 +4192,38 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "dev": true + }, "node-releases": { "version": "2.0.19", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz", "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw==" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, + "object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true + }, + "on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -2890,6 +4295,12 @@ "callsites": "^3.0.0" } }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true + }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -2906,22 +4317,34 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, + "path-to-regexp": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", + "dev": true + }, "picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, + "pkce-challenge": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.0.tgz", + "integrity": "sha512-ueGLflrrnvwB3xuo/uGob5pd5FN7l0MsLf0Z87o/UQmRtwjvfylfc9MurIxRAWywCYTgrvpXBcqjV4OfCYGCIQ==", + "dev": true + }, "playwright-core": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.45.0.tgz", - "integrity": "sha512-lZmHlFQ0VYSpAs43dRq1/nJ9G/6SiTI7VPqidld9TDefL9tX87bTKExWZZUF5PeRyqtXqd8fQi2qmfIedkwsNQ==" + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.52.0.tgz", + "integrity": "sha512-l2osTgLXSMeuLZOML9qYODUQoPPnUsKsb5/P6LJ2e6uPKXUdPK5WYhN4z03G+YNbWmGDY4YENauNu4ZKczreHg==" }, "playwright-firefox": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.45.0.tgz", - "integrity": "sha512-JmGESfFR8xTjAYQzECYO00yBbSSnu4dBImsrmJVeOXTvT+i9p1dpVUaxKz6lTFMI/xzYROqB4E4Km8NBiOgslw==", + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/playwright-firefox/-/playwright-firefox-1.52.0.tgz", + "integrity": "sha512-x4niD4HHffaYNmz0XwgXqvf7NUhCmAbcpa+DX16kAacd+6T8cwf+W/+Q0S+VLsYpQ2BJowR2HuJanO1Ge1Phzg==", "requires": { - "playwright-core": "1.45.0" + "playwright-core": "1.52.0" } }, "prelude-ls": { @@ -2930,6 +4353,16 @@ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dev": true, + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, "punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -2978,6 +4411,33 @@ "puppeteer-extra-plugin-user-data-dir": "^2.4.1" } }, + "qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "dev": true, + "requires": { + "side-channel": "^1.1.0" + } + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "dev": true + }, + "raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dev": true, + "requires": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2992,6 +4452,68 @@ "glob": "^7.1.3" } }, + "router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "dev": true, + "requires": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true + }, + "send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "dev": true, + "requires": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + } + }, + "serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "dev": true, + "requires": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + } + }, + "setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, "shallow-clone": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-0.1.2.tgz", @@ -3031,6 +4553,60 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" }, + "side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + } + }, + "statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "dev": true + }, "steno": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/steno/-/steno-4.0.2.tgz", @@ -3064,6 +4640,12 @@ "resolved": "https://registry.npmjs.org/thirty-two/-/thirty-two-1.0.2.tgz", "integrity": "sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==" }, + "toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "dev": true + }, "tslib": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", @@ -3078,10 +4660,27 @@ "prelude-ls": "^1.2.1" } }, + "type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "dev": true, + "requires": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + } + }, "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "dev": true }, "update-browserslist-db": { "version": "1.1.3", @@ -3106,6 +4705,12 @@ "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", "integrity": "sha512-sgECfZthyaCKW10N0fm27cg8HYTFK5qMWgypqkXMQ4Wbl/zZKx7xZICgcoxIIE+WFAP/MBL2EFwC/YvLxw3Zeg==" }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "dev": true + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3124,6 +4729,19 @@ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true + }, + "zod": { + "version": "3.24.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.4.tgz", + "integrity": "sha512-OdqJE9UDRPwWsrHjLN2F8bPxvwJBK22EHLWtanu0LSYr5YqzsaaW3RMgmjwr8Rypg5k+meEJdSPXJZXE/yqOMg==", + "dev": true + }, + "zod-to-json-schema": { + "version": "3.24.5", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.24.5.tgz", + "integrity": "sha512-/AuWwMP+YqiPbsJx5D6TfgRTc4kTLjsh5SOcd4bLsfUg2RcEXrFMJl1DGgdHy2aCfsIA/cr/1JM0xcB2GZji8g==", + "dev": true, + "requires": {} } } } diff --git a/package.json b/package.json index 95027a2..0487277 100644 --- a/package.json +++ b/package.json @@ -20,18 +20,18 @@ "node": ">=17" }, "dependencies": { - "chalk": "^5.3.0", + "chalk": "^5.4.1", "cross-env": "^7.0.3", - "dotenv": "^16.4.5", + "dotenv": "^16.5.0", "enquirer": "^2.4.1", - "fingerprint-injector": "^2.1.52", + "fingerprint-injector": "^2.1.66", "lowdb": "^7.0.1", "otplib": "^12.0.1", - "playwright-firefox": "^1.45.0", + "playwright-firefox": "^1.52.0", "puppeteer-extra-plugin-stealth": "^2.11.2" }, "devDependencies": { - "@stylistic/eslint-plugin-js": "^4.0.0", - "eslint": "^9.5.0" + "@stylistic/eslint-plugin-js": "^4.2.0", + "eslint": "^9.26.0" } } From 6adc7e529a63dddc74663e5b5903adabd651b5f5 Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 16 May 2025 00:05:48 +0200 Subject: [PATCH 508/520] disable Renovate for now --- renovate.json | 1 + 1 file changed, 1 insertion(+) diff --git a/renovate.json b/renovate.json index 5db72dd..ecfd5ff 100644 --- a/renovate.json +++ b/renovate.json @@ -1,5 +1,6 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "enabled": false, "extends": [ "config:recommended" ] From cea5b11c955cc7fd49dcf9e6ddcf924b922a7e8f Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 16 May 2025 00:06:39 +0200 Subject: [PATCH 509/520] mv renovate.json .github/ --- renovate.json => .github/renovate.json | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename renovate.json => .github/renovate.json (100%) diff --git a/renovate.json b/.github/renovate.json similarity index 100% rename from renovate.json rename to .github/renovate.json From 991e0a1449ab7b931be467ee00c459b2679e1eca Mon Sep 17 00:00:00 2001 From: Ralf Vogler Date: Fri, 16 May 2025 00:14:10 +0200 Subject: [PATCH 510/520] docker: only build branches main, dev Otherwise branches will accumulate in 'Recent tagged image version' https://github.com/vogler/free-games-claimer/pkgs/container/free-games-claimer --- .github/workflows/docker.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2c42006..8c12487 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -1,9 +1,9 @@ name: Build and push Docker image (amd64, arm64 to hub.docker.com and ghcr.io) on: - workflow_dispatch: # allow manual trigger - push: # build for each branch - # branches: ["main"] + workflow_dispatch: # allows manual trigger + push: # push on branch + branches: [main, dev] paths: # ignore changes to .md files - '**' - '!*.md' From faf22aafb1db5c51af6bbad32abc2be2ad1274a3 Mon Sep 17 00:00:00 2001 From: nocci Date: Mon, 29 Dec 2025 11:41:09 +0000 Subject: [PATCH 511/520] =?UTF-8?q?=F0=9F=90=9B=20fix(prime-gaming):=20upd?= =?UTF-8?q?ate=20URL=20and=20selectors=20for=20claims?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - change URL_CLAIM to point to new Luna claims home - update selectors for sign-in and user verification - improve handling of cookies acceptance ✨ feat(prime-gaming): enhance game claiming logic - add support for new layout and game list detection - implement flexible scrolling for loading all game cards - refine logic for internal and external game claims - improve store identification for external claims ♻️ refactor(prime-gaming): modularize game tab and list location - extract functions for opening games tab and locating games list - enhance code readability and maintainability 🐛 fix(prime-gaming): handle dynamic selectors for availability dates - support multiple selectors for availability date detection - improve error handling and logging for missing elements --- prime-gaming.js | 156 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 125 insertions(+), 31 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index dada15f..8e755b8 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -7,7 +7,7 @@ import { cfg } from './src/config.js'; const screenshot = (...a) => resolve(cfg.dir.screenshots, 'prime-gaming', ...a); // const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? -const URL_CLAIM = 'https://gaming.amazon.com/home'; +const URL_CLAIM = 'https://luna.amazon.com/claims/home'; console.log(datetime(), 'started checking prime-gaming'); @@ -40,9 +40,14 @@ let user; try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever // need to wait for some elements to exist before checking if signed in or accepting cookies: - await Promise.any(['button:has-text("Sign in")', '[data-a-target="user-dropdown-first-name-text"]'].map(s => page.waitForSelector(s))); + await Promise.any([ + 'button:has-text("Sign in")', + 'button:has-text("Anmelden")', + '[data-a-target="user-dropdown-first-name-text"]', + '[data-testid="user-dropdown-first-name-text"]', + ].map(s => page.waitForSelector(s))); page.click('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")').catch(_ => { }); // to not waste screen space when non-headless, TODO does not work reliably, need to wait for something else first? - while (await page.locator('button:has-text("Sign in")').count() > 0) { + while (await page.locator('button:has-text("Sign in"), button:has-text("Anmelden")').count() > 0) { console.error('Not signed in anymore.'); await page.click('button:has-text("Sign in")'); if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); // give user some extra time to log in @@ -85,7 +90,7 @@ try { await page.waitForURL('https://gaming.amazon.com/home?signedIn=true'); if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); } - user = await page.locator('[data-a-target="user-dropdown-first-name-text"]').first().innerText(); + user = await page.locator('[data-a-target="user-dropdown-first-name-text"], [data-testid="user-dropdown-first-name-text"]').first().innerText(); console.log(`Signed in as ${user}`); // await page.click('button[aria-label="User dropdown and more options"]'); // const twitch = await page.locator('[data-a-target="TwitchDisplayName"]').first().innerText(); @@ -119,15 +124,83 @@ try { await page.waitForTimeout(3000); }); - await page.click('button[data-type="Game"]'); - const games = page.locator('div[data-a-target="offer-list-FGWP_FULL"]'); - await games.waitFor(); - // await scrollUntilStable(() => games.locator('.item-card__action').count()); // number of games - await scrollUntilStable(() => page.evaluate(() => document.querySelector('.tw-full-width').scrollHeight)); // height may change during loading while number of games is still the same? - console.log('Number of already claimed games (total):', await games.locator('p:has-text("Collected")').count()); - // can't use .all() since the list of elements via locator will change after click while we iterate over it - const internal = await games.locator('.item-card__action:has(button[data-a-target="FGWPOffer"])').elementHandles(); - const external = await games.locator('.item-card__action:has(a[data-a-target="FGWPOffer"])').all(); + const openGamesTab = async () => { + const selectors = [ + 'button[data-type="Game"]', // old layout + 'button:has-text("Games")', + '[data-test-selector="category-picker"] button:has-text("Games")', + '[data-testid="category-picker"] button:has-text("Games")', + ]; + for (const sel of selectors) { + const btn = page.locator(sel).first(); + if (await btn.count()) { + await btn.click(); + return; + } + } + // New Luna claims home already shows games list + }; + + await openGamesTab(); + + const locateGamesList = async () => { + const selectors = [ + 'div[data-a-target="offer-list-FGWP_FULL"]', // old layout + '[data-testid="offer-list"]', + '[data-test-selector="offer-list"]', + 'section:has(h2:has-text("Games with Prime"))', + 'section:has(h2:has-text("Games"))', + ]; + for (const sel of selectors) { + const loc = page.locator(sel).first(); + if (await loc.count()) return loc; + } + return null; + }; + + const games = await locateGamesList(); + // Load all cards (old and new layout) by scrolling the container or the page + if (games) await scrollUntilStable(() => games.evaluate(el => el.scrollHeight).catch(() => 0)); + await scrollUntilStable(() => page.evaluate(() => document.scrollingElement?.scrollHeight ?? 0)); + + const cards = []; + const anchorClaims = page.locator('a[href*="/claims/"][href*="amzn1.pg.item"]'); + if (await anchorClaims.count()) { + const hrefs = [...new Set(await anchorClaims.evaluateAll(anchors => anchors.map(a => a.getAttribute('href')).filter(Boolean)))]; + for (const href of hrefs) { + let url = href; + if (url.startsWith('/')) url = 'https://luna.amazon.com' + url; + const title = url.split('/claims/')[1]?.split('/')[0] || (await anchorClaims.first().innerText()) || 'Unknown title'; + cards.push({ kind: 'external', title, url }); + } + } + + if (!cards.length && games) { + const cardLocator = games.locator([ + '[data-testid="offer-card"]', + '[data-test-selector="offer-card"]', + '.item-card__action', + ].join(',')); + if (await cardLocator.count() === 0) { + console.log('No games found in list.'); + } else { + for (const handle of await cardLocator.elementHandles()) { + const text = (await handle.textContent() || '').toLowerCase(); + if (text.includes('collected')) continue; // skip already claimed + const title = await (await handle.$('h3, h4, [data-testid="item-card-title"], [data-test-selector="item-card-title"], .item-card-details__body__primary'))?.innerText() || 'Unknown title'; + const linkEl = await handle.$('a[href]'); + let url = linkEl && await linkEl.getAttribute('href'); + if (url?.startsWith('/')) url = 'https://gaming.amazon.com' + url; + const hasLinkClaim = await handle.$('a:has-text("Claim"), a:has-text("Get"), a:has-text("Details")'); + const hasButtonClaim = await handle.$('button:has-text("Claim"), button:has-text("Get"), button:has-text("Get game"), button:has-text("Play")'); + if (hasLinkClaim) cards.push({ kind: 'external', title, url }); + else if (hasButtonClaim) cards.push({ kind: 'internal', title, url, handle }); + } + } + } + + const internal = cards.filter(c => c.kind == 'internal'); + const external = cards.filter(c => c.kind == 'external'); // bottom to top: oldest to newest games internal.reverse(); external.reverse(); @@ -143,39 +216,41 @@ try { const skipBasedOnTime = async url => { // console.log(' Checking time left for game:', url); const [p, isNew] = await sameOrNewPage(url); - const dueDateOrg = await p.locator('.availability-date .tw-bold').innerText(); + const dueDateLoc = p.locator('.availability-date .tw-bold, [data-testid="availability-end-date"], [data-test-selector="availability-end-date"]'); + if (!await dueDateLoc.count()) { + if (isNew) await p.close(); + return false; + } + const dueDateOrg = await dueDateLoc.first().innerText(); const dueDate = new Date(Date.parse(dueDateOrg + ' 17:00')); const daysLeft = (dueDate.getTime() - Date.now())/1000/60/60/24; - console.log(' ', await p.locator('.availability-date').innerText(), '->', daysLeft.toFixed(2)); + const availabilityText = await p.locator('.availability-date, [data-testid="availability-end-date"], [data-test-selector="availability-end-date"]').first().innerText().catch(() => dueDateOrg); + console.log(' ', availabilityText, '->', daysLeft.toFixed(2)); if (isNew) await p.close(); return daysLeft > cfg.pg_timeLeft; } console.log('\nNumber of free unclaimed games (Prime Gaming):', internal.length); // claim games in internal store for (const card of internal) { - await card.scrollIntoViewIfNeeded(); - const title = await (await card.$('.item-card-details__body__primary')).innerText(); - const slug = await (await card.$('a')).getAttribute('href'); - const url = 'https://gaming.amazon.com' + slug.split('?')[0]; + await card.handle.scrollIntoViewIfNeeded(); + const title = card.title; + const url = card.url; console.log('Current free game:', chalk.blue(title)); - if (cfg.pg_timeLeft && await skipBasedOnTime(url)) continue; + if (cfg.pg_timeLeft && url && await skipBasedOnTime(url)) continue; if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; - await (await card.$('.tw-button:has-text("Claim")')).click(); + await card.handle.locator('.tw-button:has-text("Claim"), .tw-button:has-text("Get"), button:has-text("Claim"), button:has-text("Get")').first().click(); db.data[user][title] ||= { title, time: datetime(), url, store: 'internal' }; notify_games.push({ title, status: 'claimed', url }); - // const img = await (await card.$('img.tw-image')).getAttribute('src'); - // console.log('Image:', img); - await card.screenshot({ path: screenshot('internal', `${filenamify(title)}.png`) }); + await card.handle.screenshot({ path: screenshot('internal', `${filenamify(title)}.png`) }); } console.log('\nNumber of free unclaimed games (external stores):', external.length); // claim games in external/linked stores. Linked: origin.com, epicgames.com; Redeem-key: gog.com, legacygames.com, microsoft const external_info = []; for (const card of external) { // need to get data incl. URLs in this loop and then navigate in another, otherwise .all() would update after coming back and .elementHandles() like above would lead to error due to page navigation: elementHandle.$: Protocol error (Page.adoptNode) - const title = await card.locator('.item-card-details__body__primary').innerText(); - const slug = await card.locator('a:has-text("Claim")').first().getAttribute('href'); - const url = 'https://gaming.amazon.com' + slug.split('?')[0]; - // await (await card.$('text=Claim')).click(); // goes to URL of game, no need to wait + const title = card.title; + const url = card.url ? card.url.split('?')[0] : undefined; + if (!url) continue; external_info.push({ title, url }); } // external_info = [ { title: 'Fallout 76 (XBOX)', url: 'https://gaming.amazon.com/fallout-76-xbox-fgwp/dp/amzn1.pg.item.9fe17d7b-b6c2-4f58-b494-cc4e79528d0b?ingress=amzn&ref_=SM_Fallout76XBOX_S01_FGWP_CRWN' } ]; @@ -183,13 +258,32 @@ try { console.log('Current free game:', chalk.blue(title)); // , url); await page.goto(url, { waitUntil: 'domcontentloaded' }); if (cfg.debug) await page.pause(); - const item_text = await page.innerText('[data-a-target="DescriptionItemDetails"]'); - const store = item_text.toLowerCase().replace(/.* on /, '').slice(0, -1); + let store = 'unknown'; + const detailLoc = page.locator('[data-a-target="DescriptionItemDetails"], [data-testid="DescriptionItemDetails"]'); + if (await detailLoc.count()) { + const item_text = await detailLoc.first().innerText(); + store = item_text.toLowerCase().replace(/.* on /, '').slice(0, -1); + } else if (url.includes('/claims/')) { + const slug = url.split('/claims/')[1]?.split('/')[0] || ''; + if (slug.includes('gog')) store = 'gog.com'; + else if (slug.includes('epic')) store = 'epic-games'; + else if (slug.includes('origin')) store = 'origin'; + else if (slug.includes('xbox') || slug.includes('microsoft')) store = 'microsoft store'; + else if (slug.includes('legacy')) store = 'legacy games'; + } console.log(' External store:', store); if (cfg.pg_timeLeft && await skipBasedOnTime(url)) continue; if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; - await Promise.any([page.click('[data-a-target="buy-box"] .tw-button:has-text("Get game")'), page.click('[data-a-target="buy-box"] .tw-button:has-text("Claim")'), page.click('.tw-button:has-text("Complete Claim")'), page.waitForSelector('div:has-text("Link game account")'), page.waitForSelector('.thank-you-title:has-text("Success")')]); // waits for navigation + await Promise.any([ + page.click('[data-a-target="buy-box"] .tw-button:has-text("Get game")'), + page.click('[data-a-target="buy-box"] .tw-button:has-text("Claim")'), + page.click('.tw-button:has-text("Complete Claim")'), + page.click('[data-a-target="buy-box_call-to-action-text"]'), + page.click('p[data-a-target="buy-box_call-to-action-text"]'), + page.waitForSelector('div:has-text("Link game account")'), + page.waitForSelector('.thank-you-title:has-text("Success")'), + ]); // waits for navigation db.data[user][title] ||= { title, time: datetime(), url, store }; const notify_game = { title, url }; notify_games.push(notify_game); // status is updated below From 76f578e2e6edd24d1ea374ab9736ec92c598a475 Mon Sep 17 00:00:00 2001 From: nocci Date: Mon, 29 Dec 2025 14:15:22 +0000 Subject: [PATCH 512/520] =?UTF-8?q?=E2=9C=A8=20feat(auth):=20enhance=20aut?= =?UTF-8?q?omatic=20login=20and=20MFA=20handling?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add handleMFA function for improved two-step verification - implement direct login page handling with automatic sign-in 🐛 fix(claim): improve game claim process and duplicate prevention - normalize claim URLs and deduplicate by URL - fix various selectors for claim buttons and handle different languages - prevent duplicate game claims by checking existing records ♻️ refactor(utils): improve code readability and maintainability - extract normalizeClaimUrl function for URL handling - restructure logic for claim and notification processes 🌐 i18n(claim): add support for game claim text in German - handle German text for claim buttons and status checks --- prime-gaming.js | 230 +++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 199 insertions(+), 31 deletions(-) diff --git a/prime-gaming.js b/prime-gaming.js index 8e755b8..dc39bba 100644 --- a/prime-gaming.js +++ b/prime-gaming.js @@ -37,8 +37,53 @@ await page.setViewportSize({ width: cfg.width, height: cfg.height }); // TODO wo const notify_games = []; let user; +const handleMFA = async p => { + const otpField = p.locator('#auth-mfa-otpcode, input[name=otpCode]'); + if (!await otpField.count()) return false; + console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App'); + await p.locator('#auth-mfa-remember-device, [name=rememberDevice]').check().catch(_ => {}); + const otp = cfg.pg_otpkey && authenticator.generate(cfg.pg_otpkey) || await prompt({ type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!' }); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them + await otpField.first().pressSequentially(otp.toString()); + await p.locator('input[type="submit"], button[type="submit"]').first().click(); + return true; +}; + try { await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever + const handleDirectLoginPage = async () => { + if (!page.url().includes('/ap/signin')) return false; + console.log('On Amazon login page (redirect). Trying to sign in automatically.'); + if (!cfg.debug) context.setDefaultTimeout(cfg.login_timeout); + const email = cfg.pg_email || await prompt({ message: 'Enter email' }); + const password = email && (cfg.pg_password || await prompt({ type: 'password', message: 'Enter password' })); + if (email && password) { + await page.fill('[name=email]', email); + await page.click('input[type="submit"]'); + await page.fill('[name=password]', password); + await page.click('input[type="submit"]'); + await handleMFA(page).catch(_ => {}); + page.waitForURL('**/ap/signin**').then(async () => { // check for wrong credentials + const error = await page.locator('.a-alert-content').first().innerText(); + if (!error.trim.length) return; + console.error('Login error:', error); + await notify(`prime-gaming: login: ${error}`); + await context.close(); // finishes potential recording + process.exit(1); + }); + await page.waitForURL(/luna\.amazon\.com\/claims\/.*signedIn=true/); + if (!cfg.debug) context.setDefaultTimeout(cfg.timeout); + return true; + } else { + console.log('Waiting for manual login on redirect page.'); + if (cfg.headless) { + console.log('Run `SHOW=1 node prime-gaming` to login in the opened browser.'); + await context.close(); // finishes potential recording + process.exit(1); + } + return true; + } + }; + await handleDirectLoginPage(); // need to wait for some elements to exist before checking if signed in or accepting cookies: await Promise.any([ 'button:has-text("Sign in")', @@ -70,14 +115,7 @@ try { await context.close(); // finishes potential recording process.exit(1); }); - // handle MFA, but don't await it - page.waitForURL('**/ap/mfa**').then(async () => { - console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App'); - await page.check('[name=rememberDevice]'); - const otp = cfg.pg_otpkey && authenticator.generate(cfg.pg_otpkey) || await prompt({ type: 'text', message: 'Enter two-factor sign in code', validate: n => n.toString().length == 6 || 'The code must be 6 digits!' }); // can't use type: 'number' since it strips away leading zeros and codes sometimes have them - await page.locator('input[name=otpCode]').pressSequentially(otp.toString()); - await page.click('input[type="submit"]'); - }).catch(_ => { }); + handleMFA(page).catch(_ => {}); } else { console.log('Waiting for you to login in the browser.'); await notify('prime-gaming: no longer signed in and not enough options set for automatic login.'); @@ -128,6 +166,7 @@ try { const selectors = [ 'button[data-type="Game"]', // old layout 'button:has-text("Games")', + 'button:has-text("Games einlösen")', '[data-test-selector="category-picker"] button:has-text("Games")', '[data-testid="category-picker"] button:has-text("Games")', ]; @@ -138,6 +177,15 @@ try { return; } } + // New Luna claims home: try the filter/CTA button with embedded

+ const gamesTitle = page.locator('p.offer-filters__button__title:has-text("Games"), p.offer-filters__button__title:has-text("einlösen")'); + if (await gamesTitle.count()) { + const btn = gamesTitle.first().locator('xpath=ancestor::button[1]'); + if (await btn.count()) { + await btn.click(); + return; + } + } // New Luna claims home already shows games list }; @@ -163,15 +211,38 @@ try { if (games) await scrollUntilStable(() => games.evaluate(el => el.scrollHeight).catch(() => 0)); await scrollUntilStable(() => page.evaluate(() => document.scrollingElement?.scrollHeight ?? 0)); + const normalizeClaimUrl = url => { + if (!url) return { url, key: null }; + const m = url.match(/(https?:\/\/[^/]+)?(\/claims\/[^?#]+)/); + if (!m) return { url, key: url }; + const path = m[2]; + const slug = path.split('/')[2]; + return { url: 'https://luna.amazon.com' + path, key: slug || path }; + }; + const cards = []; + // New layout: direct claim buttons on cards (FGWPOffer) with "Spiel aktivieren"/"Claim" + const fgwps = page.locator('[data-a-target="FGWPOffer"]'); + if (await fgwps.count()) { + for (const handle of await fgwps.elementHandles()) { + const href = await handle.getAttribute('href'); + const { url, key } = normalizeClaimUrl(href?.startsWith('/') ? href : href || ''); + const title = + await handle.$eval('p[title], span[title]', el => el.getAttribute('title')).catch(() => null) || + await handle.$eval('p, span', el => el.textContent).catch(() => null) || + key || + 'Unknown title'; + cards.push({ kind: 'external', title, url, key }); + } + } + const anchorClaims = page.locator('a[href*="/claims/"][href*="amzn1.pg.item"]'); if (await anchorClaims.count()) { const hrefs = [...new Set(await anchorClaims.evaluateAll(anchors => anchors.map(a => a.getAttribute('href')).filter(Boolean)))]; for (const href of hrefs) { - let url = href; - if (url.startsWith('/')) url = 'https://luna.amazon.com' + url; - const title = url.split('/claims/')[1]?.split('/')[0] || (await anchorClaims.first().innerText()) || 'Unknown title'; - cards.push({ kind: 'external', title, url }); + const { url, key } = normalizeClaimUrl(href); + const title = key || (await anchorClaims.first().innerText()) || 'Unknown title'; + cards.push({ kind: 'external', title, url, key }); } } @@ -190,17 +261,28 @@ try { const title = await (await handle.$('h3, h4, [data-testid="item-card-title"], [data-test-selector="item-card-title"], .item-card-details__body__primary'))?.innerText() || 'Unknown title'; const linkEl = await handle.$('a[href]'); let url = linkEl && await linkEl.getAttribute('href'); - if (url?.startsWith('/')) url = 'https://gaming.amazon.com' + url; + if (url?.startsWith('/')) url = 'https://luna.amazon.com' + url; + const { url: normUrl, key } = normalizeClaimUrl(url); const hasLinkClaim = await handle.$('a:has-text("Claim"), a:has-text("Get"), a:has-text("Details")'); const hasButtonClaim = await handle.$('button:has-text("Claim"), button:has-text("Get"), button:has-text("Get game"), button:has-text("Play")'); - if (hasLinkClaim) cards.push({ kind: 'external', title, url }); - else if (hasButtonClaim) cards.push({ kind: 'internal', title, url, handle }); + const hasGermanCTA = await handle.$(':is(button,p,a):has-text("Spiel aktivieren"), :is(button,p,a):has-text("Spiel holen")'); + if (hasLinkClaim || hasGermanCTA) cards.push({ kind: 'external', title, url: normUrl, key }); + else if (hasButtonClaim) cards.push({ kind: 'internal', title, url: normUrl, key, handle }); } } } - const internal = cards.filter(c => c.kind == 'internal'); - const external = cards.filter(c => c.kind == 'external'); + // dedup by URL to avoid duplicates from multiple selectors + const seenUrl = new Set(); + const uniq = cards.filter(c => { + const key = c.key || c.url || c.title; + if (seenUrl.has(key)) return false; + seenUrl.add(key); + return true; + }); + + const internal = uniq.filter(c => c.kind == 'internal'); + const external = uniq.filter(c => c.kind == 'external'); // bottom to top: oldest to newest games internal.reverse(); external.reverse(); @@ -254,9 +336,46 @@ try { external_info.push({ title, url }); } // external_info = [ { title: 'Fallout 76 (XBOX)', url: 'https://gaming.amazon.com/fallout-76-xbox-fgwp/dp/amzn1.pg.item.9fe17d7b-b6c2-4f58-b494-cc4e79528d0b?ingress=amzn&ref_=SM_Fallout76XBOX_S01_FGWP_CRWN' } ]; + + const clickCTA = async p => { + const candidates = [ + p.locator('button[data-a-target="buy-box_call-to-action"]').first(), + p.locator('[data-a-target="buy-box_call-to-action"]').first(), + p.locator('[data-a-target="buy-box"] .tw-button:has-text("Get game")').first(), + p.locator('[data-a-target="buy-box"] .tw-button:has-text("Claim")').first(), + p.locator('.tw-button:has-text("Complete Claim")').first(), + p.locator('[data-a-target="buy-box_call-to-action-text"]').first().locator('xpath=ancestor::button[1]'), + p.locator('.tw-button:has-text("Spiel holen"), .tw-button:has-text("Spiel aktivieren")').first(), + p.locator('p:has-text("Spiel holen"), p:has-text("Spiel aktivieren")').first().locator('xpath=ancestor::button[1]'), + ]; + for (const c of candidates) { + if (await c.count()) { + try { + await c.waitFor({ state: 'visible', timeout: 5000 }); + if (!await c.isEnabled()) { + await c.evaluate(el => { el.disabled = false; el.removeAttribute('disabled'); el.click(); }); + } else { + await c.click(); + } + return true; + } catch (_) { + // try next candidate + } + } + } + return false; + }; + for (const { title, url } of external_info) { console.log('Current free game:', chalk.blue(title)); // , url); + const existing = db.data[user]?.[title]; + if (existing && existing.status && !existing.status.startsWith('failed')) { + console.log(` Already recorded as ${existing.status}, skipping.`); + notify_games.push({ title, url, status: 'existed' }); + continue; + } await page.goto(url, { waitUntil: 'domcontentloaded' }); + await page.waitForSelector('[data-a-target="buy-box"]', { timeout: 10000 }).catch(_ => {}); if (cfg.debug) await page.pause(); let store = 'unknown'; const detailLoc = page.locator('[data-a-target="DescriptionItemDetails"], [data-testid="DescriptionItemDetails"]'); @@ -270,23 +389,59 @@ try { else if (slug.includes('origin')) store = 'origin'; else if (slug.includes('xbox') || slug.includes('microsoft')) store = 'microsoft store'; else if (slug.includes('legacy')) store = 'legacy games'; + const lunaPlay = await page.locator('[data-a-target="LunaOffer"], button[data-a-target="LunaOffer"], button:has-text("Spielen")').count(); + if (store == 'unknown' && lunaPlay) store = 'luna'; } console.log(' External store:', store); + const notify_game = { title, url }; + notify_games.push(notify_game); // status is updated below + // Already collected? skip + const collectedLoc = page.locator('[data-a-target="ClaimStateQuantityAndDateContent"], [data-a-target="ClaimStateClaimCodeContent"]:has-text("Collected"), [data-a-target="ClaimStateVendorContent"], [data-a-target="ClaimStateViewDetailsAndInstructions"]'); + const collectedText = page.getByText(/You collected this on/i); + const collectedEpic = page.getByText(/Sent to your Epic Games Store library/i); + const collectedBanner = page.locator('p.tw-c-text-alert-success:has-text("Collected"), p.tw-c-text-alert-success:has-text("Collected this")'); + const collectedSuccessIcon = page.locator('[data-a-target="ItemCardDetailSuccessStatus"], .claim-state__success-icon'); + const disabledCTA = page.locator('[data-a-target="buy-box_call-to-action"][disabled], button[disabled]:has-text("Get game")'); + const collectedAny = await Promise.all([ + collectedLoc.count(), + collectedBanner.count(), + collectedText.count(), + collectedEpic.count(), + collectedSuccessIcon.count(), + disabledCTA.count(), + ]).then(([a, b, c, d, e, f]) => a + b + c + d + e + f > 0); + if (collectedAny) { + console.log(' Already collected, skipping.'); + notify_game.status = 'existed'; + db.data[user][title] ||= { title, time: datetime(), url, store, status: 'existed' }; + continue; + } + // Disabled CTA (e.g., needs linking or not available) + if (await disabledCTA.count()) { + if (store !== 'epic-games') { + console.log(' CTA is disabled, skipping (likely needs linking/not available).'); + notify_game.status = 'disabled'; + db.data[user][title] ||= { title, time: datetime(), url, store, status: 'disabled' }; + continue; + } else { + console.log(' CTA disabled for epic-games, will still try to link/claim.'); + } + } + if (store == 'luna') { + console.log(' Luna cloud title detected, skipping code redemption.'); + notify_game.status = 'luna (play)'; + db.data[user][title] ||= { title, time: datetime(), url, store: 'luna', status: 'luna (play)' }; + continue; + } if (cfg.pg_timeLeft && await skipBasedOnTime(url)) continue; if (cfg.dryrun) continue; if (cfg.interactive && !await confirm()) continue; + await clickCTA(page); await Promise.any([ - page.click('[data-a-target="buy-box"] .tw-button:has-text("Get game")'), - page.click('[data-a-target="buy-box"] .tw-button:has-text("Claim")'), - page.click('.tw-button:has-text("Complete Claim")'), - page.click('[data-a-target="buy-box_call-to-action-text"]'), - page.click('p[data-a-target="buy-box_call-to-action-text"]'), - page.waitForSelector('div:has-text("Link game account")'), - page.waitForSelector('.thank-you-title:has-text("Success")'), - ]); // waits for navigation + page.waitForSelector('.thank-you-title:has-text("Success")', { timeout: cfg.timeout }).catch(_ => {}), + page.waitForSelector('div:has-text("Link game account")', { timeout: cfg.timeout }).catch(_ => {}), + ]).catch(_ => {}); db.data[user][title] ||= { title, time: datetime(), url, store }; - const notify_game = { title, url }; - notify_games.push(notify_game); // status is updated below if (await page.locator('div:has-text("Link game account")').count() // TODO still needed? epic games store just has 'Link account' as the button text now. || await page.locator('div:has-text("Link account")').count()) { console.error(' Account linking is required to claim this offer!'); @@ -308,7 +463,21 @@ try { 'legacy games': 'https://www.legacygames.com/primedeal', }; if (store in redeem) { // did not work for linked origin: && !await page.locator('div:has-text("Successfully Claimed")').count() - const code = await Promise.any([page.inputValue('input[type="text"]'), page.textContent('[data-a-target="ClaimStateClaimCodeContent"]').then(s => s.replace('Your code: ', ''))]); // input: Legacy Games; text: gog.com + let code; + try { + // ensure CTA was clicked in case code is behind it + await clickCTA(page).catch(_ => {}); + code = await Promise.any([ + page.inputValue('input[type="text"]'), + page.textContent('[data-a-target="ClaimStateClaimCodeContent"]').then(s => s.replace('Your code: ', '')), + ]); + } catch (_) { + console.error(' Could not find claim code on page (timeout). Please check manually.'); + db.data[user][title].status = 'claimed (code not found)'; + notify_game.status = 'claimed (code not found)'; + await page.screenshot({ path: screenshot('external', `${filenamify(title)}_nocode.png`), fullPage: true }).catch(_ => {}); + continue; + } console.log(' Code to redeem game:', chalk.blue(code)); if (store == 'legacy games') { // may be different URL like https://legacygames.com/primeday/puzzleoftheyear/ redeem[store] = await (await page.$('li:has-text("Click here") a')).getAttribute('href'); // full text: Click here to enter your redemption code. @@ -441,11 +610,10 @@ try { // await page.pause(); } await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); - await page.click('button[data-type="Game"]'); + page.click('button[data-type="Game"]').catch(_ => {}); - if (notify_games.length) { // make screenshot of all games if something was claimed + if (notify_games.length && games) { // make screenshot of all games if something was claimed and list exists const p = screenshot(`${filenamify(datetime())}.png`); - // await page.screenshot({ path: p, fullPage: true }); // fullPage does not make a difference since scroll not on body but on some element await scrollUntilStable(() => games.locator('.item-card__action').count()); const viewportSize = page.viewportSize(); // current viewport size await page.setViewportSize({ ...viewportSize, height: 3000 }); // increase height, otherwise element screenshot is cut off at the top and bottom From a21dced86ec47540efc722cb43ce900343509bc7 Mon Sep 17 00:00:00 2001 From: nocci Date: Mon, 29 Dec 2025 14:31:25 +0000 Subject: [PATCH 513/520] =?UTF-8?q?=F0=9F=93=A6=20build(ci):=20add=20build?= =?UTF-8?q?-and-push=20workflow=20for=20docker=20images?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - create a new CI workflow to automate image building and pushing - trigger workflow on push to main branch - include steps for checkout, login, build, and push docker images --- .forgejo/workflows/build.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .forgejo/workflows/build.yml diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml new file mode 100644 index 0000000..97fa8a0 --- /dev/null +++ b/.forgejo/workflows/build.yml @@ -0,0 +1,26 @@ +name: build-and-push + +on: + push: + branches: + - main + +jobs: + docker: + runs-on: self-hosted + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Login to registry + run: echo "${{ secrets.REG_TOKEN }}" | docker login "${{ secrets.REGISTRY }}" -u "${{ secrets.REG_USER }}" --password-stdin + + - name: Build image + run: | + docker build -t "${{ secrets.REGISTRY_IMAGE }}:${{ github.sha }}" \ + -t "${{ secrets.REGISTRY_IMAGE }}:latest" . + + - name: Push image + run: | + docker push "${{ secrets.REGISTRY_IMAGE }}:${{ github.sha }}" + docker push "${{ secrets.REGISTRY_IMAGE }}:latest" From 6f778d71ac751dec0f079739141a48c4d11ebf1d Mon Sep 17 00:00:00 2001 From: nocci Date: Mon, 29 Dec 2025 14:33:14 +0000 Subject: [PATCH 514/520] =?UTF-8?q?=F0=9F=93=9D=20docs(README):=20add=20in?= =?UTF-8?q?structions=20for=20building=20images=20in=20Forgejo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - include steps for building and pushing Docker images using Forgejo - provide details on setting Forgejo secrets and using self-hosted runners --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 17ec854..5e0ab4d 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,14 @@ _This currently gives you a captcha challenge for epic-games. Until [issue #183] This will run `node epic-games; node prime-gaming; node gog` - if you only want to claim games for one of the stores, you can override the default command by appending e.g. `node epic-games` at the end of the `docker run` command, or if you want several `bash -c "node epic-games.js; node gog.js"`. Data (including json files with claimed games, codes to redeem, screenshots) is stored in the Docker volume `fgc`. +### Eigene Images aus Forgejo bauen +Falls du den Fork in einer selbst gehosteten Forgejo-Instanz pflegst: + +- Der Workflow `.forgejo/workflows/build.yml` baut/pusht das Docker-Image auf `push` nach `main`. +- Setze in Forgejo die Secrets `REGISTRY`, `REGISTRY_IMAGE`, `REG_USER`, `REG_TOKEN` (PAT mit Paket-Push). +- Self-hosted Runner mit Docker muss registriert sein (`runs-on: self-hosted`). +- Danach kannst du das Image ziehen, z.B.: `docker pull $REGISTRY_IMAGE:latest`. +

I want to run without Docker or develop locally. From bde0f34a80e8f59dce8b6b8e74b5e5286fda3320 Mon Sep 17 00:00:00 2001 From: nocci Date: Mon, 29 Dec 2025 14:36:18 +0000 Subject: [PATCH 515/520] =?UTF-8?q?=F0=9F=93=9D=20docs(README):=20simplify?= =?UTF-8?q?=20and=20update=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove detailed setup instructions and history - add quick-start guide for Docker and Docker Compose - update CI and configuration sections - streamline language and formatting for clarity --- README.md | 300 +++++++++++++++--------------------------------------- 1 file changed, 80 insertions(+), 220 deletions(-) diff --git a/README.md b/README.md index 5e0ab4d..be82e97 100644 --- a/README.md +++ b/README.md @@ -1,232 +1,92 @@ -

-logo-free-games-claimer -

+Free Games Claimer (Fork) +========================== -[![Code Smells](https://sonarcloud.io/api/project_badges/measure?project=vogler_free-games-claimer&metric=code_smells)](https://sonarcloud.io/project/overview?id=vogler_free-games-claimer) -# free-games-claimer +Automates claiming of free games for: +- Epic Games Store (and Epic-linked assets) +- Amazon Prime Gaming / Luna claims (incl. external stores like GOG, Legacy, Microsoft) +- GOG giveaways +- Optional extras: Steam stats, AliExpress dailies (experimental) -Claims free games periodically on -- [Epic Games Store](https://www.epicgames.com/store/free-games) -- [Amazon Prime Gaming](https://gaming.amazon.com) -- [GOG](https://www.gog.com) -- [Unreal Engine (Assets)](https://www.unrealengine.com/marketplace/en-US/assets?count=20&sortBy=effectiveDate&sortDir=DESC&start=0&tag=4910) ([experimental](https://github.com/vogler/free-games-claimer/issues/44), same login as Epic Games) - +This fork adds a Forgejo CI pipeline and instructions for running the pre-built image from your own registry. -Pull requests welcome :) +Requirements +------------ +- Docker or Podman (empfohlen) **oder** Node.js ≥ 20 zum lokalen Lauf +- (Optional) Python `apprise` für Benachrichtigungen: `pip install apprise` +- Für Playwright: Linux-Desktop-Abhängigkeiten sind im Container enthalten; lokal ggf. `npm install` zieht Firefox mit. -![Telegram Screenshot](https://user-images.githubusercontent.com/493741/214667078-eb5c1877-2bdd-40c1-b94e-4a50d6852c06.png) - -_Works on Windows/macOS/Linux._ - -Raspberry Pi (3, 4, Zero 2): [requires 64-bit OS](https://github.com/vogler/free-games-claimer/issues/3) like Raspberry Pi OS or Ubuntu (Raspbian won't work since it's 32-bit). - -## How to run -Easy option: [install Docker](https://docs.docker.com/get-docker/) (or [podman](https://podman-desktop.io/)) and run this command in a terminal: +Schnellstart (Docker Run) +------------------------- ``` -docker run --rm -it -p 6080:6080 -v fgc:/fgc/data --pull=always ghcr.io/vogler/free-games-claimer +docker run --rm -it \ + -p 6080:6080 \ + -v fgc:/fgc/data \ + -e SHOW=1 \ + :latest \ + node prime-gaming.js +``` +- `` z. B. `git.sky-net.it/nocci/free-games-claimer` +- Ports 6080/5900: noVNC/VNC (nur nötig mit `SHOW=1`) +- Daten/Configs landen in Volume `fgc` unter `/fgc/data` + +Docker Compose Beispiel +----------------------- +```yaml +services: + fgc: + image: :latest + container_name: fgc + environment: + - SHOW=1 # Browser sichtbar via VNC/noVNC + # - PG_EMAIL=... + # - PG_PASSWORD=... + # - PG_OTPKEY=... + volumes: + - fgc:/fgc/data + ports: + - "6080:6080" # noVNC + # - "5900:5900" # VNC optional + command: bash -c "node epic-games; node prime-gaming; node gog" +volumes: + fgc: ``` -_This currently gives you a captcha challenge for epic-games. Until [issue #183](https://github.com/vogler/free-games-claimer/issues/183) is fixed, it is recommended to just run `node epic-games` without docker (see below)._ +CI / eigenes Image +------------------ +- Workflow: `.forgejo/workflows/build.yml` baut/pusht auf `push` nach `main`. +- Secrets in Forgejo setzen: + - `REGISTRY` (z. B. `git.sky-net.it`) + - `REGISTRY_IMAGE` (z. B. `git.sky-net.it/nocci/free-games-claimer`) + - `REG_USER`, `REG_TOKEN` (PAT mit Paket-Push) +- Self-hosted Runner mit Docker-Access (`runs-on: self-hosted`) wird benötigt. -This will run `node epic-games; node prime-gaming; node gog` - if you only want to claim games for one of the stores, you can override the default command by appending e.g. `node epic-games` at the end of the `docker run` command, or if you want several `bash -c "node epic-games.js; node gog.js"`. -Data (including json files with claimed games, codes to redeem, screenshots) is stored in the Docker volume `fgc`. +Konfiguration (Umgebungsvariablen) +---------------------------------- +Typische Optionen: +- `SHOW=0/1` (0 = headless, 1 = UI) +- `WIDTH`, `HEIGHT` (Browsergröße) +- `TIMEOUT`, `LOGIN_TIMEOUT` (Sek.) +- Login: `EMAIL`, `PASSWORD` global; spezifisch `EG_EMAIL`, `EG_PASSWORD`, `EG_OTPKEY`, `PG_EMAIL`, `PG_PASSWORD`, `PG_OTPKEY`, `GOG_EMAIL`, `GOG_PASSWORD` +- Prime-Gaming: `PG_REDEEM=1` (Keys automatisch einlösen, experimentell), `PG_CLAIMDLC=1`, `PG_TIMELEFT=` zum Überspringen mit langer Restzeit +- Screenshots: `SCREENSHOTS_DIR` (Standard `data/screenshots`) +- Notifications: `NOTIFY='...'` (Apprise-URL), optional `NOTIFY_TITLE` -### Eigene Images aus Forgejo bauen -Falls du den Fork in einer selbst gehosteten Forgejo-Instanz pflegst: +Du kannst eine `data/config.env` anlegen; sie wird per dotenv geladen und überschreibt nichts, was bereits in der Umgebung gesetzt ist. -- Der Workflow `.forgejo/workflows/build.yml` baut/pusht das Docker-Image auf `push` nach `main`. -- Setze in Forgejo die Secrets `REGISTRY`, `REGISTRY_IMAGE`, `REG_USER`, `REG_TOKEN` (PAT mit Paket-Push). -- Self-hosted Runner mit Docker muss registriert sein (`runs-on: self-hosted`). -- Danach kannst du das Image ziehen, z.B.: `docker pull $REGISTRY_IMAGE:latest`. +Lokal ohne Docker +----------------- +``` +npm install +SHOW=0 PG_EMAIL=... PG_PASSWORD=... PG_OTPKEY=... node prime-gaming.js +``` +- Playwright lädt Firefox beim `npm install` in `~/.cache/ms-playwright`. +- Für sichtbaren Browser `SHOW=1` (GUI/Xvfb nötig). -
- I want to run without Docker or develop locally. +Persistenz & Ausgaben +--------------------- +- Daten & Status: `data/*.json` (pro Store) +- Browserprofil: `data/browser` +- Screenshots: `data/screenshots//` +- Optionale Videos/HAR: `RECORD=1` → `data/record/` -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` -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`. - -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. -
- -## Usage -All scripts start an automated Firefox instance, either with the browser GUI shown or hidden (*headless mode*). By default, you won't see any browser open on your host system. - -- When running inside Docker, the browser will be shown only inside the container. You can open http://localhost:6080 to interact with the browser running inside the container via noVNC (or use other VNC clients on port 5900). -- When running the scripts outside of Docker, the browser will be hidden by default; you can use `SHOW=1 ...` to show the UI (see options below). - -When running the first time, you have to login for each store you want to claim games on. -You can login indirectly via the terminal or directly in the browser. The scripts will wait until you are successfully logged in. - -There will be prompts in the terminal asking you to enter email, password, and afterwards some OTP (one time password / security code) if you have 2FA/MFA (two-/multi-factor authentication) enabled. If you want to login yourself via the browser, you can press escape in the terminal to skip the prompts. - -After login, the script will continue claiming the current games. If it still waits after you are already logged in, you can restart it (and open an issue). If you run the scripts regularly, you should not have to login again. - -### Configuration / Options -Options are set via [environment variables](https://kinsta.com/knowledgebase/what-is-an-environment-variable/) which allow for flexible configuration. - -TODO: ~~On the first run, the script will guide you through configuration and save all settings to `data/config.env`. You can edit this file directly or run `node fgc config` to run the configuration assistant again.~~ - -Available options/variables and their default values: - -| Option | Default | Description | -|--------------- |--------- |------------------------------------------------------------------------ | -| SHOW | 1 | Show browser if 1. Default for Docker, not shown when running outside. | -| WIDTH | 1280 | Width of the opened browser (and of screen for VNC in Docker). | -| HEIGHT | 1280 | Height of the opened browser (and of screen for VNC in Docker). | -| VNC_PASSWORD | | VNC password for Docker. No password used by default! | -| NOTIFY | | Notification services to use (Pushover, Slack, Telegram...), see below. [Apprise](https://github.com/caronc/apprise) | -| NOTIFY_TITLE | | Optional title for notifications, e.g. for Pushover. | -| BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. | -| TIMEOUT | 60 | Timeout for any page action. Should be fine even on slow machines. | -| LOGIN_TIMEOUT | 180 | Timeout for login in seconds. Will wait twice (prompt + manual login). | -| EMAIL | | Default email for any login. | -| PASSWORD | | Default password for any login. | -| EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | -| EG_PASSWORD | | Epic Games password for login. Overrides PASSWORD. | -| EG_OTPKEY | | Epic Games MFA OTP key. | -| EG_PARENTALPIN | | Epic Games Parental Controls PIN. | -| PG_EMAIL | | Prime Gaming email for login. Overrides EMAIL. | -| PG_PASSWORD | | Prime Gaming password for login. Overrides PASSWORD. | -| PG_OTPKEY | | Prime Gaming MFA OTP key. | -| PG_REDEEM | 0 | Prime Gaming: try to redeem keys on external stores ([experimental](https://github.com/vogler/free-games-claimer/issues/5)). | -| PG_CLAIMDLC | 0 | Prime Gaming: try to claim DLCs ([experimental](https://github.com/vogler/free-games-claimer/issues/55)). | -| GOG_EMAIL | | GOG email for login. Overrides EMAIL. | -| GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. | -| GOG_NEWSLETTER | 0 | Do not unsubscribe from newsletter after claiming a game if 1. | -| LG_EMAIL | | Legacy Games: email to use for redeeming (if not set, defaults to PG_EMAIL) | - -See `src/config.js` for all options. - -#### How to set options -You can add options directly in the command or put them in a file to load. - -##### Docker -You can pass variables using `-e VAR=VAL`, for example `docker run -e EMAIL=foo@bar.baz -e NOTIFY='tgram://bottoken/ChatID' ...` or using `--env-file fgc.env` where `fgc.env` is a file on your host system (see [docs](https://docs.docker.com/engine/reference/commandline/run/#env)). You can also `docker cp` your configuration file to `/fgc/data/config.env` in the `fgc` volume to store it with the rest of the data instead of on the host ([example](https://github.com/moby/moby/issues/25245#issuecomment-365980572)). -If you are using [docker compose](https://docs.docker.com/compose/environment-variables/) (or Portainer etc.), you can put options in the `environment:` section. - -##### Without Docker -On Linux/macOS you can prefix the variables you want to set, for example `EMAIL=foo@bar.baz SHOW=1 node epic-games` will show the browser and skip asking you for your login email. On Windows you have to use `set`, [example](https://github.com/vogler/free-games-claimer/issues/314). -You can also put options in `data/config.env` which will be loaded by [dotenv](https://github.com/motdotla/dotenv). - -### Notifications -The scripts will try to send notifications for successfully claimed games and any errors like needing to log in or encountered captchas (should not happen). - -[apprise](https://github.com/caronc/apprise) is used for notifications and offers many services including Pushover, Slack, Telegram, SMS, Email, desktop and custom notifications. -You just need to set `NOTIFY` to the notification services you want to use, e.g. `NOTIFY='mailto://myemail:mypass@gmail.com' 'pbul://o.gn5kj6nfhv736I7jC3cj3QLRiyhgl98b'` - refer to their list of services and [examples](https://github.com/caronc/apprise#command-line-usage). - -### Automatic login, two-factor authentication -If you set the options for email, password and OTP key, there will be no prompts and logins should happen automatically. This is optional since all stores should stay logged in since cookies are refreshed. -To get the OTP key, it is easiest to follow the store's guide for adding an authenticator app. You should also scan the shown QR code with your favorite app to have an alternative method for 2FA. - -- **Epic Games**: visit [password & security](https://www.epicgames.com/account/password), enable 'third-party authenticator app', copy the 'Manual Entry Key' and use it to set `EG_OTPKEY`. -- **Prime Gaming**: visit Amazon 'Your Account › Login & security', 2-step verification › Manage › Add new app › Can't scan the barcode, copy the bold key and use it to set `PG_OTPKEY` -- **GOG**: only offers OTP via email - - -Beware that storing passwords and OTP keys as clear text may be a security risk. Use a unique/generated password! TODO: maybe at least offer to base64 encode for storage. - -### Epic Games Store -Run `node epic-games` (locally or in Docker). - -### Amazon Prime Gaming -Run `node prime-gaming` (locally or in Docker). - -Claiming the Amazon Games works out-of-the-box, however, for games on external stores you need to either link your account or redeem a key. - -- Stores that require account linking: Epic Games, Battle.net, Origin. -- Stores that require redeeming a key: GOG.com, Microsoft Games, Legacy Games. - - Keys and URLs are printed to the console, included in notifications and saved in `data/prime-gaming.json`. A screenshot of the page with the key is also saved to `data/screenshots`. - [TODO](https://github.com/vogler/free-games-claimer/issues/5): ~~redeem keys on external stores.~~ - - - - -### Run periodically -#### How often? -Epic Games usually has two free games *every week*, before Christmas every day. -Prime Gaming has new games *every month* or more often during Prime days. -GOG usually has one new game every couples of weeks. -Unreal Engine has new assets to claim *every first Tuesday of a month*. - - -It is safe to run the scripts every day. - -#### How to schedule? -The container/scripts will claim currently available games and then exit. -If you want it to run regularly, you have to schedule the runs yourself: - -- Linux/macOS: `crontab -e` ([example](https://github.com/vogler/free-games-claimer/discussions/56)) -- macOS: [launchd](https://stackoverflow.com/questions/132955/how-do-i-set-a-task-to-run-every-so-often) -- Windows: [task scheduler](https://active-directory-wp.com/docs/Usage/How_to_add_a_cron_job_on_Windows/Scheduled_tasks_and_cron_jobs_on_Windows/index.html) ([example](https://github.com/vogler/free-games-claimer/wiki/%5BHowTo%5D-Schedule-runs-on-Windows)), [other options](https://stackoverflow.com/questions/132971/what-is-the-windows-version-of-cron), or just put the command in a `.bat` file in Autostart if you restart often... -- any OS: use a process manager like [pm2](https://pm2.keymetrics.io/docs/usage/restart-strategies/) -- Docker Compose `command: bash -c "node epic-games; node prime-gaming; node gog; echo sleeping; sleep 1d"` additionally add `restart: unless-stopped` to it. - -TODO: ~~add some server-mode where the script just keeps running and claims games e.g. every day.~~ - -### Problems? - -Check the open [issues](https://github.com/vogler/free-games-claimer/issues) and comment there or open a new issue. - -If you're a developer, you can use `PWDEBUG=1 ...` to [inspect](https://playwright.dev/docs/inspector) which opens a debugger where you can step through the script. - - -## History/DevLog -
- Click to expand - -Tried [epicgames-freebies-claimer](https://github.com/Revadike/epicgames-freebies-claimer), but had problems since epicgames introduced hcaptcha (see [issue](https://github.com/Revadike/epicgames-freebies-claimer/issues/172)). - -Played around with puppeteer before, now trying newer https://playwright.dev which is pretty similar. -Playwright Inspector and `codegen` to generate scripts are nice, but failed to generate the right code for clicking a button in an iframe. - -Added [main.spec.ts](https://github.com/vogler/epicgames-claimer/commit/e5ce7916ab6329cfc7134677c4d89c2b3fa3ba97#diff-d18d03e9c407a20e05fbf03cbd6f9299857740544fb6b50d6a70b9c6fbc35831) which was the test script generated by `npx playwright codegen` with manual fix for clicking buttons in the created iframe. Can be executed by `npx playwright test`. The test runner has options `--debug` and `--timeout` and can execute typescript which is nice. However, this only worked up to the button 'I Agree', and then showed an hcaptcha. - -Added [main.captcha.js](https://github.com/vogler/epicgames-claimer/commit/e5ce7916ab6329cfc7134677c4d89c2b3fa3ba97#diff-d18d03e9c407a20e05fbf03cbd6f9299857740544fb6b50d6a70b9c6fbc35831) which uses beta of `playwright-extra@next` and `@extra/recaptcha@next` (from [comment on puppeteer-extra](https://github.com/berstend/puppeteer-extra/pull/303#issuecomment-775277480)). -However, `playwright-extra` seems to be old and missing `:has-text` selector (fixed [here](https://github.com/vogler/epicgames-claimer/commit/ba97a0e840b65f4476cca18e28d8461b0c703420)) and `page.frameLocator`, so the script did not run without adjustments. -Also, solving via [2captcha](https://2captcha.com?from=13225256) is a paid service which takes time and may be unreliable. - - -Added [main.stealth.js](https://github.com/vogler/epicgames-claimer/commit/64d0ba8ce71baec3947d1b64acd567befcb39340#diff-f70d3bd29df4a343f11062a97063953173491ce30fe34f69a0fc52517adbf342) which uses the stealth plugin without `playwright-extra` wrapper but up-to-date `playwright` (from [comment](https://github.com/berstend/puppeteer-extra/issues/454#issuecomment-917437212)). -The listed evasions are enough to not show an hcaptcha. Script claimed game successfully in non-headless mode. - -Removed `main.captcha.js`. -Using Playwright Test (`main.spec.ts`) instead of Library (`main.stealth.js`) has the advantage of free CLI like `--debug` and `--timeout`. - - -Button selectors should preferably use text in order to be more stable against changes in the DOM. - -Renamed repository from epicgames-claimer to free-games-claimer since a script for Amazon Prime Gaming was also added. Removed all old scripts in favor of just `epic-games.js` and `prime-gaming.js`. - -epic games: `headless` mode gets hcaptcha challenge. More details/references in [issue](https://github.com/vogler/free-games-claimer/issues/2). - -https://github.com/vogler/free-games-claimer/pull/11 introduced a Dockerfile for running non-headless inside the container via xvfb which makes it headless for the host running the container. - -v1.0 Standalone scripts node epic-games and node prime-gaming using Chromium. - -Changed to Firefox for all scripts since Chromium led to captchas. Claiming then also worked in headless mode without Docker. - -Added options via env vars, configurable in `data/config.env`. - -Added OTP generation via otplib for automatic login, even with 2FA. - -Added notifications via [apprise](https://github.com/caronc/apprise). -
- -[![Star History Chart](https://api.star-history.com/svg?repos=vogler/free-games-claimer&type=Date)](https://star-history.com/#vogler/free-games-claimer&Date) - - -![Alt](https://repobeats.axiom.co/api/embed/a1c5e6e420d90e0d6b34c1285e92a69a44138faa.svg "Repobeats analytics image") - ---- - -Logo with smaller aspect ratio (for Telegram bot etc.): 👾 - [emojipedia](https://emojipedia.org/alien-monster/) - -![logo-fgc](https://user-images.githubusercontent.com/493741/214589922-093d6557-6393-421c-b577-da58ff3671bc.png) +Tipp: Bei Captchas oder Erst-Login `SHOW=1` nutzen und einmal manuell einloggen; Cookies bleiben im Profil. Notifications via `NOTIFY` helfen bei Fehlermeldungen (z. B. Captcha, Login).*** From eba07721ca09a04c0c95e2f9672f87d084c6ee15 Mon Sep 17 00:00:00 2001 From: nocci Date: Mon, 29 Dec 2025 14:38:54 +0000 Subject: [PATCH 516/520] =?UTF-8?q?=F0=9F=93=9D=20docs(README):=20update?= =?UTF-8?q?=20instructions=20and=20clarify=20configurations?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - update registry image references to point to `git.sky-net.it/nocci/free-games-claimer` - improve clarity on Docker and Docker Compose examples - translate German sections into English for wider accessibility - add detailed explanations for environment variables and configurations - enhance quickstart and CI instructions for better understanding --- README.md | 83 +++++++++++++++++++++++++++---------------------------- 1 file changed, 41 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index be82e97..8ccf31c 100644 --- a/README.md +++ b/README.md @@ -3,41 +3,40 @@ Free Games Claimer (Fork) Automates claiming of free games for: - Epic Games Store (and Epic-linked assets) -- Amazon Prime Gaming / Luna claims (incl. external stores like GOG, Legacy, Microsoft) +- Amazon Prime Gaming / Luna claims (including external stores like GOG, Legacy, Microsoft) - GOG giveaways - Optional extras: Steam stats, AliExpress dailies (experimental) -This fork adds a Forgejo CI pipeline and instructions for running the pre-built image from your own registry. +This fork adds a Forgejo CI pipeline and instructions for running the pre-built image from the registry at `git.sky-net.it/nocci/free-games-claimer`. Requirements ------------ -- Docker or Podman (empfohlen) **oder** Node.js ≥ 20 zum lokalen Lauf -- (Optional) Python `apprise` für Benachrichtigungen: `pip install apprise` -- Für Playwright: Linux-Desktop-Abhängigkeiten sind im Container enthalten; lokal ggf. `npm install` zieht Firefox mit. +- Docker or Podman (recommended), or Node.js ≥ 20 for local runs +- Optional notifications: `pip install apprise` +- Playwright dependencies are baked into the container; locally, `npm install` downloads Firefox. -Schnellstart (Docker Run) -------------------------- +Quickstart (Docker Run) +----------------------- ``` docker run --rm -it \ -p 6080:6080 \ -v fgc:/fgc/data \ -e SHOW=1 \ - :latest \ + git.sky-net.it/nocci/free-games-claimer:latest \ node prime-gaming.js ``` -- `` z. B. `git.sky-net.it/nocci/free-games-claimer` -- Ports 6080/5900: noVNC/VNC (nur nötig mit `SHOW=1`) -- Daten/Configs landen in Volume `fgc` unter `/fgc/data` +- Ports 6080/5900: noVNC/VNC (only needed with `SHOW=1`) +- Data/configs are stored in volume `fgc` under `/fgc/data` -Docker Compose Beispiel ------------------------ +Docker Compose Example +---------------------- ```yaml services: fgc: - image: :latest + image: git.sky-net.it/nocci/free-games-claimer:latest container_name: fgc environment: - - SHOW=1 # Browser sichtbar via VNC/noVNC + - SHOW=1 # show browser via VNC/noVNC # - PG_EMAIL=... # - PG_PASSWORD=... # - PG_OTPKEY=... @@ -51,42 +50,42 @@ volumes: fgc: ``` -CI / eigenes Image ------------------- -- Workflow: `.forgejo/workflows/build.yml` baut/pusht auf `push` nach `main`. -- Secrets in Forgejo setzen: - - `REGISTRY` (z. B. `git.sky-net.it`) - - `REGISTRY_IMAGE` (z. B. `git.sky-net.it/nocci/free-games-claimer`) - - `REG_USER`, `REG_TOKEN` (PAT mit Paket-Push) -- Self-hosted Runner mit Docker-Access (`runs-on: self-hosted`) wird benötigt. +CI / Build Your Own Image +------------------------- +- Workflow: `.forgejo/workflows/build.yml` builds/pushes on `push` to `main`. +- Secrets needed in Forgejo: + - `REGISTRY` (e.g., `git.sky-net.it`) + - `REGISTRY_IMAGE` (e.g., `git.sky-net.it/nocci/free-games-claimer`) + - `REG_USER`, `REG_TOKEN` (PAT with package push) +- Requires a self-hosted runner with Docker access (`runs-on: self-hosted`). -Konfiguration (Umgebungsvariablen) ----------------------------------- -Typische Optionen: +Configuration (Environment Variables) +------------------------------------- +Common options: - `SHOW=0/1` (0 = headless, 1 = UI) -- `WIDTH`, `HEIGHT` (Browsergröße) -- `TIMEOUT`, `LOGIN_TIMEOUT` (Sek.) -- Login: `EMAIL`, `PASSWORD` global; spezifisch `EG_EMAIL`, `EG_PASSWORD`, `EG_OTPKEY`, `PG_EMAIL`, `PG_PASSWORD`, `PG_OTPKEY`, `GOG_EMAIL`, `GOG_PASSWORD` -- Prime-Gaming: `PG_REDEEM=1` (Keys automatisch einlösen, experimentell), `PG_CLAIMDLC=1`, `PG_TIMELEFT=` zum Überspringen mit langer Restzeit -- Screenshots: `SCREENSHOTS_DIR` (Standard `data/screenshots`) -- Notifications: `NOTIFY='...'` (Apprise-URL), optional `NOTIFY_TITLE` +- `WIDTH`, `HEIGHT` (browser size) +- `TIMEOUT`, `LOGIN_TIMEOUT` (seconds) +- Login: `EMAIL`, `PASSWORD` global; per store `EG_EMAIL`, `EG_PASSWORD`, `EG_OTPKEY`, `PG_EMAIL`, `PG_PASSWORD`, `PG_OTPKEY`, `GOG_EMAIL`, `GOG_PASSWORD` +- Prime Gaming: `PG_REDEEM=1` (auto-redeem keys, experimental), `PG_CLAIMDLC=1`, `PG_TIMELEFT=` to skip long-remaining offers +- Screenshots: `SCREENSHOTS_DIR` (default `data/screenshots`) +- Notifications: `NOTIFY='...'` (Apprise URL), optional `NOTIFY_TITLE` -Du kannst eine `data/config.env` anlegen; sie wird per dotenv geladen und überschreibt nichts, was bereits in der Umgebung gesetzt ist. +You can place a `data/config.env`; it is loaded via dotenv and is overridden by explicitly set environment variables. -Lokal ohne Docker ------------------ +Local Run Without Docker +------------------------ ``` npm install SHOW=0 PG_EMAIL=... PG_PASSWORD=... PG_OTPKEY=... node prime-gaming.js ``` -- Playwright lädt Firefox beim `npm install` in `~/.cache/ms-playwright`. -- Für sichtbaren Browser `SHOW=1` (GUI/Xvfb nötig). +- Playwright downloads Firefox to `~/.cache/ms-playwright`. +- Use `SHOW=1` for a visible browser (requires GUI/Xvfb). -Persistenz & Ausgaben +Persistence & Outputs --------------------- -- Daten & Status: `data/*.json` (pro Store) -- Browserprofil: `data/browser` +- Data/status: `data/*.json` (per store) +- Browser profile: `data/browser` - Screenshots: `data/screenshots//` -- Optionale Videos/HAR: `RECORD=1` → `data/record/` +- Optional videos/HAR: `RECORD=1` → `data/record/` -Tipp: Bei Captchas oder Erst-Login `SHOW=1` nutzen und einmal manuell einloggen; Cookies bleiben im Profil. Notifications via `NOTIFY` helfen bei Fehlermeldungen (z. B. Captcha, Login).*** +Tip: For captchas or first-time login, run with `SHOW=1` and log in once; cookies stay in the profile. Notifications via `NOTIFY` help surface errors (e.g., captcha, login). From eb5b9bbb6e8890e51072c2a592dd14d45a469d83 Mon Sep 17 00:00:00 2001 From: nocci Date: Mon, 29 Dec 2025 15:01:55 +0000 Subject: [PATCH 517/520] =?UTF-8?q?=F0=9F=91=B7=20ci(build):=20enhance=20d?= =?UTF-8?q?ocker=20build=20process=20with=20buildx?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - add Docker Buildx setup for advanced build capabilities - update build step to use buildx for multi-platform support --- .forgejo/workflows/build.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 97fa8a0..3aa7b56 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -9,6 +9,9 @@ jobs: docker: runs-on: self-hosted steps: + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Checkout uses: actions/checkout@v4 @@ -17,8 +20,9 @@ jobs: - name: Build image run: | - docker build -t "${{ secrets.REGISTRY_IMAGE }}:${{ github.sha }}" \ - -t "${{ secrets.REGISTRY_IMAGE }}:latest" . + docker buildx build --load \ + -t "${{ secrets.REGISTRY_IMAGE }}:${{ github.sha }}" \ + -t "${{ secrets.REGISTRY_IMAGE }}:latest" . - name: Push image run: | From 0a729d0cbfa562f6913bbe2d7a0085e2da07ab72 Mon Sep 17 00:00:00 2001 From: nocci Date: Mon, 29 Dec 2025 15:08:00 +0000 Subject: [PATCH 518/520] =?UTF-8?q?=F0=9F=94=A7=20chore(workflow):=20simpl?= =?UTF-8?q?ify=20docker=20image=20tagging=20and=20pushing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove specific sha tag from build and push steps - streamline workflow by focusing on latest tag --- .forgejo/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.forgejo/workflows/build.yml b/.forgejo/workflows/build.yml index 3aa7b56..cb83fb0 100644 --- a/.forgejo/workflows/build.yml +++ b/.forgejo/workflows/build.yml @@ -21,10 +21,8 @@ jobs: - name: Build image run: | docker buildx build --load \ - -t "${{ secrets.REGISTRY_IMAGE }}:${{ github.sha }}" \ -t "${{ secrets.REGISTRY_IMAGE }}:latest" . - name: Push image run: | - docker push "${{ secrets.REGISTRY_IMAGE }}:${{ github.sha }}" docker push "${{ secrets.REGISTRY_IMAGE }}:latest" From 9d79f9ac7851f8d4dabdb0cb0e5bc1dd3cc29525 Mon Sep 17 00:00:00 2001 From: nocci Date: Mon, 29 Dec 2025 15:54:41 +0000 Subject: [PATCH 519/520] =?UTF-8?q?=F0=9F=93=9D=20docs(README):=20update?= =?UTF-8?q?=20configuration=20and=20remove=20CI=20section?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - remove outdated CI/build instructions - add new environment variable options for configuration 🐛 fix(util): handle notification errors gracefully - resolve promise instead of rejecting on notification errors - prevent whole run from failing due to notification issues --- README.md | 16 +++++++--------- src/util.js | 3 ++- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 8ccf31c..16ac157 100644 --- a/README.md +++ b/README.md @@ -50,15 +50,6 @@ volumes: fgc: ``` -CI / Build Your Own Image -------------------------- -- Workflow: `.forgejo/workflows/build.yml` builds/pushes on `push` to `main`. -- Secrets needed in Forgejo: - - `REGISTRY` (e.g., `git.sky-net.it`) - - `REGISTRY_IMAGE` (e.g., `git.sky-net.it/nocci/free-games-claimer`) - - `REG_USER`, `REG_TOKEN` (PAT with package push) -- Requires a self-hosted runner with Docker access (`runs-on: self-hosted`). - Configuration (Environment Variables) ------------------------------------- Common options: @@ -69,6 +60,13 @@ Common options: - Prime Gaming: `PG_REDEEM=1` (auto-redeem keys, experimental), `PG_CLAIMDLC=1`, `PG_TIMELEFT=` to skip long-remaining offers - Screenshots: `SCREENSHOTS_DIR` (default `data/screenshots`) - Notifications: `NOTIFY='...'` (Apprise URL), optional `NOTIFY_TITLE` +- Browser profile: `BROWSER_DIR` (default `data/browser`) +- Recording: `RECORD=1` to save videos/HAR to `data/record/` +- Debugging: `DEBUG=1` (opens Playwright inspector), `DEBUG_NETWORK=1` (logs requests), `TIME=1` (prints timings) +- Dry run / Interaction: `DRYRUN=1` (do not claim), `INTERACTIVE=1` (ask before claiming), `HEADLESS` is derived from `SHOW`/`DEBUG` +- Directories: `SCREENSHOTS_DIR`, `BROWSER_DIR`, `DATA_DIR` (prefix for data; default under `data/`) +- VNC/noVNC: `VNC_PASSWORD` (for Docker entrypoint), `NOVNC_PORT`/`VNC_PORT` (Docker) +- General timeouts: `TIMEOUT` (per action), `LOGIN_TIMEOUT` (extra time for login) You can place a `data/config.env`; it is loaded via dotenv and is overridden by explicitly set environment variables. diff --git a/src/util.js b/src/util.js index 308952d..49424f8 100644 --- a/src/util.js +++ b/src/util.js @@ -125,7 +125,8 @@ export const notify = html => new Promise((resolve, reject) => { if (error.message.includes('command not found')) { console.info('Run `pip install apprise`. See https://github.com/vogler/free-games-claimer#notifications'); } - return reject(error); + // don't fail the whole run on notification errors + return resolve(); } if (stderr) console.error(`stderr: ${stderr}`); if (stdout) console.log(`stdout: ${stdout}`); From e39cca93c23fe1412ca1bdecbbeda45843bd060f Mon Sep 17 00:00:00 2001 From: nocci Date: Mon, 29 Dec 2025 17:19:39 +0100 Subject: [PATCH 520/520] better README.md --- README.md | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 16ac157..248995e 100644 --- a/README.md +++ b/README.md @@ -2,12 +2,9 @@ Free Games Claimer (Fork) ========================== Automates claiming of free games for: -- Epic Games Store (and Epic-linked assets) -- Amazon Prime Gaming / Luna claims (including external stores like GOG, Legacy, Microsoft) +- Amazon Luna Gaming / Luna claims (including external stores like GOG, Epic Games, Legacy Games ) - GOG giveaways -- Optional extras: Steam stats, AliExpress dailies (experimental) - -This fork adds a Forgejo CI pipeline and instructions for running the pre-built image from the registry at `git.sky-net.it/nocci/free-games-claimer`. +- Optional extras: Steam stats, AliExpress dailies (not implemated yet) Requirements ------------