BROWSER_DIR for multiple profiles or testing, SCREENSHOTS_DIR, closes #12

This commit is contained in:
Ralf Vogler 2023-02-16 16:10:28 +01:00
parent a90062b631
commit e2b07dc1e6
6 changed files with 24 additions and 21 deletions

View file

@ -67,6 +67,7 @@ Available options/variables and their default values:
| HEIGHT | 1280 | Height 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! | | 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. |
| BROWSER_DIR | data/browser | Directory for browser profile, e.g. for multiple accounts. |
| EMAIL | | Default email for any login. | | EMAIL | | Default email for any login. |
| PASSWORD | | Default password for any login. | | PASSWORD | | Default password for any login. |
| EG_EMAIL | | Epic Games email for login. Overrides EMAIL. | | EG_EMAIL | | Epic Games email for login. Overrides EMAIL. |

View file

@ -1,4 +1,6 @@
import * as dotenv from 'dotenv'; 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 ... 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 // 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 timeout: (Number(process.env.TIMEOUT) || 20) * 1000, // 20s, default for playwright is 30s
novnc_port: process.env.NOVNC_PORT, // running in docker if set novnc_port: process.env.NOVNC_PORT, // running in docker if set
notify: process.env.NOTIFY, // apprise notification services 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 // auth epic-games
eg_email: process.env.EG_EMAIL || process.env.EMAIL, eg_email: process.env.EG_EMAIL || process.env.EMAIL,
eg_password: process.env.EG_PASSWORD || process.env.PASSWORD, eg_password: process.env.EG_PASSWORD || process.env.PASSWORD,

View file

@ -2,7 +2,7 @@ import { firefox } from 'playwright'; // stealth plugin needs no outdated playwr
import { authenticator } from 'otplib'; import { authenticator } from 'otplib';
import path from 'path'; import path from 'path';
import { existsSync, writeFileSync } from 'fs'; 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'; import { cfg } from './config.js';
const URL_CLAIM = 'https://store.epicgames.com/en-US/free-games'; 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 // const ext = path.resolve('nopecha'); // used in Chromium, currently not needed in Firefox
// https://playwright.dev/docs/auth#multi-factor-authentication // 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 // chrome will not work in linux arm64, only chromium
// channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge // channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge
headless: cfg.headless, headless: cfg.headless,
@ -156,7 +156,7 @@ try {
// 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.') 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); // 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 }); // await captcha.screenshot({ path: p });
// console.info(' Saved a screenshot of hcaptcha challenge to', 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.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.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.'); 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 }); await page.screenshot({ path: p, fullPage: true });
db.data[user][game_id].status = 'failed'; db.data[user][game_id].status = 'failed';
} }
notify_game.status = db.data[user][game_id].status; // claimed or 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... if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long...
} }
} }
@ -190,5 +190,5 @@ try {
notify(`epic-games:<br>${html_game_list(notify_games)}`); notify(`epic-games:<br>${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(); await context.close();

6
gog.js
View file

@ -1,6 +1,6 @@
import { firefox } 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 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'; import { cfg } from './config.js';
const URL_CLAIM = 'https://www.gog.com/en'; const URL_CLAIM = 'https://www.gog.com/en';
@ -11,7 +11,7 @@ const db = await jsonDb('gog.json');
db.data ||= {}; db.data ||= {};
// https://playwright.dev/docs/auth#multi-factor-authentication // 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, 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 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}`); console.log(`Current free game: ${title} - ${url}`);
db.data[user][title] ||= { title, time: datetime(), url }; db.data[user][title] ||= { title, time: datetime(), url };
if (cfg.dryrun) process.exit(1); 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.screenshot({ path: p }); // overwrites every time - only keep first?
// await banner.getByRole('button', { name: 'Add to library' }).click(); // await banner.getByRole('button', { name: 'Add to library' }).click();

View file

@ -1,7 +1,7 @@
import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra import { firefox } from 'playwright'; // stealth plugin needs no outdated playwright-extra
import { authenticator } from 'otplib'; import { authenticator } from 'otplib';
import path from 'path'; 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'; import { cfg } from './config.js';
// const URL_LOGIN = 'https://www.amazon.de/ap/signin'; // wrong. needs some session args to be valid? // 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 ||= {}; db.data ||= {};
// https://playwright.dev/docs/auth#multi-factor-authentication // 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, 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 locale: "en-US", // ignore OS locale to be sure to have english text for locators
@ -98,7 +98,7 @@ try {
if (cfg.dryrun) continue; if (cfg.dryrun) continue;
// const img = await (await card.$('img.tw-image')).getAttribute('src'); // const img = await (await card.$('img.tw-image')).getAttribute('src');
// console.log('Image:', img); // 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 card.screenshot({ path: p });
await (await card.$('button:has-text("Claim game")')).click(); await (await card.$('button:has-text("Claim game")')).click();
db.data[user][title] ||= { title, time: datetime(), store: 'internal' }; db.data[user][title] ||= { title, time: datetime(), store: 'internal' };
@ -150,7 +150,7 @@ try {
notify_game.status = `claimed on ${store}`; notify_game.status = `claimed on ${store}`;
} }
// save screenshot of potential code just in case // 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 }); 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);
} }
@ -158,7 +158,7 @@ try {
await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' });
await page.click('button[data-type="Game"]'); await page.click('button[data-type="Game"]');
} while (n); } 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.screenshot({ path: p, fullPage: true });
await page.locator(games_sel).screenshot({ path: p }); await page.locator(games_sel).screenshot({ path: p });
} catch (error) { } catch (error) {

View file

@ -5,13 +5,7 @@ import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename); 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 // 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 dataDir = s => path.resolve(__dirname, 'data', s);
export const dirs = {
data: dataDir('.'),
browser: dataDir('browser'),
screenshots: dataDir('screenshots'),
};
// json database // json database
import { Low } from 'lowdb'; import { Low } from 'lowdb';