From 48c861b3de5465b225e6a6f02e869d99f5baf1b7 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 8 Mar 2026 13:58:57 +0000 Subject: [PATCH] fix: Multiple bug fixes and code cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- docker-entrypoint.sh | 2 +- epic-claimer-new.js | 63 ++----------------- epic-games.js | 91 +-------------------------- package-lock.json | 145 ++++++++++++++++++++++++++++++++++++++++--- package.json | 2 + src/cloudflare.js | 37 ++++++----- 6 files changed, 166 insertions(+), 174 deletions(-) diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index c97e5ac..66c4c2e 100755 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -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 diff --git a/epic-claimer-new.js b/epic-claimer-new.js index 1dfd612..f1e3362 100644 --- a/epic-claimer-new.js +++ b/epic-claimer-new.js @@ -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; diff --git a/epic-games.js b/epic-games.js index 565de90..57fb585 100644 --- a/epic-games.js +++ b/epic-games.js @@ -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') { diff --git a/package-lock.json b/package-lock.json index 903bc52..19ca23d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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", diff --git a/package.json b/package.json index cdc7bfa..d75796d 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/src/cloudflare.js b/src/cloudflare.js index 6c3983c..2ee0837 100644 --- a/src/cloudflare.js +++ b/src/cloudflare.js @@ -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'; @@ -29,15 +28,23 @@ export const checkFlareSolverr = async () => { export const solveCloudflare = async (page, url) => { try { 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', @@ -51,14 +58,14 @@ export const solveCloudflare = async (page, url) => { }); const data = await response.json(); - + if (data.status !== 'ok') { console.warn('FlareSolverr failed:', data.message); return null; } const solution = data.solution; - + // Apply cookies to the browser context const cookies = solution.cookies.map(cookie => ({ name: cookie.name, @@ -68,13 +75,13 @@ export const solveCloudflare = async (page, url) => { secure: cookie.secure, httpOnly: cookie.httpOnly, })); - + // Get the browser context from the page const context = page.context(); await context.addCookies(cookies); - + console.log('✅ Cloudflare challenge solved by FlareSolverr'); - + return { cookies, userAgent: solution.userAgent, @@ -98,19 +105,19 @@ export const isCloudflareChallenge = async page => { if (await cfFrame.count() > 0) { return true; } - + // Check for Cloudflare text const cfText = page.locator('text=Verify you are human, text=Checking your browser'); if (await cfText.count() > 0) { return true; } - + // Check for specific Cloudflare URLs const url = page.url(); if (url.includes('cloudflare') || url.includes('challenges')) { return true; } - + return false; } catch { return false; @@ -125,13 +132,13 @@ export const isCloudflareChallenge = async page => { */ export const waitForCloudflareSolved = async (page, timeout = 60000) => { const startTime = Date.now(); - + while (Date.now() - startTime < timeout) { if (!await isCloudflareChallenge(page)) { return true; } await new Promise(resolve => setTimeout(resolve, 1000)); } - + return false; };