👷 ci(build): add SonarQube scan to build workflow
- introduce SonarQube scanning step for code quality analysis - update workflow dependencies and execution order 🐛 fix(auth): improve error handling and code formatting - remove unused imports and fix code indentation - enhance error handling with improved catch blocks 💄 style(general): standardize code formatting and style consistency - update various files to ensure consistent code style - adjust indentation and whitespace for readability
This commit is contained in:
parent
0e5303da62
commit
d40a577f47
7 changed files with 95 additions and 62 deletions
|
|
@ -20,9 +20,32 @@ jobs:
|
||||||
- name: Run ESLint
|
- name: Run ESLint
|
||||||
run: npm run lint
|
run: npm run lint
|
||||||
|
|
||||||
docker:
|
sonar:
|
||||||
needs: lint
|
needs: lint
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: SonarQube Scan
|
||||||
|
env:
|
||||||
|
SONAR_HOST_URL: ${{ secrets.SONAR_HOST_URL }}
|
||||||
|
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
|
||||||
|
run: |
|
||||||
|
docker run --rm \
|
||||||
|
-e SONAR_HOST_URL="$SONAR_HOST_URL" \
|
||||||
|
-e SONAR_TOKEN="$SONAR_TOKEN" \
|
||||||
|
-v "$PWD:/usr/src" \
|
||||||
|
-w /usr/src \
|
||||||
|
sonarsource/sonar-scanner-cli \
|
||||||
|
sonar-scanner \
|
||||||
|
-Dsonar.host.url="$SONAR_HOST_URL" \
|
||||||
|
-Dsonar.login="$SONAR_TOKEN"
|
||||||
|
|
||||||
|
docker:
|
||||||
|
needs: [lint, sonar]
|
||||||
|
runs-on: self-hosted
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra
|
import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra
|
||||||
import { datetime, filenamify, prompt, handleSIGINT, stealth } from './src/util.js';
|
import { datetime, filenamify, prompt, handleSIGINT } from './src/util.js';
|
||||||
import { cfg } from './src/config.js';
|
import { cfg } from './src/config.js';
|
||||||
|
|
||||||
// using https://github.com/apify/fingerprint-suite worked, but has no launchPersistentContext...
|
// using https://github.com/apify/fingerprint-suite worked, but has no launchPersistentContext...
|
||||||
|
|
@ -8,8 +8,8 @@ import { FingerprintInjector } from 'fingerprint-injector';
|
||||||
import { FingerprintGenerator } from 'fingerprint-generator';
|
import { FingerprintGenerator } from 'fingerprint-generator';
|
||||||
|
|
||||||
const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({
|
const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({
|
||||||
devices: ["mobile"],
|
devices: ['mobile'],
|
||||||
operatingSystems: ["android"],
|
operatingSystems: ['android'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const context = await firefox.launchPersistentContext(cfg.dir.browser, {
|
const context = await firefox.launchPersistentContext(cfg.dir.browser, {
|
||||||
|
|
@ -36,7 +36,7 @@ context.setDefaultTimeout(cfg.debug ? 0 : cfg.timeout);
|
||||||
|
|
||||||
const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist
|
const page = context.pages().length ? context.pages()[0] : await context.newPage(); // should always exist
|
||||||
|
|
||||||
const auth = async (url) => {
|
const auth = async url => {
|
||||||
console.log('auth', url);
|
console.log('auth', url);
|
||||||
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
||||||
// redirects to https://login.aliexpress.com/?return_url=https%3A%2F%2Fwww.aliexpress.com%2Fp%2Fcoin-pc-index%2Findex.html
|
// redirects to https://login.aliexpress.com/?return_url=https%3A%2F%2Fwww.aliexpress.com%2Fp%2Fcoin-pc-index%2Findex.html
|
||||||
|
|
@ -45,7 +45,7 @@ const auth = async (url) => {
|
||||||
console.error('Not logged in! Will wait for 120s for you to login...');
|
console.error('Not logged in! Will wait for 120s for you to login...');
|
||||||
// await page.waitForTimeout(120*1000);
|
// await page.waitForTimeout(120*1000);
|
||||||
// or try automated
|
// or try automated
|
||||||
page.locator('span:has-text("Switch account")').click().catch(_ => {}); // sometimes no longer logged in, but previous user/email is pre-selected -> in this case we want to go back to the classic login
|
page.locator('span:has-text("Switch account")').click().catch(() => {}); // sometimes no longer logged in, but previous user/email is pre-selected -> in this case we want to go back to the classic login
|
||||||
const login = page.locator('.login-container');
|
const login = page.locator('.login-container');
|
||||||
const email = cfg.ae_email || await prompt({ message: 'Enter email' });
|
const email = cfg.ae_email || await prompt({ message: 'Enter email' });
|
||||||
const emailInput = login.locator('input[label="Email or phone number"]');
|
const emailInput = login.locator('input[label="Email or phone number"]');
|
||||||
|
|
@ -58,11 +58,11 @@ const auth = async (url) => {
|
||||||
await login.locator('input[label="Password"]').fill(password);
|
await login.locator('input[label="Password"]').fill(password);
|
||||||
await login.locator('button:has-text("Sign in")').click();
|
await login.locator('button:has-text("Sign in")').click();
|
||||||
const error = login.locator('.error-text');
|
const error = login.locator('.error-text');
|
||||||
error.waitFor().then(async _ => console.error('Login error:', await error.innerText()));
|
error.waitFor().then(async () => console.error('Login error:', await error.innerText()));
|
||||||
await page.waitForURL(url);
|
await page.waitForURL(url);
|
||||||
// await page.addLocatorHandler(page.getByRole('button', { name: 'Accept cookies' }), btn => btn.click());
|
// await page.addLocatorHandler(page.getByRole('button', { name: 'Accept cookies' }), btn => btn.click());
|
||||||
page.getByRole('button', { name: 'Accept cookies' }).click().then(_ => console.log('Accepted cookies')).catch(_ => { });
|
page.getByRole('button', { name: 'Accept cookies' }).click().then(() => console.log('Accepted cookies')).catch(() => { });
|
||||||
}), page.locator('#nav-user-account').waitFor()]).catch(_ => {});
|
}), page.locator('#nav-user-account').waitFor()]).catch(() => {});
|
||||||
|
|
||||||
// await page.locator('#nav-user-account').hover();
|
// await page.locator('#nav-user-account').hover();
|
||||||
// console.log('Logged in as:', await page.locator('.welcome-name').innerText());
|
// console.log('Logged in as:', await page.locator('.welcome-name').innerText());
|
||||||
|
|
@ -80,6 +80,7 @@ const urls = {
|
||||||
merge: 'https://m.aliexpress.com/p/merge-market/index.html',
|
merge: 'https://m.aliexpress.com/p/merge-market/index.html',
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* eslint-disable no-unused-vars */
|
||||||
const coins = async () => {
|
const coins = async () => {
|
||||||
// await auth(urls.coins);
|
// await auth(urls.coins);
|
||||||
await Promise.any([page.locator('.checkin-button').click(), page.locator('.addcoin').waitFor()]);
|
await Promise.any([page.locator('.checkin-button').click(), page.locator('.addcoin').waitFor()]);
|
||||||
|
|
@ -103,6 +104,7 @@ const euro = async () => {
|
||||||
const merge = async () => {
|
const merge = async () => {
|
||||||
await page.pause();
|
await page.pause();
|
||||||
};
|
};
|
||||||
|
/* eslint-enable no-unused-vars */
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// await coins();
|
// await coins();
|
||||||
|
|
@ -112,7 +114,11 @@ try {
|
||||||
// gogo,
|
// gogo,
|
||||||
// euro,
|
// euro,
|
||||||
merge,
|
merge,
|
||||||
].reduce((a, f) => a.then(async _ => { await auth(urls[f.name]); await f(); console.log() }), Promise.resolve());
|
].reduce((a, f) => a.then(async () => {
|
||||||
|
await auth(urls[f.name]);
|
||||||
|
await f();
|
||||||
|
console.log();
|
||||||
|
}), Promise.resolve());
|
||||||
|
|
||||||
// await page.pause();
|
// await page.pause();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ const handleMFA = async p => {
|
||||||
const otpField = p.locator('#auth-mfa-otpcode, input[name=otpCode]');
|
const otpField = p.locator('#auth-mfa-otpcode, input[name=otpCode]');
|
||||||
if (!await otpField.count()) return false;
|
if (!await otpField.count()) return false;
|
||||||
console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App');
|
console.log('Two-Step Verification - enter the One Time Password (OTP), e.g. generated by your Authenticator App');
|
||||||
await p.locator('#auth-mfa-remember-device, [name=rememberDevice]').check().catch(_ => {});
|
await p.locator('#auth-mfa-remember-device, [name=rememberDevice]').check().catch(() => {});
|
||||||
const otp = cfg.pg_otpkey && authenticator.generate(cfg.pg_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
|
const otp = cfg.pg_otpkey && authenticator.generate(cfg.pg_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 otpField.first().pressSequentially(otp.toString());
|
await otpField.first().pressSequentially(otp.toString());
|
||||||
await p.locator('input[type="submit"], button[type="submit"]').first().click();
|
await p.locator('input[type="submit"], button[type="submit"]').first().click();
|
||||||
|
|
@ -61,7 +61,7 @@ try {
|
||||||
await page.click('input[type="submit"]');
|
await page.click('input[type="submit"]');
|
||||||
await page.fill('[name=password]', password);
|
await page.fill('[name=password]', password);
|
||||||
await page.click('input[type="submit"]');
|
await page.click('input[type="submit"]');
|
||||||
await handleMFA(page).catch(_ => {});
|
await handleMFA(page).catch(() => {});
|
||||||
page.waitForURL('**/ap/signin**').then(async () => { // check for wrong credentials
|
page.waitForURL('**/ap/signin**').then(async () => { // check for wrong credentials
|
||||||
const error = await page.locator('.a-alert-content').first().innerText();
|
const error = await page.locator('.a-alert-content').first().innerText();
|
||||||
if (!error.trim.length) return;
|
if (!error.trim.length) return;
|
||||||
|
|
@ -91,7 +91,7 @@ try {
|
||||||
'[data-a-target="user-dropdown-first-name-text"]',
|
'[data-a-target="user-dropdown-first-name-text"]',
|
||||||
'[data-testid="user-dropdown-first-name-text"]',
|
'[data-testid="user-dropdown-first-name-text"]',
|
||||||
].map(s => page.waitForSelector(s)));
|
].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, TODO does not work reliably, need to wait for something else first?
|
page.click('[aria-label="Cookies usage disclaimer banner"] button:has-text("Accept Cookies")').catch(() => { }); // to not waste screen space when non-headless, TODO does not work reliably, need to wait for something else first?
|
||||||
while (await page.locator('button:has-text("Sign in"), button:has-text("Anmelden")').count() > 0) {
|
while (await page.locator('button:has-text("Sign in"), button:has-text("Anmelden")').count() > 0) {
|
||||||
console.error('Not signed in anymore.');
|
console.error('Not signed in anymore.');
|
||||||
await page.click('button:has-text("Sign in")');
|
await page.click('button:has-text("Sign in")');
|
||||||
|
|
@ -115,7 +115,7 @@ try {
|
||||||
await context.close(); // finishes potential recording
|
await context.close(); // finishes potential recording
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
handleMFA(page).catch(_ => {});
|
handleMFA(page).catch(() => {});
|
||||||
} else {
|
} else {
|
||||||
console.log('Waiting for you to login in the browser.');
|
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.');
|
await notify('prime-gaming: no longer signed in and not enough options set for automatic login.');
|
||||||
|
|
@ -209,7 +209,7 @@ try {
|
||||||
const games = await locateGamesList();
|
const games = await locateGamesList();
|
||||||
// Load all cards (old and new layout) by scrolling the container or the page
|
// Load all cards (old and new layout) by scrolling the container or the page
|
||||||
if (games) await scrollUntilStable(() => games.evaluate(el => el.scrollHeight).catch(() => 0));
|
if (games) await scrollUntilStable(() => games.evaluate(el => el.scrollHeight).catch(() => 0));
|
||||||
await scrollUntilStable(() => page.evaluate(() => document.scrollingElement?.scrollHeight ?? 0));
|
await scrollUntilStable(() => page.evaluate(() => globalThis.document?.scrollingElement?.scrollHeight ?? 0));
|
||||||
|
|
||||||
const normalizeClaimUrl = url => {
|
const normalizeClaimUrl = url => {
|
||||||
if (!url) return { url, key: null };
|
if (!url) return { url, key: null };
|
||||||
|
|
@ -241,7 +241,7 @@ try {
|
||||||
const hrefs = [...new Set(await anchorClaims.evaluateAll(anchors => anchors.map(a => a.getAttribute('href')).filter(Boolean)))];
|
const hrefs = [...new Set(await anchorClaims.evaluateAll(anchors => anchors.map(a => a.getAttribute('href')).filter(Boolean)))];
|
||||||
for (const href of hrefs) {
|
for (const href of hrefs) {
|
||||||
const { url, key } = normalizeClaimUrl(href);
|
const { url, key } = normalizeClaimUrl(href);
|
||||||
const title = key || (await anchorClaims.first().innerText()) || 'Unknown title';
|
const title = key || await anchorClaims.first().innerText() || 'Unknown title';
|
||||||
cards.push({ kind: 'external', title, url, key });
|
cards.push({ kind: 'external', title, url, key });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -286,15 +286,15 @@ try {
|
||||||
// bottom to top: oldest to newest games
|
// bottom to top: oldest to newest games
|
||||||
internal.reverse();
|
internal.reverse();
|
||||||
external.reverse();
|
external.reverse();
|
||||||
const sameOrNewPage = async url => new Promise(async (resolve, _reject) => {
|
const sameOrNewPage = async url => {
|
||||||
const isNew = page.url() != url;
|
const isNew = page.url() != url;
|
||||||
let p = page;
|
let p = page;
|
||||||
if (isNew) {
|
if (isNew) {
|
||||||
p = await context.newPage();
|
p = await context.newPage();
|
||||||
await p.goto(url, { waitUntil: 'domcontentloaded' });
|
await p.goto(url, { waitUntil: 'domcontentloaded' });
|
||||||
}
|
}
|
||||||
resolve([p, isNew]);
|
return [p, isNew];
|
||||||
});
|
};
|
||||||
const skipBasedOnTime = async url => {
|
const skipBasedOnTime = async url => {
|
||||||
// console.log(' Checking time left for game:', url);
|
// console.log(' Checking time left for game:', url);
|
||||||
const [p, isNew] = await sameOrNewPage(url);
|
const [p, isNew] = await sameOrNewPage(url);
|
||||||
|
|
@ -305,12 +305,12 @@ try {
|
||||||
}
|
}
|
||||||
const dueDateOrg = await dueDateLoc.first().innerText();
|
const dueDateOrg = await dueDateLoc.first().innerText();
|
||||||
const dueDate = new Date(Date.parse(dueDateOrg + ' 17:00'));
|
const dueDate = new Date(Date.parse(dueDateOrg + ' 17:00'));
|
||||||
const daysLeft = (dueDate.getTime() - Date.now())/1000/60/60/24;
|
const daysLeft = (dueDate.getTime() - Date.now()) / 1000 / 60 / 60 / 24;
|
||||||
const availabilityText = await p.locator('.availability-date, [data-testid="availability-end-date"], [data-test-selector="availability-end-date"]').first().innerText().catch(() => dueDateOrg);
|
const availabilityText = await p.locator('.availability-date, [data-testid="availability-end-date"], [data-test-selector="availability-end-date"]').first().innerText().catch(() => dueDateOrg);
|
||||||
console.log(' ', availabilityText, '->', daysLeft.toFixed(2));
|
console.log(' ', availabilityText, '->', daysLeft.toFixed(2));
|
||||||
if (isNew) await p.close();
|
if (isNew) await p.close();
|
||||||
return daysLeft > cfg.pg_timeLeft;
|
return daysLeft > cfg.pg_timeLeft;
|
||||||
}
|
};
|
||||||
console.log('\nNumber of free unclaimed games (Prime Gaming):', internal.length);
|
console.log('\nNumber of free unclaimed games (Prime Gaming):', internal.length);
|
||||||
// claim games in internal store
|
// claim games in internal store
|
||||||
for (const card of internal) {
|
for (const card of internal) {
|
||||||
|
|
@ -353,12 +353,16 @@ try {
|
||||||
try {
|
try {
|
||||||
await c.waitFor({ state: 'visible', timeout: 5000 });
|
await c.waitFor({ state: 'visible', timeout: 5000 });
|
||||||
if (!await c.isEnabled()) {
|
if (!await c.isEnabled()) {
|
||||||
await c.evaluate(el => { el.disabled = false; el.removeAttribute('disabled'); el.click(); });
|
await c.evaluate(el => {
|
||||||
|
el.disabled = false;
|
||||||
|
el.removeAttribute('disabled');
|
||||||
|
el.click();
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
await c.click();
|
await c.click();
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (_) {
|
} catch {
|
||||||
// try next candidate
|
// try next candidate
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -375,7 +379,7 @@ try {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
await page.goto(url, { waitUntil: 'domcontentloaded' });
|
||||||
await page.waitForSelector('[data-a-target="buy-box"]', { timeout: 10000 }).catch(_ => {});
|
await page.waitForSelector('[data-a-target="buy-box"]', { timeout: 10000 }).catch(() => {});
|
||||||
if (cfg.debug) await page.pause();
|
if (cfg.debug) await page.pause();
|
||||||
let store = 'unknown';
|
let store = 'unknown';
|
||||||
const detailLoc = page.locator('[data-a-target="DescriptionItemDetails"], [data-testid="DescriptionItemDetails"]');
|
const detailLoc = page.locator('[data-a-target="DescriptionItemDetails"], [data-testid="DescriptionItemDetails"]');
|
||||||
|
|
@ -438,9 +442,9 @@ try {
|
||||||
if (cfg.interactive && !await confirm()) continue;
|
if (cfg.interactive && !await confirm()) continue;
|
||||||
await clickCTA(page);
|
await clickCTA(page);
|
||||||
await Promise.any([
|
await Promise.any([
|
||||||
page.waitForSelector('.thank-you-title:has-text("Success")', { timeout: cfg.timeout }).catch(_ => {}),
|
page.waitForSelector('.thank-you-title:has-text("Success")', { timeout: cfg.timeout }).catch(() => {}),
|
||||||
page.waitForSelector('div:has-text("Link game account")', { timeout: cfg.timeout }).catch(_ => {}),
|
page.waitForSelector('div:has-text("Link game account")', { timeout: cfg.timeout }).catch(() => {}),
|
||||||
]).catch(_ => {});
|
]).catch(() => {});
|
||||||
db.data[user][title] ||= { title, time: datetime(), url, store };
|
db.data[user][title] ||= { title, time: datetime(), url, store };
|
||||||
if (await page.locator('div:has-text("Link game account")').count() // TODO still needed? epic games store just has 'Link account' as the button text now.
|
if (await page.locator('div:has-text("Link game account")').count() // TODO still needed? epic games store just has 'Link account' as the button text now.
|
||||||
|| await page.locator('div:has-text("Link account")').count()) {
|
|| await page.locator('div:has-text("Link account")').count()) {
|
||||||
|
|
@ -466,16 +470,16 @@ try {
|
||||||
let code;
|
let code;
|
||||||
try {
|
try {
|
||||||
// ensure CTA was clicked in case code is behind it
|
// ensure CTA was clicked in case code is behind it
|
||||||
await clickCTA(page).catch(_ => {});
|
await clickCTA(page).catch(() => {});
|
||||||
code = await Promise.any([
|
code = await Promise.any([
|
||||||
page.inputValue('input[type="text"]'),
|
page.inputValue('input[type="text"]'),
|
||||||
page.textContent('[data-a-target="ClaimStateClaimCodeContent"]').then(s => s.replace('Your code: ', '')),
|
page.textContent('[data-a-target="ClaimStateClaimCodeContent"]').then(s => s.replace('Your code: ', '')),
|
||||||
]);
|
]);
|
||||||
} catch (_) {
|
} catch {
|
||||||
console.error(' Could not find claim code on page (timeout). Please check manually.');
|
console.error(' Could not find claim code on page (timeout). Please check manually.');
|
||||||
db.data[user][title].status = 'claimed (code not found)';
|
db.data[user][title].status = 'claimed (code not found)';
|
||||||
notify_game.status = 'claimed (code not found)';
|
notify_game.status = 'claimed (code not found)';
|
||||||
await page.screenshot({ path: screenshot('external', `${filenamify(title)}_nocode.png`), fullPage: true }).catch(_ => {});
|
await page.screenshot({ path: screenshot('external', `${filenamify(title)}_nocode.png`), fullPage: true }).catch(() => {});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
console.log(' Code to redeem game:', chalk.blue(code));
|
console.log(' Code to redeem game:', chalk.blue(code));
|
||||||
|
|
@ -563,7 +567,7 @@ try {
|
||||||
if (j?.events?.cart.length && j.events.cart[0]?.data?.reason == 'UserAlreadyOwnsContent') {
|
if (j?.events?.cart.length && j.events.cart[0]?.data?.reason == 'UserAlreadyOwnsContent') {
|
||||||
redeem_action = 'already redeemed';
|
redeem_action = 'already redeemed';
|
||||||
console.error(' error: UserAlreadyOwnsContent');
|
console.error(' error: UserAlreadyOwnsContent');
|
||||||
} else if (true) { // TODO what's returned on success?
|
} else { // TODO what's returned on success?
|
||||||
redeem_action = 'redeemed';
|
redeem_action = 'redeemed';
|
||||||
db.data[user][title].status = 'claimed and redeemed?';
|
db.data[user][title].status = 'claimed and redeemed?';
|
||||||
console.log(' Redeemed successfully? Please report if not in https://github.com/vogler/free-games-claimer/issues/5');
|
console.log(' Redeemed successfully? Please report if not in https://github.com/vogler/free-games-claimer/issues/5');
|
||||||
|
|
@ -610,7 +614,7 @@ try {
|
||||||
// await page.pause();
|
// await page.pause();
|
||||||
}
|
}
|
||||||
await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' });
|
await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' });
|
||||||
page.click('button[data-type="Game"]').catch(_ => {});
|
page.click('button[data-type="Game"]').catch(() => {});
|
||||||
|
|
||||||
if (notify_games.length && games) { // make screenshot of all games if something was claimed and list exists
|
if (notify_games.length && games) { // make screenshot of all games if something was claimed and list exists
|
||||||
const p = screenshot(`${filenamify(datetime())}.png`);
|
const p = screenshot(`${filenamify(datetime())}.png`);
|
||||||
|
|
@ -628,7 +632,7 @@ try {
|
||||||
await loot.waitFor();
|
await loot.waitFor();
|
||||||
|
|
||||||
process.stdout.write('Loading all DLCs on page...');
|
process.stdout.write('Loading all DLCs on page...');
|
||||||
await scrollUntilStable(() => loot.locator('[data-a-target="item-card"]').count())
|
await scrollUntilStable(() => loot.locator('[data-a-target="item-card"]').count());
|
||||||
|
|
||||||
console.log('\nNumber of already claimed DLC:', await loot.locator('p:has-text("Collected")').count());
|
console.log('\nNumber of already claimed DLC:', await loot.locator('p:has-text("Collected")').count());
|
||||||
|
|
||||||
|
|
@ -657,7 +661,7 @@ try {
|
||||||
// most games have a button 'Get in-game content'
|
// 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?
|
// 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")'))]);
|
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(_ => { });
|
page.click('button:has-text("Continue")').catch(() => { });
|
||||||
const linkAccountButton = page.locator('[data-a-target="LinkAccountButton"]');
|
const linkAccountButton = page.locator('[data-a-target="LinkAccountButton"]');
|
||||||
let unlinked_store;
|
let unlinked_store;
|
||||||
if (await linkAccountButton.count()) {
|
if (await linkAccountButton.count()) {
|
||||||
|
|
@ -674,7 +678,7 @@ try {
|
||||||
dlc_unlinked[unlinked_store] ??= [];
|
dlc_unlinked[unlinked_store] ??= [];
|
||||||
dlc_unlinked[unlinked_store].push(title);
|
dlc_unlinked[unlinked_store].push(title);
|
||||||
} else {
|
} else {
|
||||||
const code = await page.inputValue('input[type="text"]').catch(_ => undefined);
|
const code = await page.inputValue('input[type="text"]').catch(() => undefined);
|
||||||
console.log(' Code to redeem game:', chalk.blue(code));
|
console.log(' Code to redeem game:', chalk.blue(code));
|
||||||
db.data[user][title].code = code;
|
db.data[user][title].code = code;
|
||||||
db.data[user][title].status = 'claimed';
|
db.data[user][title].status = 'claimed';
|
||||||
|
|
|
||||||
|
|
@ -96,21 +96,21 @@ const timeoutPlugin = timeout => enquirer => { // cancel prompt after timeout ms
|
||||||
prompt.hint = () => 'timeout';
|
prompt.hint = () => 'timeout';
|
||||||
prompt.cancel();
|
prompt.cancel();
|
||||||
}, timeout);
|
}, timeout);
|
||||||
prompt.on('submit', _ => clearTimeout(t));
|
prompt.on('submit', () => clearTimeout(t));
|
||||||
prompt.on('cancel', _ => clearTimeout(t));
|
prompt.on('cancel', () => clearTimeout(t));
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
enquirer.use(timeoutPlugin(cfg.login_timeout)); // TODO may not want to have this timeout for all prompts; better extend Prompt and add a timeout prompt option
|
enquirer.use(timeoutPlugin(cfg.login_timeout)); // TODO may not want to have this timeout for all prompts; better extend Prompt and add a timeout prompt option
|
||||||
// single prompt that just returns the non-empty value instead of an object
|
// single prompt that just returns the non-empty value instead of an object
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
export const prompt = o => enquirer.prompt({ name: 'name', type: 'input', message: 'Enter value', ...o }).then(r => r.name).catch(_ => {});
|
export const prompt = o => enquirer.prompt({ name: 'name', type: 'input', message: 'Enter value', ...o }).then(r => r.name).catch(() => {});
|
||||||
export const confirm = o => prompt({ type: 'confirm', message: 'Continue?', ...o });
|
export const confirm = o => prompt({ type: 'confirm', message: 'Continue?', ...o });
|
||||||
|
|
||||||
// notifications via apprise CLI
|
// notifications via apprise CLI
|
||||||
import { execFile } from 'child_process';
|
import { execFile } from 'child_process';
|
||||||
import { cfg } from './config.js';
|
import { cfg } from './config.js';
|
||||||
|
|
||||||
export const notify = html => new Promise((resolve, reject) => {
|
export const notify = html => new Promise(resolve => {
|
||||||
if (!cfg.notify) {
|
if (!cfg.notify) {
|
||||||
if (cfg.debug) console.debug('notify: NOTIFY is not set!');
|
if (cfg.debug) console.debug('notify: NOTIFY is not set!');
|
||||||
return resolve();
|
return resolve();
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,8 @@ import { FingerprintInjector } from 'fingerprint-injector';
|
||||||
import { FingerprintGenerator } from 'fingerprint-generator';
|
import { FingerprintGenerator } from 'fingerprint-generator';
|
||||||
|
|
||||||
const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({
|
const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({
|
||||||
devices: ["desktop"],
|
devices: ['desktop'],
|
||||||
operatingSystems: ["windows"],
|
operatingSystems: ['windows'],
|
||||||
});
|
});
|
||||||
|
|
||||||
const context = await firefox.launchPersistentContext(cfg.dir.browser, {
|
const context = await firefox.launchPersistentContext(cfg.dir.browser, {
|
||||||
|
|
|
||||||
|
|
@ -12,10 +12,10 @@ function onRawSIGINT(fn) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
console.log(1)
|
console.log(1);
|
||||||
onRawSIGINT(() => {
|
onRawSIGINT(() => {
|
||||||
console.log('raw'); process.exit(1);
|
console.log('raw'); process.exit(1);
|
||||||
});
|
});
|
||||||
console.log(2)
|
console.log(2);
|
||||||
|
|
||||||
// onRawSIGINT workaround for enquirer keeps the process from exiting here...
|
// onRawSIGINT workaround for enquirer keeps the process from exiting here...
|
||||||
|
|
|
||||||
|
|
@ -7,19 +7,19 @@ import { prompt, handleSIGINT } from '../src/util.js';
|
||||||
// });
|
// });
|
||||||
handleSIGINT();
|
handleSIGINT();
|
||||||
|
|
||||||
function onRawSIGINT(fn) {
|
// function onRawSIGINT(fn) {
|
||||||
const { stdin, stdout } = process;
|
// const { stdin, stdout } = process;
|
||||||
stdin.setRawMode(true);
|
// stdin.setRawMode(true);
|
||||||
stdin.resume();
|
// stdin.resume();
|
||||||
stdin.on('data', data => {
|
// stdin.on('data', data => {
|
||||||
const key = data.toString('utf-8');
|
// const key = data.toString('utf-8');
|
||||||
if (key === '\u0003') { // ctrl + c
|
// if (key === '\u0003') { // ctrl + c
|
||||||
fn();
|
// fn();
|
||||||
} else {
|
// } else {
|
||||||
stdout.write(key);
|
// stdout.write(key);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
// onRawSIGINT(() => {
|
// onRawSIGINT(() => {
|
||||||
// console.log('raw'); process.exit(1);
|
// console.log('raw'); process.exit(1);
|
||||||
// });
|
// });
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue