BROWSER_DIR for multiple profiles or testing, SCREENSHOTS_DIR, closes #12
This commit is contained in:
parent
a90062b631
commit
e2b07dc1e6
6 changed files with 24 additions and 21 deletions
|
|
@ -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. |
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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
6
gog.js
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
8
util.js
8
util.js
|
|
@ -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';
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue