Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: dockerize, fix sign in loop #11

Merged
merged 5 commits into from
Apr 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
userDataDir**
node_modules
screenshots

.gitignore
**Dockerfile**
.dockerignore
.env
auth.json
77 changes: 77 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# 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
ENV NOVNC_PORT 6080
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
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 \
ca-certificates \
x11vnc \
curl \
tini \
novnc websockify \
&& 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 npm install \
&& npx playwright install --with-deps 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"]
15 changes: 15 additions & 0 deletions docker/entrypoint.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/sh

# 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
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

exec tini -g -- "$@"
11 changes: 11 additions & 0 deletions docker/vnc-start.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/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 "$@"
30 changes: 15 additions & 15 deletions epic-games.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,19 @@ 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/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_HEIGHT = Number(process.env.SCREEN_HEIGHT) || 1280;

// 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!
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.
'--disable-session-crashed-bubble',
Expand All @@ -35,15 +38,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);
}
Expand All @@ -54,7 +57,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);
Expand Down Expand Up @@ -83,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 :(');
Expand All @@ -105,8 +105,8 @@ for (let i=1; i<=n; i++) {
}
// await page.pause();
}
if (i<n) { // no need to go back if it's the last game
await page.goto(URL_CLAIM, {waitUntil: 'domcontentloaded'});
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);
}
}
Expand Down
Loading