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 # 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 # echo $DISPLAY
# ls -l /tmp/.X11-unix/ # ls -l /tmp/.X11-unix/
rm -f /tmp/.X1-lock rm -f /tmp/.X11-lock
# Ensure X11 socket dir exists with sane ownership/permissions. # Ensure X11 socket dir exists with sane ownership/permissions.
mkdir -p /tmp/.X11-unix mkdir -p /tmp/.X11-unix

View file

@ -164,67 +164,12 @@ const ensureLoggedIn = async (page, context) => {
} }
}; };
const isChallenge = async () => { // Use imported isCloudflareChallenge and solveCloudflare from src/cloudflare.js
const cfFrame = page.locator('iframe[title*="Cloudflare"], iframe[src*="challenges"]'); const isChallenge = async () => await isCloudflareChallenge(page);
const cfText = page.locator('text=Verify you are human');
return await cfFrame.count() > 0 || await cfText.count() > 0;
};
const solveCloudflareChallenge = async () => { const solveCloudflareChallenge = async () => {
try { const solution = await solveCloudflare(page, URL_CLAIM);
console.log('🔍 Detecting Cloudflare challenge...'); return solution !== null;
// 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;
}
}; };
let loginAttempts = 0; let loginAttempts = 0;

View file

@ -76,27 +76,6 @@ if (cfg.debug_network) {
const notify_games = []; const notify_games = [];
let user; 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 // Generate login redirect URL
const generateLoginRedirect = redirectUrl => { const generateLoginRedirect = redirectUrl => {
const loginRedirectUrl = new URL(ID_LOGIN_ENDPOINT); const loginRedirectUrl = new URL(ID_LOGIN_ENDPOINT);
@ -115,53 +94,6 @@ const generateCheckoutUrl = offers => {
return generateLoginRedirect(checkoutUrl); 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) // Get free games from promotions API (weekly free games)
const getFreeGamesFromPromotions = async () => { const getFreeGamesFromPromotions = async () => {
const response = await page.evaluate(async () => { const response = await page.evaluate(async () => {
@ -237,25 +169,6 @@ const loginWithDeviceAuth = async () => {
return false; 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 // Save device auth
const saveDeviceAuth = async (accessToken, refreshToken, expiresAt) => { const saveDeviceAuth = async (accessToken, refreshToken, expiresAt) => {
const deviceAuth = { const deviceAuth = {
@ -292,8 +205,8 @@ try {
if (cfg.time) console.timeEnd('startup'); if (cfg.time) console.timeEnd('startup');
if (cfg.time) console.time('login'); if (cfg.time) console.time('login');
// Try device auth first (unused - kept for reference) // Try device auth first
const deviceAuthLoginSuccess = await loginWithDeviceAuth(); await loginWithDeviceAuth();
// If device auth failed, try regular login // If device auth failed, try regular login
while (await page.locator('egs-navigation').getAttribute('isloggedin') != 'true') { while (await page.locator('egs-navigation').getAttribute('isloggedin') != 'true') {

145
package-lock.json generated
View file

@ -18,11 +18,14 @@
"lowdb": "^7.0.1", "lowdb": "^7.0.1",
"otplib": "^12.0.1", "otplib": "^12.0.1",
"playwright-firefox": "^1.52.0", "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": { "devDependencies": {
"@eslint/js": "^9.26.0",
"@stylistic/eslint-plugin-js": "^4.2.0", "@stylistic/eslint-plugin-js": "^4.2.0",
"eslint": "^9.26.0", "eslint": "^9.26.0",
"globals": "^15.14.0",
"typescript": "^5.9.3" "typescript": "^5.9.3"
}, },
"engines": { "engines": {
@ -124,6 +127,19 @@
"url": "https://opencollective.com/eslint" "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": { "node_modules/@eslint/js": {
"version": "9.26.0", "version": "9.26.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.26.0.tgz",
@ -1583,9 +1599,9 @@
} }
}, },
"node_modules/globals": { "node_modules/globals": {
"version": "14.0.0", "version": "15.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
@ -2362,11 +2378,22 @@
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT" "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": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
@ -2494,6 +2521,12 @@
"url": "https://github.com/sponsors/ljharb" "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": { "node_modules/range-parser": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -2520,6 +2553,12 @@
"node": ">= 0.8" "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": { "node_modules/resolve-from": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@ -2844,6 +2883,30 @@
"node": ">=0.6" "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": { "node_modules/tslib": {
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -2950,6 +3013,16 @@
"punycode": "^2.1.0" "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": { "node_modules/vali-date": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",
@ -3085,6 +3158,14 @@
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"minimatch": "^3.1.2", "minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1" "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": { "@eslint/js": {
@ -4050,9 +4131,9 @@
} }
}, },
"globals": { "globals": {
"version": "14.0.0", "version": "15.15.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-15.15.0.tgz",
"integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "integrity": "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==",
"dev": true "dev": true
}, },
"gopd": { "gopd": {
@ -4570,11 +4651,18 @@
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" "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": { "punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
"dev": true
}, },
"puppeteer-extra-plugin": { "puppeteer-extra-plugin": {
"version": "3.2.3", "version": "3.2.3",
@ -4627,6 +4715,11 @@
"side-channel": "^1.1.0" "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": { "range-parser": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -4645,6 +4738,11 @@
"unpipe": "1.0.0" "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": { "resolve-from": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "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==", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"dev": true "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": { "tslib": {
"version": "2.8.1", "version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
@ -4913,6 +5029,15 @@
"punycode": "^2.1.0" "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": { "vali-date": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz", "resolved": "https://registry.npmjs.org/vali-date/-/vali-date-1.0.0.tgz",

View file

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

View file

@ -1,4 +1,3 @@
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
import { cfg } from './config.js'; import { cfg } from './config.js';
const FLARESOLVERR_URL = process.env.FLARESOLVERR_URL || 'http://localhost:8191/v1'; 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) => { export const solveCloudflare = async (page, url) => {
try { try {
console.log('🔍 Detecting Cloudflare challenge...'); console.log('🔍 Detecting Cloudflare challenge...');
// Check if FlareSolverr is available // Check if FlareSolverr is available
if (!await checkFlareSolverr()) { const flaresolverrUrl = cfg.flaresolverr_url || 'http://localhost:8191/v1';
console.warn('⚠️ FlareSolverr not available at', FLARESOLVERR_URL); 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; return null;
} }
// Send request to FlareSolverr // Send request to FlareSolverr
const response = await fetch(`${FLARESOLVERR_URL}/request`, { const response = await fetch(`${flaresolverrUrl}/request`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
@ -51,14 +58,14 @@ export const solveCloudflare = async (page, url) => {
}); });
const data = await response.json(); const data = await response.json();
if (data.status !== 'ok') { if (data.status !== 'ok') {
console.warn('FlareSolverr failed:', data.message); console.warn('FlareSolverr failed:', data.message);
return null; return null;
} }
const solution = data.solution; const solution = data.solution;
// Apply cookies to the browser context // Apply cookies to the browser context
const cookies = solution.cookies.map(cookie => ({ const cookies = solution.cookies.map(cookie => ({
name: cookie.name, name: cookie.name,
@ -68,13 +75,13 @@ export const solveCloudflare = async (page, url) => {
secure: cookie.secure, secure: cookie.secure,
httpOnly: cookie.httpOnly, httpOnly: cookie.httpOnly,
})); }));
// Get the browser context from the page // Get the browser context from the page
const context = page.context(); const context = page.context();
await context.addCookies(cookies); await context.addCookies(cookies);
console.log('✅ Cloudflare challenge solved by FlareSolverr'); console.log('✅ Cloudflare challenge solved by FlareSolverr');
return { return {
cookies, cookies,
userAgent: solution.userAgent, userAgent: solution.userAgent,
@ -98,19 +105,19 @@ export const isCloudflareChallenge = async page => {
if (await cfFrame.count() > 0) { if (await cfFrame.count() > 0) {
return true; return true;
} }
// Check for Cloudflare text // Check for Cloudflare text
const cfText = page.locator('text=Verify you are human, text=Checking your browser'); const cfText = page.locator('text=Verify you are human, text=Checking your browser');
if (await cfText.count() > 0) { if (await cfText.count() > 0) {
return true; return true;
} }
// Check for specific Cloudflare URLs // Check for specific Cloudflare URLs
const url = page.url(); const url = page.url();
if (url.includes('cloudflare') || url.includes('challenges')) { if (url.includes('cloudflare') || url.includes('challenges')) {
return true; return true;
} }
return false; return false;
} catch { } catch {
return false; return false;
@ -125,13 +132,13 @@ export const isCloudflareChallenge = async page => {
*/ */
export const waitForCloudflareSolved = async (page, timeout = 60000) => { export const waitForCloudflareSolved = async (page, timeout = 60000) => {
const startTime = Date.now(); const startTime = Date.now();
while (Date.now() - startTime < timeout) { while (Date.now() - startTime < timeout) {
if (!await isCloudflareChallenge(page)) { if (!await isCloudflareChallenge(page)) {
return true; return true;
} }
await new Promise(resolve => setTimeout(resolve, 1000)); await new Promise(resolve => setTimeout(resolve, 1000));
} }
return false; return false;
}; };