fix: Multiple bug fixes and code cleanup
All checks were successful
build-and-push / lint (push) Successful in 8s
build-and-push / sonar (push) Successful in 21s
build-and-push / docker (push) Successful in 25s

- package.json: Add missing @eslint/js and globals devDependencies
- docker-entrypoint.sh: Fix X11 lock file name (.X1-lock → .X11-lock)
- epic-claimer-new.js: Use imported solveCloudflare/isCloudflareChallenge instead of duplicate implementations
- src/cloudflare.js: Fix solveCloudflare to use cfg.flaresolverr_url, remove unused imports
- epic-games.js: Remove unused code (getFreeGamesFromGraphQL, exchangeTokenForCookies, FREE_GAMES_QUERY, deviceAuthLoginSuccess variable)
- Run eslint --fix to clean up trailing spaces
This commit is contained in:
root 2026-03-08 13:58:57 +00:00
parent b14530537a
commit 48c861b3de
6 changed files with 166 additions and 174 deletions

View file

@ -59,7 +59,7 @@ export BROWSER_DIR
# Remove X server display lock, fix for `docker compose up` which reuses container which made it fail after initial run, https://github.com/vogler/free-games-claimer/issues/31
# echo $DISPLAY
# ls -l /tmp/.X11-unix/
rm -f /tmp/.X1-lock
rm -f /tmp/.X11-lock
# Ensure X11 socket dir exists with sane ownership/permissions.
mkdir -p /tmp/.X11-unix

View file

@ -164,67 +164,12 @@ const ensureLoggedIn = async (page, context) => {
}
};
const isChallenge = async () => {
const cfFrame = page.locator('iframe[title*="Cloudflare"], iframe[src*="challenges"]');
const cfText = page.locator('text=Verify you are human');
return await cfFrame.count() > 0 || await cfText.count() > 0;
};
// Use imported isCloudflareChallenge and solveCloudflare from src/cloudflare.js
const isChallenge = async () => await isCloudflareChallenge(page);
const solveCloudflareChallenge = async () => {
try {
console.log('🔍 Detecting Cloudflare challenge...');
// Check if FlareSolverr is available
const flaresolverrUrl = cfg.flaresolverr_url || 'http://localhost:8191/v1';
const healthResponse = await fetch(`${flaresolverrUrl}/health`, {
method: 'GET',
headers: { 'Content-Type': 'application/json' },
});
if (!healthResponse.ok) {
console.warn('⚠️ FlareSolverr not available at', flaresolverrUrl);
return false;
}
// Send request to FlareSolverr
const response = await fetch(`${flaresolverrUrl}/request`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
cmd: 'request.get',
url: URL_CLAIM,
maxTimeout: 60000,
session: 'epic-games',
}),
});
const data = await response.json();
if (data.status !== 'ok') {
console.warn('FlareSolverr failed:', data.message);
return false;
}
const solution = data.solution;
// Apply cookies to the browser context
const cookies = solution.cookies.map(cookie => ({
name: cookie.name,
value: cookie.value,
domain: cookie.domain,
path: cookie.path || '/',
secure: cookie.secure,
httpOnly: cookie.httpOnly,
}));
await context.addCookies(cookies);
console.log('✅ Cloudflare challenge solved by FlareSolverr');
return true;
} catch (error) {
console.error('FlareSolverr error:', error.message);
return false;
}
const solution = await solveCloudflare(page, URL_CLAIM);
return solution !== null;
};
let loginAttempts = 0;

View file

@ -76,27 +76,6 @@ if (cfg.debug_network) {
const notify_games = [];
let user;
// GraphQL query for free games
const FREE_GAMES_QUERY = {
operationName: 'searchStoreQuery',
variables: {
allowCountries: 'US',
category: 'games/edition/base|software/edition/base|editors|bundles/games',
count: 1000,
country: 'US',
sortBy: 'relevancy',
sortDir: 'DESC',
start: 0,
withPrice: true,
},
extensions: {
persistedQuery: {
version: 1,
sha256Hash: '7d58e12d9dd8cb14c84a3ff18d360bf9f0caa96bf218f2c5fda68ba88d68a437',
},
},
};
// Generate login redirect URL
const generateLoginRedirect = redirectUrl => {
const loginRedirectUrl = new URL(ID_LOGIN_ENDPOINT);
@ -115,53 +94,6 @@ const generateCheckoutUrl = offers => {
return generateLoginRedirect(checkoutUrl);
};
// Get free games from GraphQL API (unused - kept for reference)
const getFreeGamesFromGraphQL = async () => {
const items = [];
let start = 0;
const pageLimit = 1000;
do {
const response = await page.evaluate(async (query, startOffset) => {
const variables = { ...query.variables, start: startOffset };
const resp = await fetch(GRAPHQL_ENDPOINT, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
operationName: query.operationName,
variables: JSON.stringify(variables),
extensions: JSON.stringify(query.extensions),
}),
});
return await resp.json();
}, [FREE_GAMES_QUERY, start]);
const elements = response.data?.Catalog?.searchStore?.elements;
if (!elements) break;
items.push(...elements);
start += pageLimit;
} while (items.length < pageLimit);
// Filter free games
const freeGames = items.filter(game => game.price?.totalPrice?.discountPrice === 0);
// Deduplicate by productSlug
const uniqueGames = new Map();
for (const game of freeGames) {
if (!uniqueGames.has(game.productSlug)) {
uniqueGames.set(game.productSlug, game);
}
}
return Array.from(uniqueGames.values()).map(game => ({
offerId: game.id,
offerNamespace: game.namespace,
productName: game.title,
productSlug: game.productSlug || game.urlSlug,
}));
};
// Get free games from promotions API (weekly free games)
const getFreeGamesFromPromotions = async () => {
const response = await page.evaluate(async () => {
@ -237,25 +169,6 @@ const loginWithDeviceAuth = async () => {
return false;
};
// Exchange token for cookies (alternative method - unused)
const exchangeTokenForCookies = async accessToken => {
try {
const cookies = await page.evaluate(async token => {
const resp = await fetch('https://store.epicgames.com/', {
headers: {
Authorization: `Bearer ${token}`,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36',
},
});
return await resp.headers.get('set-cookie');
}, accessToken);
return cookies;
} catch {
return null;
}
};
// Save device auth
const saveDeviceAuth = async (accessToken, refreshToken, expiresAt) => {
const deviceAuth = {
@ -292,8 +205,8 @@ try {
if (cfg.time) console.timeEnd('startup');
if (cfg.time) console.time('login');
// Try device auth first (unused - kept for reference)
const deviceAuthLoginSuccess = await loginWithDeviceAuth();
// Try device auth first
await loginWithDeviceAuth();
// If device auth failed, try regular login
while (await page.locator('egs-navigation').getAttribute('isloggedin') != 'true') {

145
package-lock.json generated
View file

@ -18,11 +18,14 @@
"lowdb": "^7.0.1",
"otplib": "^12.0.1",
"playwright-firefox": "^1.52.0",
"puppeteer-extra-plugin-stealth": "^2.11.2"
"puppeteer-extra-plugin-stealth": "^2.11.2",
"tough-cookie": "^4.1.4"
},
"devDependencies": {
"@eslint/js": "^9.26.0",
"@stylistic/eslint-plugin-js": "^4.2.0",
"eslint": "^9.26.0",
"globals": "^15.14.0",
"typescript": "^5.9.3"
},
"engines": {
@ -124,6 +127,19 @@
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint/eslintrc/node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@eslint/js": {
"version": "9.26.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz",
@ -1583,9 +1599,9 @@
}
},
"node_modules/globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"version": "15.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
"integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
"dev": true,
"license": "MIT",
"engines": {
@ -2362,11 +2378,22 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"funding": {
"url": "https://github.com/sponsors/lupomontero"
}
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@ -2494,6 +2521,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
"license": "MIT"
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -2520,6 +2553,12 @@
"node": ">= 0.8"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"license": "MIT"
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@ -2844,6 +2883,30 @@
"node": ">=0.6"
}
},
"node_modules/tough-cookie": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
"license": "BSD-3-Clause",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tough-cookie/node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -2950,6 +3013,16 @@
"punycode": "^2.1.0"
}
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"license": "MIT",
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"node_modules/vali-date": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",
@ -3085,6 +3158,14 @@
"js-yaml": "^4.1.0",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"dependencies": {
"globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"dev": true
}
}
},
"@eslint/js": {
@ -4050,9 +4131,9 @@
}
},
"globals": {
"version": "14.0.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
"version": "15.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
"integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
"dev": true
},
"gopd": {
@ -4570,11 +4651,18 @@
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"psl": {
"version": "1.15.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
"integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
"requires": {
"punycode": "^2.3.1"
}
},
"punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
},
"puppeteer-extra-plugin": {
"version": "3.2.3",
@ -4627,6 +4715,11 @@
"side-channel": "^1.1.0"
}
},
"querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -4645,6 +4738,11 @@
"unpipe": "1.0.0"
}
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@ -4853,6 +4951,24 @@
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"dev": true
},
"tough-cookie": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"dependencies": {
"universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
}
}
},
"tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -4913,6 +5029,15 @@
"punycode": "^2.1.0"
}
},
"url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"vali-date": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",

View file

@ -33,6 +33,8 @@
"tough-cookie": "^4.1.4"
},
"devDependencies": {
"@eslint/js": "^9.26.0",
"globals": "^15.14.0",
"@stylistic/eslint-plugin-js": "^4.2.0",
"eslint": "^9.26.0",
"typescript": "^5.9.3"

View file

@ -1,4 +1,3 @@
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { cfg } from './config.js';
const FLARESOLVERR_URL = process.env.FLARESOLVERR_URL || 'http://localhost:8191/v1';
@ -31,13 +30,21 @@ export const solveCloudflare = async (page, url) => {
console.log('🔍 Detecting Cloudflare challenge...');
// Check if FlareSolverr is available
if (!await checkFlareSolverr()) {
console.warn('⚠️ FlareSolverr not available at', FLARESOLVERR_URL);
const flaresolverrUrl = cfg.flaresolverr_url || 'http://localhost:8191/v1';
const healthResponse = await fetch(`${flaresolverrUrl}/health`, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
},
});
if (!healthResponse.ok) {
console.warn('⚠️ FlareSolverr not available at', flaresolverrUrl);
return null;
}
// Send request to FlareSolverr
const response = await fetch(`${FLARESOLVERR_URL}/request`, {
const response = await fetch(`${flaresolverrUrl}/request`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',