Clean Sonar issues in store scripts
This commit is contained in:
parent
397871b012
commit
5f919039ab
6 changed files with 172 additions and 187 deletions
151
epic-games.js
151
epic-games.js
|
|
@ -29,17 +29,13 @@ if (existsSync(browserPrefs)) {
|
|||
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; 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
|
||||
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:127.0) Gecko/20100101 Firefox/127.0', // Windows UA avoids "device not supported"; update when browser version changes
|
||||
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-${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
|
||||
// '-kiosk',
|
||||
],
|
||||
args: [], // https://wiki.mozilla.org/Firefox/CommandLineOptions
|
||||
});
|
||||
|
||||
handleSIGINT(context);
|
||||
|
|
@ -50,7 +46,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
|
||||
await page.setViewportSize({ width: cfg.width, height: cfg.height }); // 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
|
||||
|
|
@ -76,8 +72,6 @@ try {
|
|||
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('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.`);
|
||||
|
|
@ -98,16 +92,20 @@ try {
|
|||
const email = cfg.eg_email || await prompt({ message: 'Enter email' });
|
||||
if (!email) await notifyBrowserLogin();
|
||||
else {
|
||||
// await page.click('text=Sign in with Epic Games');
|
||||
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('p:has-text("Incorrect response.")').then(async () => {
|
||||
console.error('Incorrect response for captcha!');
|
||||
}).catch(_ => { });
|
||||
void (async () => {
|
||||
try {
|
||||
await page.waitForSelector('.h_captcha_challenge iframe', { timeout: 15000 });
|
||||
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 {}
|
||||
})();
|
||||
void (async () => {
|
||||
try {
|
||||
await page.waitForSelector('p:has-text("Incorrect response.")', { timeout: 15000 });
|
||||
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
|
||||
const password = email && (cfg.eg_password || await prompt({ type: 'password', message: 'Enter password' }));
|
||||
if (!password) await notifyBrowserLogin();
|
||||
else {
|
||||
|
|
@ -115,18 +113,22 @@ try {
|
|||
await page.click('button[type="submit"]');
|
||||
}
|
||||
const error = page.locator('#form-error-message');
|
||||
error.waitFor().then(async () => {
|
||||
console.error('Login error:', await error.innerText());
|
||||
console.log('Please login in the browser!');
|
||||
}).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.locator('input[name="code-input-0"]').pressSequentially(otp.toString());
|
||||
await page.click('button[type="submit"]');
|
||||
}).catch(_ => { });
|
||||
void (async () => {
|
||||
try {
|
||||
await error.waitFor({ timeout: 15000 });
|
||||
console.error('Login error:', await error.innerText());
|
||||
console.log('Please login in the browser!');
|
||||
} catch {}
|
||||
})();
|
||||
void (async () => {
|
||||
try {
|
||||
await page.waitForURL('**/id/login/mfa**', { timeout: cfg.login_timeout });
|
||||
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 = 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 {}
|
||||
})();
|
||||
}
|
||||
await page.waitForURL(URL_CLAIM);
|
||||
if (!cfg.debug) context.setDefaultTimeout(cfg.timeout);
|
||||
|
|
@ -141,7 +143,7 @@ try {
|
|||
const game_loc = page.locator('a:has(span:text-is("Free Now"))');
|
||||
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
|
||||
// waiting for timeout; alternative would be waiting for "coming soon"
|
||||
// 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
|
||||
|
|
@ -208,36 +210,31 @@ 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
|
||||
// if base game is free, add to queue as well
|
||||
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
|
||||
// re-add original add-on to queue after base game
|
||||
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', 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?
|
||||
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(':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; 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(_ => { });
|
||||
void (async () => {
|
||||
try {
|
||||
await page.locator(':has-text("end user license agreement")').waitFor({ timeout: 10000 });
|
||||
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?
|
||||
await page.waitForSelector('#webPurchaseContainer iframe');
|
||||
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
|
||||
// skip game if unavailable in region, https://github.com/vogler/free-games-claimer/issues/46
|
||||
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';
|
||||
|
|
@ -245,14 +242,17 @@ try {
|
|||
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().pressSequentially(cfg.eg_parentalpin);
|
||||
await iframe.locator('button:has-text("Continue")').click({ delay: 11 });
|
||||
}).catch(_ => { });
|
||||
void (async () => {
|
||||
try {
|
||||
await iframe.locator('.payment-pin-code').waitFor({ timeout: 10000 });
|
||||
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().pressSequentially(cfg.eg_parentalpin);
|
||||
await iframe.locator('button:has-text("Continue")').click({ delay: 11 });
|
||||
} catch {}
|
||||
})();
|
||||
|
||||
if (cfg.debug) await page.pause();
|
||||
if (cfg.dryrun) {
|
||||
|
|
@ -267,27 +267,30 @@ 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 Accept")');
|
||||
btnAgree.waitFor().then(() => btnAgree.click()).catch(_ => { }); // EU: wait for and click 'I Agree'
|
||||
void (async () => {
|
||||
try {
|
||||
await btnAgree.waitFor({ timeout: 10000 });
|
||||
await 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 notify(`epic-games: got captcha challenge right before claim of <a href="${url}">${title}</a>. 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 });
|
||||
// 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.');
|
||||
}).catch(_ => { });
|
||||
await page.locator('text=Thanks for your order!').waitFor({ state: 'attached' }); // TODO Bundle: got stuck here, but normal game now as well
|
||||
void (async () => {
|
||||
try {
|
||||
await captcha.waitFor({ timeout: 10000 });
|
||||
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 for.\nGame link: ${url}`);
|
||||
} catch {}
|
||||
})(); // may time out if not shown
|
||||
void (async () => {
|
||||
try {
|
||||
await iframe.locator('.payment__errors:has-text("Failed to challenge captcha, please try again later.")').waitFor({ timeout: 10000 });
|
||||
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' });
|
||||
db.data[user][game_id].status = 'claimed';
|
||||
db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time
|
||||
console.log(' Claimed successfully!');
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue