feat: dockerize, fix sign in loop

This commit is contained in:
Trung Le 2022-03-31 03:06:01 +07:00
parent 3b3289dddf
commit a8c578bd93
6 changed files with 127 additions and 13 deletions

6
.dockerignore Normal file
View file

@ -0,0 +1,6 @@
userDataDir**
node_modules
.gitignore
**Dockerfile**
.dockerignore

75
Dockerfile Normal file
View file

@ -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"]

20
docker/entrypoint.sh Executable file
View file

@ -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" "$@"

12
docker/vnc-start.sh Executable file
View file

@ -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 "$@"

View file

@ -3,16 +3,17 @@ import path from 'path';
import { __dirname, stealth } from './util.js'; import { __dirname, stealth } from './util.js';
const debug = process.env.PWDEBUG == '1'; // runs non-headless and opens https://playwright.dev/docs/inspector 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://store.epicgames.com/store/en-US/free-games';
const URL_CLAIM = 'https://www.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 const TIMEOUT = 20 * 1000; // 20s, default is 30s
// https://playwright.dev/docs/auth#multi-factor-authentication // https://playwright.dev/docs/auth#multi-factor-authentication
const context = await chromium.launchPersistentContext(path.resolve(__dirname, 'userDataDir'), { 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, headless: false,
viewport: { width: 1280, height: 1280 }, 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 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. 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', '--disable-session-crashed-bubble',
@ -35,15 +36,15 @@ const clickIfExists = async selector => {
await page.click(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. // 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 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 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.");
context.setDefaultTimeout(0); // give user time to log in without timeout 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... // after login it just reloads the login page...
await page.waitForNavigation({url: URL_CLAIM}); await page.waitForNavigation({ url: URL_CLAIM });
context.setDefaultTimeout(TIMEOUT); context.setDefaultTimeout(TIMEOUT);
// process.exit(1); // 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 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(); const n = await page.locator(game_sel).count();
console.log('Number of free games:', n); 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})`); await page.click(`:nth-match(${game_sel}, ${i})`);
const title = await page.locator('h1 div').first().innerText(); const title = await page.locator('h1 div').first().innerText();
console.log('Current free game:', title); console.log('Current free game:', title);
@ -105,8 +106,8 @@ for (let i=1; i<=n; i++) {
} }
// await page.pause(); // await page.pause();
} }
if (i<n) { // no need to go back if it's the last game if (i < n) { // no need to go back if it's the last game
await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'}); await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' });
await page.waitForSelector(game_sel); await page.waitForSelector(game_sel);
} }
} }

View file

@ -7,8 +7,8 @@
"start": "node main.stealth" "start": "node main.stealth"
}, },
"devDependencies": { "devDependencies": {
"@playwright/test": "^1.17.1", "@playwright/test": "^1.20.1",
"playwright": "^1.17.1", "playwright": "^1.20.1",
"puppeteer-extra-plugin-stealth": "^2.9.0" "puppeteer-extra-plugin-stealth": "^2.9.0"
}, },
"type": "module" "type": "module"