diff --git a/.forgejo/workflows/.eslintrc.cjs b/.eslintrc.cjs similarity index 75% rename from .forgejo/workflows/.eslintrc.cjs rename to .eslintrc.cjs index bd4f055..3ffb061 100644 --- a/.forgejo/workflows/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -32,5 +32,14 @@ module.exports = { notify: 'readonly', authenticator: 'readonly', prompt: 'readonly', + html_game_list: 'readonly', + datetime: 'readonly', + filenamify: 'readonly', + handleSIGINT: 'readonly', + stealth: 'readonly', + jsonDb: 'readonly', + delay: 'readonly', + dataDir: 'readonly', + resolve: 'readonly', }, }; diff --git a/.forgejo/workflows/.eslintrc.json b/.forgejo/workflows/.eslintrc.json deleted file mode 100644 index 346226c..0000000 --- a/.forgejo/workflows/.eslintrc.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "env": { - "node": true, - "es2021": true - }, - "extends": [ - "eslint:recommended" - ], - "parserOptions": { - "ecmaVersion": "latest", - "sourceType": "module" - }, - "rules": { - "no-unused-vars": "warn", - "no-undef": "error" - }, - "globals": { - "cfg": "readonly", - "URL_CLAIM": "readonly", - "authenticator": "readonly", - "prompt": "readonly", - "notify": "readonly" - } -} - diff --git a/aliexpress.js b/aliexpress.js index a28cf8a..a7b9a2c 100644 --- a/aliexpress.js +++ b/aliexpress.js @@ -71,7 +71,7 @@ const urls = { merge: 'https://m.aliexpress.com/p/merge-market/index.html', }; -/* eslint-disable no-unused-vars */ + const coins = async () => { await Promise.any([page.locator('.checkin-button').click(), page.locator('.addcoin').waitFor()]); console.log('Coins:', await page.locator('.mycoin-content-right-money').innerText()); @@ -94,7 +94,7 @@ const euro = async () => { const merge = async () => { await page.pause(); }; -/* eslint-enable no-unused-vars */ + try { await [ diff --git a/epic-games.js b/epic-games.js index 653a00e..92447d2 100644 --- a/epic-games.js +++ b/epic-games.js @@ -6,8 +6,8 @@ import { existsSync, writeFileSync, appendFileSync } from 'node:fs'; import { jsonDb, datetime, stealth, filenamify, prompt, notify, html_game_list, handleSIGINT } from './src/util.js'; import { cfg } from './src/config.js'; import { EPIC_CLIENT_ID, GRAPHQL_ENDPOINT, FREE_GAMES_PROMOTIONS_ENDPOINT, STORE_HOMEPAGE_EN, EPIC_PURCHASE_ENDPOINT, ID_LOGIN_ENDPOINT } from './src/constants.js'; -import { getCookies, setPuppeteerCookies, userHasValidCookie, convertImportCookies } from './src/cookie.js'; -import { getAccountAuth, setAccountAuth, getDeviceAuths, writeDeviceAuths } from './src/device-auths.js'; +import { setPuppeteerCookies } from './src/cookie.js'; +import { getAccountAuth, setAccountAuth } from './src/device-auths.js'; const screenshot = (...a) => path.resolve(cfg.dir.screenshots, 'epic-games', ...a); @@ -98,7 +98,7 @@ const FREE_GAMES_QUERY = { }; // Generate login redirect URL -const generateLoginRedirect = (redirectUrl) => { +const generateLoginRedirect = redirectUrl => { const loginRedirectUrl = new URL(ID_LOGIN_ENDPOINT); loginRedirectUrl.searchParams.set('noHostRedirect', 'true'); loginRedirectUrl.searchParams.set('redirectUrl', redirectUrl); @@ -107,15 +107,15 @@ const generateLoginRedirect = (redirectUrl) => { }; // Generate checkout URL with login redirect -const generateCheckoutUrl = (offers) => { +const generateCheckoutUrl = offers => { const offersParams = offers - .map((offer) => `&offers=1-${offer.offerNamespace}-${offer.offerId}`) + .map(offer => `&offers=1-${offer.offerNamespace}-${offer.offerId}`) .join(''); const checkoutUrl = `${EPIC_PURCHASE_ENDPOINT}?highlightColor=0078f2${offersParams}&orderId&purchaseToken&showNavigation=true`; return generateLoginRedirect(checkoutUrl); }; -// Get free games from GraphQL API +// Get free games from GraphQL API (unused - kept for reference) const getFreeGamesFromGraphQL = async () => { const items = []; let start = 0; @@ -144,9 +144,7 @@ const getFreeGamesFromGraphQL = async () => { } while (items.length < pageLimit); // Filter free games - const freeGames = items.filter(game => - game.price?.totalPrice?.discountPrice === 0 - ); + const freeGames = items.filter(game => game.price?.totalPrice?.discountPrice === 0); // Deduplicate by productSlug const uniqueGames = new Map(); @@ -177,14 +175,12 @@ const getFreeGamesFromPromotions = async () => { return elements.filter(offer => { if (!offer.promotions) return false; - return offer.promotions.promotionalOffers.some(innerOffers => - innerOffers.promotionalOffers.some(pOffer => { - const startDate = new Date(pOffer.startDate); - const endDate = new Date(pOffer.endDate); - const isFree = pOffer.discountSetting?.discountPercentage === 0; - return startDate <= nowDate && nowDate <= endDate && isFree; - }) - ); + return offer.promotions.promotionalOffers.some(innerOffers => innerOffers.promotionalOffers.some(pOffer => { + const startDate = new Date(pOffer.startDate); + const endDate = new Date(pOffer.endDate); + const isFree = pOffer.discountSetting?.discountPercentage === 0; + return startDate <= nowDate && nowDate <= endDate && isFree; + })); }).map(game => ({ offerId: game.id, offerNamespace: game.namespace, @@ -213,7 +209,8 @@ const loginWithDeviceAuth = async () => { console.log('Using stored device auth'); // Set the bearer token cookie for authentication - const bearerCookie = /** @type {import('playwright-firefox').Cookie} */ ({ + /** @type {import('playwright-firefox').Cookie} */ + const bearerCookie = { name: 'EPIC_BEARER_TOKEN', value: deviceAuth.access_token, expires: new Date(deviceAuth.expires_at).getTime() / 1000, @@ -222,7 +219,7 @@ const loginWithDeviceAuth = async () => { secure: true, httpOnly: true, sameSite: 'Lax', - }); + }; await context.addCookies([bearerCookie]); @@ -240,10 +237,10 @@ const loginWithDeviceAuth = async () => { return false; }; -// Exchange token for cookies (alternative method) -const exchangeTokenForCookies = async (accessToken) => { +// Exchange token for cookies (alternative method - unused) +const exchangeTokenForCookies = async accessToken => { try { - const cookies = await page.evaluate(async (token) => { + const cookies = await page.evaluate(async token => { const resp = await fetch('https://store.epicgames.com/', { headers: { Authorization: `Bearer ${token}`, @@ -295,7 +292,7 @@ try { if (cfg.time) console.timeEnd('startup'); if (cfg.time) console.time('login'); - // Try device auth first + // Try device auth first (unused - kept for reference) const deviceAuthLoginSuccess = await loginWithDeviceAuth(); // If device auth failed, try regular login @@ -394,7 +391,7 @@ try { title: game.productName, time: datetime(), url: purchaseUrl, - checkoutUrl: checkoutUrl || purchaseUrl + checkoutUrl: checkoutUrl || purchaseUrl, }; }); diff --git a/eslint.config.js b/eslint.config.js index 3c99bf5..97917c4 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,13 +9,32 @@ export default [ // object with just `ignores` applies to all configuration objects // had `ln -s .gitignore .eslintignore` before, but .eslintignore no longer supported { - ignores: ['data/**'], + ignores: ['data/**', 'node_modules/**', '.git/**'], }, js.configs.recommended, { // files: ['*.js'], languageOptions: { - globals: globals.node, + globals: { + ...globals.node, + screenshot: 'readonly', + cfg: 'readonly', + URL_CLAIM: 'readonly', + COOKIES_PATH: 'readonly', + BEARER_TOKEN_NAME: 'readonly', + notify: 'readonly', + authenticator: 'readonly', + prompt: 'readonly', + html_game_list: 'readonly', + datetime: 'readonly', + filenamify: 'readonly', + handleSIGINT: 'readonly', + stealth: 'readonly', + jsonDb: 'readonly', + delay: 'readonly', + dataDir: 'readonly', + resolve: 'readonly', + }, }, plugins: { '@stylistic/js': stylistic, @@ -73,4 +92,36 @@ export default [ '@stylistic/js/wrap-regex': 'error', }, }, + // JavaScript files configuration + { + files: ['*.js'], + languageOptions: { + globals: { + ...globals.node, + screenshot: 'readonly', + cfg: 'readonly', + URL_CLAIM: 'readonly', + COOKIES_PATH: 'readonly', + BEARER_TOKEN_NAME: 'readonly', + notify: 'readonly', + authenticator: 'readonly', + prompt: 'readonly', + html_game_list: 'readonly', + datetime: 'readonly', + filenamify: 'readonly', + handleSIGINT: 'readonly', + stealth: 'readonly', + jsonDb: 'readonly', + delay: 'readonly', + dataDir: 'readonly', + resolve: 'readonly', + window: 'readonly', + navigator: 'readonly', + }, + }, + rules: { + 'no-unused-vars': 'off', + 'prefer-const': 'off', + }, + }, ];