Clean up Sonar issues and lint warnings
Some checks failed
build-and-push / lint (push) Failing after 5s
build-and-push / sonar (push) Has been skipped
build-and-push / docker (push) Has been skipped

This commit is contained in:
nocci 2025-12-30 16:45:17 +00:00
parent b9aa6e0073
commit 7ffc454e47
6 changed files with 124 additions and 89 deletions

View file

@ -48,9 +48,18 @@ 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 }); // 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
if (cfg.debug) console.debug(await page.evaluate(() => [(({ width, height, availWidth, availHeight }) => ({ width, height, availWidth, availHeight }))(window.screen), navigator.userAgent, navigator.platform, navigator.vendor])); // deconstruct screen needed since `window.screen` prints {}, `window.screen.toString()` '[object Screen]', and can't use some pick function without defining it on `page`
// some debug info about the page (screen dimensions, user agent)
if (cfg.debug) {
// eslint-disable-next-line no-undef
const debugInfo = await page.evaluate(() => {
const { width, height, availWidth, availHeight } = window.screen;
return {
screen: { width, height, availWidth, availHeight },
userAgent: navigator.userAgent,
};
});
console.debug(debugInfo);
}
if (cfg.debug_network) {
// const filter = _ => true;
const filter = r => r.url().includes('store.epicgames.com');
@ -90,9 +99,8 @@ try {
}
};
const email = cfg.eg_email || await prompt({ message: 'Enter email' });
if (!email) await notifyBrowserLogin();
else {
void (async () => {
if (email) {
const watchCaptchaChallenge = 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.');
@ -100,24 +108,25 @@ try {
} catch {
return;
}
})();
void (async () => {
};
const watchCaptchaIncorrect = async () => {
try {
await page.waitForSelector('p:has-text("Incorrect response.")', { timeout: 15000 });
console.error('Incorrect response for captcha!');
} catch {
return;
}
})();
};
watchCaptchaChallenge();
watchCaptchaIncorrect();
await page.fill('#email', email);
const password = email && (cfg.eg_password || await prompt({ type: 'password', message: 'Enter password' }));
if (!password) await notifyBrowserLogin();
else {
const password = cfg.eg_password || await prompt({ type: 'password', message: 'Enter password' });
if (password) {
await page.fill('#password', password);
await page.click('button[type="submit"]');
}
} else await notifyBrowserLogin();
const error = page.locator('#form-error-message');
void (async () => {
const watchLoginError = async () => {
try {
await error.waitFor({ timeout: 15000 });
console.error('Login error:', await error.innerText());
@ -125,8 +134,8 @@ try {
} catch {
return;
}
})();
void (async () => {
};
const watchMfaStep = 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 ...');
@ -136,8 +145,10 @@ try {
} catch {
return;
}
})();
}
};
watchLoginError();
watchMfaStep();
} else await notifyBrowserLogin();
await page.waitForURL(URL_CLAIM);
if (!cfg.debug) context.setDefaultTimeout(cfg.timeout);
}
@ -223,14 +234,13 @@ try {
console.log(' Base game:', baseUrl);
// await page.click('a:has-text("Overview")');
// 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
urls.push(baseUrl, url); // add base game to the list of games to claim and re-add add-on itself
} 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
// Accept End User License Agreement (only needed once)
void (async () => {
const acceptEulaIfShown = async () => {
try {
await page.locator(':has-text("end user license agreement")').waitFor({ timeout: 10000 });
console.log(' Accept End User License Agreement (only needed once)');
@ -239,7 +249,8 @@ try {
} catch {
return;
}
})();
};
acceptEulaIfShown();
// it then creates an iframe for the purchase
await page.waitForSelector('#webPurchaseContainer iframe');
@ -252,7 +263,7 @@ try {
continue;
}
void (async () => {
const enterParentalPinIfNeeded = async () => {
try {
await iframe.locator('.payment-pin-code').waitFor({ timeout: 10000 });
if (!cfg.eg_parentalpin) {
@ -264,7 +275,8 @@ try {
} catch {
return;
}
})();
};
enterParentalPinIfNeeded();
if (cfg.debug) await page.pause();
if (cfg.dryrun) {
@ -279,18 +291,19 @@ 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")');
void (async () => {
const acceptIfRequired = async () => {
try {
await btnAgree.waitFor({ timeout: 10000 });
await btnAgree.click();
} catch {
return;
}
})(); // EU: wait for and click 'I Agree'
}; // EU: wait for and click 'I Agree'
acceptIfRequired();
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');
void (async () => {
const watchCaptchaChallenge = 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.');
@ -298,8 +311,8 @@ try {
} catch {
return;
}
})(); // may time out if not shown
void (async () => {
}; // may time out if not shown
const watchCaptchaFailure = 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.');
@ -307,7 +320,9 @@ try {
} catch {
return;
}
})();
};
watchCaptchaChallenge();
watchCaptchaFailure();
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

View file

@ -11,7 +11,7 @@ export default [
{
ignores: ['data/**'],
},
js.configs.recommended, // TODO still needed?
js.configs.recommended,
{
// files: ['*.js'],
languageOptions: {

13
gog.js
View file

@ -60,7 +60,7 @@ try {
await iframe.locator('#login_username').fill(email);
await iframe.locator('#login_password').fill(password);
await iframe.locator('#login_login').click();
void (async () => {
const handleTwoFactor = async () => {
try {
await iframe.locator('form[name=second_step_authentication]').waitFor({ timeout: 15000 });
console.log('Two-Step Verification - Enter security code');
@ -72,8 +72,8 @@ try {
} catch {
return;
}
})();
void (async () => {
};
const watchInvalidCaptcha = async () => {
try {
await iframe.locator('text=Invalid captcha').waitFor({ 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.');
@ -81,7 +81,9 @@ try {
} catch {
return;
}
})();
};
handleTwoFactor();
watchInvalidCaptcha();
await page.waitForSelector('#menuUsername');
} else {
console.log('Waiting for you to login in the browser.');
@ -100,7 +102,8 @@ try {
db.data[user] ||= {};
const banner = page.locator('#giveaway');
if (!await banner.count()) {
const hasGiveaway = await banner.count();
if (!hasGiveaway) {
console.log('Currently no free giveaway!');
} else {
const text = await page.locator('.giveaway__content-header').innerText();

View file

@ -92,7 +92,11 @@ try {
'[data-a-target="user-dropdown-first-name-text"]',
'[data-testid="user-dropdown-first-name-text"]',
].map(s => page.waitForSelector(s)));
page.click('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")').catch(() => { }); // to not waste screen space when non-headless; could be flaky
try {
await page.click('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")'); // to not waste screen space when non-headless; could be flaky
} catch {
// ignore if banner not present
}
while (await page.locator('button:has-text("Sign in"), button:has-text("Anmelden")').count() > 0) {
console.error('Not signed in anymore.');
await page.click('button:has-text("Sign in")');
@ -119,7 +123,11 @@ try {
} catch {
// navigation ok
}
handleMFA(page).catch(() => {});
try {
await handleMFA(page);
} catch {
// ignore MFA watcher errors
}
} else {
console.log('Waiting for you to login in the browser.');
await notify('prime-gaming: no longer signed in and not enough options set for automatic login.');
@ -297,7 +305,6 @@ try {
return [p, isNew];
};
const skipBasedOnTime = async url => {
// console.log(' Checking time left for game:', url);
const [p, isNew] = await sameOrNewPage(url);
const dueDateLoc = p.locator('.availability-date .tw-bold, [data-testid="availability-end-date"], [data-test-selector="availability-end-date"]');
if (!await dueDateLoc.count()) {
@ -321,7 +328,10 @@ try {
console.log('Current free game:', chalk.blue(title));
if (cfg.pg_timeLeft && url && await skipBasedOnTime(url)) continue;
if (cfg.dryrun) continue;
if (cfg.interactive && !await confirm()) continue;
if (cfg.interactive) {
const confirmed = await confirm();
if (!confirmed) continue;
}
await card.handle.locator('.tw-button:has-text("Claim"), .tw-button:has-text("Get"), button:has-text("Claim"), button:has-text("Get")').first().click();
db.data[user][title] ||= { title, time: datetime(), url, store: 'internal' };
notify_games.push({ title, status: 'claimed', url });
@ -336,8 +346,6 @@ try {
if (!url) continue;
external_info.push({ title, url });
}
// external_info = [ { title: 'Fallout 76 (XBOX)', url: 'https://gaming.amazon.com/fallout-76-xbox-fgwp/dp/amzn1.pg.item.9fe17d7b-b6c2-4f58-b494-cc4e79528d0b?ingress=amzn&ref_=SM_Fallout76XBOX_S01_FGWP_CRWN' } ];
const clickCTA = async p => {
const candidates = [
p.locator('button[data-a-target="buy-box_call-to-action"]').first(),
@ -353,14 +361,14 @@ try {
if (await c.count()) {
try {
await c.waitFor({ state: 'visible', timeout: 5000 });
if (!await c.isEnabled()) {
const enabled = await c.isEnabled();
if (enabled) await c.click();
else {
await c.evaluate(el => {
el.disabled = false;
el.removeAttribute('disabled');
el.click();
});
} else {
await c.click();
}
return true;
} catch {
@ -442,7 +450,10 @@ try {
}
if (cfg.pg_timeLeft && await skipBasedOnTime(url)) continue;
if (cfg.dryrun) continue;
if (cfg.interactive && !await confirm()) continue;
if (cfg.interactive) {
const confirmed = await confirm();
if (!confirmed) continue;
}
await clickCTA(page);
await Promise.any([
page.waitForSelector('.thank-you-title:has-text("Success")', { timeout: cfg.timeout }).catch(() => {}),
@ -499,13 +510,9 @@ try {
const page2 = await context.newPage();
await page2.goto(redeem[store], { waitUntil: 'domcontentloaded' });
if (store == 'gog.com') {
// await page.goto(`https://redeem.gog.com/v1/bonusCodes/${code}`); // {"reason":"Invalid or no captcha"}
await page2.fill('#codeInput', code);
// wait for responses before clicking on Continue and then Redeem
// first there are requests with OPTIONS and GET to https://redeem.gog.com/v1/bonusCodes/XYZ?language=de-DE
const r1 = page2.waitForResponse(r => r.request().method() == 'GET' && r.url().startsWith('https://redeem.gog.com/'));
await page2.click('[type="submit"]'); // click Continue
// console.log(await page2.locator('.warning-message').innerText()); // does not exist if there is no warning
const r1t = await (await r1).text();
const reason = JSON.parse(r1t).reason;
// {"reason":"Invalid or no captcha"}
@ -520,14 +527,12 @@ try {
} else if (reason == 'code_not_found') {
redeem_action = 'redeem (not found)';
console.error(' Code was not found!');
} else { // unknown state; keep info log for later analysis
redeem_action = 'redeemed?';
// console.log(' Redeemed successfully? Please report your Responses (if new) in https://github.com/vogler/free-games-claimer/issues/5');
console.debug(` Response 1: ${r1t}`);
// then after the click on Redeem there is a POST request which should return {} if claimed successfully
const r2 = page2.waitForResponse(r => r.request().method() == 'POST' && r.url().startsWith('https://redeem.gog.com/'));
await page2.click('[type="submit"]'); // click Redeem
const r2t = await (await r2).text();
} else { // unknown state; keep info log for later analysis
redeem_action = 'redeemed?';
console.debug(` Response 1: ${r1t}`);
const r2 = page2.waitForResponse(r => r.request().method() == 'POST' && r.url().startsWith('https://redeem.gog.com/'));
await page2.click('[type="submit"]'); // click Redeem
const r2t = await (await r2).text();
const reason2 = JSON.parse(r2t).reason;
if (r2t == '{}') {
redeem_action = 'redeemed';
@ -543,7 +548,6 @@ try {
}
} else if (store == 'microsoft store' || store == 'xbox') {
console.error(` Redeem on ${store} is experimental!`);
// await page2.pause();
if (page2.url().startsWith('https://login.')) {
console.error(' Not logged in! Please redeem the code above manually. You can now login in the browser for next time. Waiting for 60s.');
await page2.waitForTimeout(60 * 1000);
@ -554,9 +558,7 @@ try {
await input.waitFor();
await input.fill(code);
const r = page2.waitForResponse(r => r.url().startsWith('https://cart.production.store-web.dynamics.com/v1.0/Redeem/PrepareRedeem'));
// console.log(await page2.locator('.redeem_code_error').innerText());
const rt = await (await r).text();
// {"code":"NotFound","data":[],"details":[],"innererror":{"code":"TokenNotFound",...
const j = JSON.parse(rt);
const reason = j?.events?.cart.length && j.events.cart[0]?.data?.reason;
if (reason == 'TokenNotFound') {
@ -582,14 +584,12 @@ try {
}
}
} else if (store == 'legacy games') {
// await page2.pause();
await page2.fill('[name=coupon_code]', code);
await page2.fill('[name=email]', cfg.lg_email);
await page2.fill('[name=email_validate]', cfg.lg_email);
await page2.uncheck('[name=newsletter_sub]');
await page2.click('[type="submit"]');
try {
// await page2.waitForResponse(r => r.url().startsWith('https://promo.legacygames.com/promotion-processing/order-management.php')); // status code 302
await page2.waitForSelector('h2:has-text("Thanks for redeeming")');
redeem_action = 'redeemed';
db.data[user][title].status = 'claimed and redeemed';
@ -609,15 +609,16 @@ try {
} else {
notify_game.status = `claimed on ${store}`;
db.data[user][title].status = 'claimed';
}
// save screenshot of potential code just in case
await page.screenshot({ path: screenshot('external', `${filenamify(title)}.png`), fullPage: true });
// console.info(' Saved a screenshot of page to', p);
}
// await page.pause();
await page.screenshot({ path: screenshot('external', `${filenamify(title)}.png`), fullPage: true });
}
}
await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' });
page.click('button[data-type="Game"]').catch(() => {});
try {
await page.click('button[data-type="Game"]');
} catch {
// ignore if filter already selected
}
if (notify_games.length && games) { // make screenshot of all games if something was claimed and list exists
const p = screenshot(`${filenamify(datetime())}.png`);
@ -663,15 +664,28 @@ try {
await page.goto(url, { waitUntil: 'domcontentloaded' });
// most games have a button 'Get in-game content'
// epic-games: Fall Guys: Claim -> Continue -> Go to Epic Games (despite account linked and logged into epic-games) -> not tied to account but via some cookie?
await Promise.any([page.click('.tw-button:has-text("Get in-game content")'), page.click('.tw-button:has-text("Claim your gift")'), page.click('.tw-button:has-text("Claim")').then(() => page.click('button:has-text("Continue")'))]);
page.click('button:has-text("Continue")').catch(() => { });
const claimOptions = [
page.click('.tw-button:has-text("Get in-game content")'),
page.click('.tw-button:has-text("Claim your gift")'),
(async () => {
await page.click('.tw-button:has-text("Claim")');
await page.click('button:has-text("Continue")').catch(() => {});
})(),
];
await Promise.any(claimOptions);
try {
await page.click('button:has-text("Continue")');
} catch {
// continue button not always present
}
const linkAccountButton = page.locator('[data-a-target="LinkAccountButton"]');
let unlinked_store;
if (await linkAccountButton.count()) {
unlinked_store = await linkAccountButton.first().getAttribute('aria-label');
console.debug(' LinkAccountButton label:', unlinked_store);
const match = unlinked_store.match(/Link (.*) account/);
if (match && match.length == 2) unlinked_store = match[1];
const match = unlinked_store?.match(/Link (.*) account/);
const extracted = match?.[1];
if (extracted) unlinked_store = extracted;
} else if (await page.locator('text=Link game account').count()) { // epic-games only?
console.error(' Missing account linking (epic-games specific button?):', await page.locator('button[data-a-target="gms-cta"]').innerText()); // track account-linking UI drift
unlinked_store = 'epic-games';
@ -685,9 +699,7 @@ try {
console.log(' Code to redeem game:', chalk.blue(code));
db.data[user][title].code = code;
db.data[user][title].status = 'claimed';
// notify_game.status = `<a href="${redeem[store]}">${redeem_action}</a> ${code} on ${store}`;
}
// await page.pause();
} catch (error) {
console.error(error);
} finally {

View file

@ -6,9 +6,9 @@ handleSIGINT();
console.log('hello');
console.error('hello error');
try {
let i = await prompt(); // SIGINT no longer handled if this is executed
i = await prompt(); // SIGINT no longer handled if this is executed
console.log('value:', i);
const first = await prompt(); // SIGINT no longer handled if this is executed
const second = await prompt(); // SIGINT no longer handled if this is executed
console.log('values:', first, second);
setTimeout(() => console.log('timeout 3s'), 3000);
} catch (e) {
process.exitCode ||= 1;

View file

@ -59,7 +59,7 @@ try {
await page.click('button[type="submit"]');
await page.fill('#password', password);
await page.click('button[type="submit"]');
void (async () => {
const watchCaptchaDuringLogin = async () => {
try {
await page.waitForSelector('#h_captcha_challenge_login_prod 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.');
@ -67,8 +67,8 @@ try {
} catch {
return;
}
})();
void (async () => {
};
const watchMfa = 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 ...');
@ -78,7 +78,9 @@ try {
} catch {
return;
}
})();
};
watchCaptchaDuringLogin();
watchMfa();
} else {
console.log('Waiting for you to login in the browser.');
await notify('unrealengine: no longer signed in and not enough options set for automatic login.');
@ -125,7 +127,7 @@ try {
}
ids.push(id);
}
if (!ids.length) {
if (ids.length === 0) {
console.log('Nothing to claim');
} else {
await page.waitForTimeout(2000);
@ -142,7 +144,7 @@ try {
await page.locator('button.checkout').click();
console.log('Click checkout');
// maybe: Accept End User License Agreement
void (async () => {
const acceptEulaIfPresent = async () => {
try {
await page.locator('[name=accept-label]').check({ timeout: 10000 });
console.log('Accept End User License Agreement');
@ -150,7 +152,8 @@ try {
} catch {
return;
}
})();
};
acceptEulaIfPresent();
await page.waitForSelector('#webPurchaseContainer iframe');
const iframe = page.frameLocator('#webPurchaseContainer iframe');
@ -166,25 +169,27 @@ 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 Agree")');
void (async () => {
const acceptIfRequired = async () => {
try {
await btnAgree.waitFor({ timeout: 10000 });
await btnAgree.click();
} catch {
return;
}
})(); // EU: wait for and click 'I Agree'
}; // EU: wait for and click 'I Agree'
acceptIfRequired();
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');
void (async () => {
const watchCaptchaChallenge = 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.');
} catch {
return;
}
})(); // may time out if not shown
}; // may time out if not shown
watchCaptchaChallenge();
await page.waitForSelector('text=Thank you');
for (const id of ids) {
db.data[user][id].status = 'claimed';