fixed claim procedure with age claims functioning and relocated config.js with updated variables

This commit is contained in:
drklien 2025-07-17 12:38:21 +10:00 committed by GitHub
parent 617229f659
commit 450191dffc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 239 additions and 81 deletions

View file

@ -51,7 +51,9 @@ export const cfg = {
steam_username: process.env.STEAM_USERNAME, steam_username: process.env.STEAM_USERNAME,
steam_password: process.env.STEAM_PASSWORD || process.env.PASSWORD, steam_password: process.env.STEAM_PASSWORD || process.env.PASSWORD,
steam_json: process.env.STEAM_JSON == '1', steam_json: process.env.STEAM_JSON == '0',
steam_json_url: process.env.STEAM_JSON_URL || 'https://raw.githubusercontent.com/vogler/free-games-claimer/main/steam-games.json', steam_json_url: process.env.STEAM_JSON_URL || 'https://raw.githubusercontent.com/vogler/free-games-claimer/main/steam-games.json',
steam_gamerpower: process.env.STEAM_GAMERPOWER == '1', steam_gamerpower: true,
steam_gamerpower_url: process.env.STEAM_GAMERPOWER_URL || 'https://www.gamerpower.com/api/giveaways?platform=steam&type=game',
}; };

View file

@ -1,103 +1,82 @@
import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra import { chromium } from 'patchright';
import { resolve, jsonDb, datetime, prompt, stealth, notify, html_game_list, handleSIGINT } from './util.js'; import dotenv from 'dotenv';
import path from 'path'; import path from 'path';
import { existsSync, writeFileSync } from 'fs'; import { existsSync, writeFileSync } from 'fs';
import { cfg } from './config.js'; import { resolve, jsonDb, datetime, prompt, notify, html_game_list, handleSIGINT } from './src/util.js';
import { cfg } from './src/config.js';
const notify_games = [];
const screenshot = (...a) => resolve(cfg.dir.screenshots, 'steam', ...a); const screenshot = (...a) => resolve(cfg.dir.screenshots, 'steam', ...a);
const URL_CLAIM = 'https://store.steampowered.com/?l=english'; const URL_CLAIM = 'https://store.steampowered.com/?l=english';
const URL_LOGIN = 'https://store.steampowered.com/login/'; const URL_LOGIN = 'https://store.steampowered.com/login/';
console.log(datetime(), 'started checking steam'); console.log(datetime(), 'started checking steam');
const db = await jsonDb('steam.json', {}); const db = await jsonDb('steam.json', {});
handleSIGINT(); handleSIGINT();
// https://playwright.dev/docs/auth#multi-factor-authentication const context = await chromium.launchPersistentContext(cfg.dir.browser, {
const context = await firefox.launchPersistentContext(cfg.dir.browser, { headless: cfg.headless,
// chrome will not work in linux arm64, only chromium locale: "en-US",
// channel: 'chrome', // https://playwright.dev/docs/browsers#google-chrome--microsoft-edge
headless: false,
viewport: { width: cfg.width, height: cfg.height },
userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.83 Safari/537.36',
locale: "en-US", // ignore OS locale to be sure to have english text for locators
recordVideo: cfg.record ? { dir: 'data/record/', size: { width: cfg.width, height: cfg.height } } : undefined, recordVideo: cfg.record ? { dir: 'data/record/', size: { width: cfg.width, height: cfg.height } } : undefined,
recordHar: cfg.record ? { path: `data/record/eg-${datetime()}.har` } : undefined, recordHar: cfg.record ? { path: `data/record/eg-${datetime()}.har` } : undefined,
args: [ // https://peter.sh/experiments/chromium-command-line-switches handleSIGINT: false,
// don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.' args: [
// '--restore-last-session', // does not apply for crash/killed
'--hide-crash-restore-bubble', '--hide-crash-restore-bubble',
// `--disable-extensions-except=${ext}`,
// `--load-extension=${ext}`,
], ],
// ignoreDefaultArgs: ['--enable-automation'], // remove default arg that shows the info bar with 'Chrome is being controlled by automated test software.'. Since Chromeium 106 this leads to show another info bar with 'You are using an unsupported command-line flag: --no-sandbox. Stability and security will suffer.'.
}); });
await stealth(context);
if (!cfg.debug) context.setDefaultTimeout(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
// console.debug('userAgent:', await page.evaluate(() => navigator.userAgent));
const notify_games = []; async function doLogin(page) {
let user;
async function doLogin() {
await page.goto(URL_LOGIN, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever
if (cfg.steam_username && cfg.steam_password) {
console.info('Using username and password from environment.');
}
else {
console.info('Press ESC to skip the prompts if you want to login in the browser (not possible in headless mode).');
}
const username = cfg.steam_username || await prompt({ message: 'Enter username' }); const username = cfg.steam_username || await prompt({ message: 'Enter username' });
const password = username && (cfg.steam_password || await prompt({ type: 'password', message: 'Enter password' })); const password = username && (cfg.steam_password || await prompt({ type: 'password', message: 'Enter password' }));
if (username && password) { if (username && password) {
await page.type('input[type=text]:visible', username); await page.type('input[type=text]:visible', username);
await page.type('input[type=password]:visible', password); await page.type('input[type=password]:visible', password);
await page.waitForTimeout(2000); await Promise.all([page.click('button[type=submit]'), page.waitForNavigation()]);
await page.click('button[type=submit]');
await page.waitForTimeout(2000);
}
const auth = await page.getByText('You have a mobile authenticator protecting this account.').first();
let isFirstCheck = true;
while (await auth.isVisible()) {
if (isFirstCheck) {
console.log("Steam requires confirmation from authenticator");
notify(`Steam requires confirmation from authenticator`);
isFirstCheck = false;
}
await page.waitForTimeout(2000);
} }
} }
async function claim() { async function claim() {
await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' });
console.log('Navigated to Steam store page');
await context.addCookies([{ name: 'cookieSettings', value: '%7B%22version%22%3A1%2C%22preference_state%22%3A2%2C%22content_customization%22%3Anull%2C%22valve_analytics%22%3Anull%2C%22third_party_analytics%22%3Anull%2C%22third_party_content%22%3Anull%2C%22utm_enabled%22%3Atrue%7D', domain: 'store.steampowered.com', path: '/' }]); // Decline all cookies to get rid of banner to save space on screen. await context.addCookies([{ name: 'cookieSettings', value: '%7B%22version%22%3A1%2C%22preference_state%22%3A2%2C%22content_customization%22%3Anull%2C%22valve_analytics%22%3Anull%2C%22third_party_analytics%22%3Anull%2C%22third_party_content%22%3Anull%2C%22utm_enabled%22%3Atrue%7D', domain: 'store.steampowered.com', path: '/' }]); // Decline all cookies to get rid of banner to save space on screen.
console.log('Cookies added');
const signIn = page.locator('a:has-text("Sign In")').first(); const loginText = await page.textContent('a.global_action_link');
while (await signIn.isVisible()) { const user = await page.locator("#account_pulldown").first().innerText();
const result = await Promise.race([loginText, user]);
while (await result.includes('Log In')) {
console.error('Not signed in to steam.'); console.error('Not signed in to steam.');
await doLogin(); await doLogin();
} loginText = await page.textContent('a.global_action_link');
user = await page.locator("#account_pulldown").first().innerText(); user = await page.locator("#account_pulldown").first().innerText();
console.error('You are logged in as ' + user); result = await Promise.race([loginText, user]);
db.data[user] ||= {}; }
console.log('You are logged in as ' + user);
db.data[user] ||= {};
if (cfg.steam_json) { if (cfg.steam_json) {
await claimJson(); console.log('Starting to claim from Steam JSON');
await claimJson(user);
console.log('Finished claiming from Steam JSON');
} }
if (cfg.steam_gamerpower) { if (cfg.steam_gamerpower) {
await claimGamerpower(); console.log('Starting to claim from GamerPower');
await claimGamerpower(user);
console.log('Finished claiming from GamerPower');
} }
// Write db.data[user] to a file
writeFileSync(`data/${user}.json`, JSON.stringify(db.data[user], null, 2));
console.log('Data written to file for user:', user);
} }
async function claimJson() { async function claimJson(user) {
console.log("Claiming JSON"); console.log("Claiming JSON");
const response = await page.goto(cfg.steam_json_url); const response = await page.goto(cfg.steam_json_url);
const items = await response.json(); const items = await response.json();
@ -111,49 +90,224 @@ async function claimJson() {
return; return;
} }
} }
await claimGame(item.url); await claimGame(item.url, user);
} }
} }
} }
async function claimGamerpower() { async function claimGamerpower(user) {
console.log("Claiming Gamerpower"); console.log("Claiming Gamerpower");
const response = await page.goto("https://www.gamerpower.com/api/giveaways?platform=steam&type=game"); try {
const response = await page.goto(cfg.steam_gamerpower_url);
if (!response.ok) {
throw new Error(`Failed to fetch GamerPower data: ${response.statusText}`);
}
const items = await response.json(); const items = await response.json();
for (const item of items) { for (const item of items) {
console.log(item.open_giveaway_url); console.log(item.open_giveaway_url);
try {
await page.goto(item.open_giveaway_url, { waitUntil: 'domcontentloaded' }); await page.goto(item.open_giveaway_url, { waitUntil: 'domcontentloaded' });
const url = page.url(); const url = page.url();
if (url.includes("https://store.steampowered.com/app")) { if (url.includes("https://store.steampowered.com/app")) {
if (!await isClaimedUrl(url)) { if (!await isClaimedUrl(url)) {
await claimGame(url); await claimGame(url, user);
}
} else if (url.includes("https://store.steampowered.com/agecheck/app")) {
if (!await isClaimedUrl(url)) {
await handleAgeGate(page, 21, 1, 1989);
await claimGame(url, user);
}
} else {
console.log("Game can be claimed outside of Steam! " + url);
}
} catch (error) {
console.error(`Failed to claim game from ${item.open_giveaway_url}:`, error.message);
} }
} }
else { } catch (error) {
console.log("Game can be claimed outside of steam! " + url); console.error(`Error in claimGamerpower:`, error.message);
}
} }
} }
async function claimGame(url) { // async function claimAgedRestrictedGame(url, user) {
// await page.goto(url, { waitUntil: 'domcontentloaded' });
// try {
// await page.waitForSelector('#ageDay', { timeout: 5000 });
// // Select a random day between 1 and 31
// const dayOptions = document.querySelectorAll('#ageDay option');
// const dayIndex = Math.floor(Math.random() * 20) + 1;
// await dayOptions[dayIndex].setAttribute('selected', 'true');
// // Select a random month between January and December
// const monthOptions = document.querySelectorAll('#ageMonth option');
// await page.selectOption('#ageMonth', { value: monthOptions[monthIndex] });
// // Select a year between 1900 and the current year
// const yearOptions = await page.$$eval('#ageYear option', options => options.map(option => option.value));
// const currentDate = new Date();
// const yearIndex = currentDate.getFullYear() - 25;
// await page.selectOption('#ageYear', { value: yearOptions[yearIndex] });
// await Promise.all([
// page.click('#view_product_page_btn'),
// page.waitForNavigation({ waitUntil: 'networkidle2' })
// ]);
// } catch (error) {
// console.error("Age gate not found or failed to handle");
// }
// try {
// await claimGame(url, user);
// } catch (error) {
// console.error("Failed to handle age gate or claim game:", error);
// }
// }
async function handleAgeGate(page, day, month, year, timeout = 30000) {
try {
// 1. Check if age_gate element is visible
console.log('Looking for age gate...');
const ageGate = page.locator('.age_gate');
// Try to wait a little on the element to appear
await page.waitForTimeout(1000);
const isVisible = await ageGate.isVisible().catch(() => false);
if (!isVisible) {
console.log('Age gate not found or not visible');
return false;
}
console.log('Age gate found, filling in dates...');
// 2. Set day
const daySelect = page.locator('#ageDay');
const isdaySelect = await daySelect.isVisible().catch(() => false);
if (!isdaySelect) {
console.log('Day select not found. Attempting to click product page button...')
} else {
await daySelect.waitFor({ timeout });
await daySelect.selectOption(day.toString());
console.log(`Day set to: ${day}`);
// 3. Set month (convert to English month name with capital first letter)
const monthNames = {
1: 'January', 2: 'February', 3: 'March', 4: 'April',
5: 'May', 6: 'June', 7: 'July', 8: 'August',
9: 'September', 10: 'October', 11: 'November', 12: 'December'
};
const monthNumber = parseInt(month);
const monthName = monthNames[monthNumber];
if (!monthName) {
throw new Error(`Invalid Month: ${month}. Must be between 1-12`);
}
const monthSelect = page.locator('#ageMonth');
await monthSelect.waitFor({ timeout });
await monthSelect.selectOption(monthName);
console.log(`Month set to: ${monthName}`);
// 4. Set year
const yearSelect = page.locator('#ageYear');
await yearSelect.waitFor({ timeout });
await yearSelect.selectOption(year.toString());
console.log(`Year set to: ${year}`);
}
// 5. Click on view_product_page_btn
console.log('Clicking on the view product button...');
const viewProductBtn = page.locator('#view_product_page_btn');
await viewProductBtn.waitFor({ timeout });
await viewProductBtn.click();
// 6. Wait until the page has loaded
console.log('Waiting for page to load...');
await page.waitForLoadState('networkidle', { timeout });
console.log('Age gate completed');
return true;
} catch (error) {
console.error('Error during age gate process:', error.message);
return false;
}
}
async function claimGame(url, user) {
await page.goto(url, { waitUntil: 'domcontentloaded' }); await page.goto(url, { waitUntil: 'domcontentloaded' });
const title = await page.locator('#appHubAppName').first().innerText(); const title = await page.locator('#appHubAppName').first().innerText();
const pattern = "/app/"; const pattern = "/app/";
let game_id = page.url().substring(page.url().indexOf(pattern) + pattern.length); let game_id = page.url().substring(page.url().indexOf(pattern) + pattern.length);
game_id = game_id.substring(0, game_id.indexOf("/")); game_id = game_id.substring(0, game_id.indexOf("/"));
db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only!
db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only!
const notify_game = { title, url: url, status: 'failed' }; const notify_game = { title, url: url, status: 'failed' };
notify_games.push(notify_game); // status is updated below notify_games.push(notify_game); // status is updated below
const alreadyOwned = await page.locator('.game_area_already_owned').first(); const alreadyOwned = await page.locator('.game_area_already_owned').first();
if (await alreadyOwned.isVisible()) { if (await alreadyOwned.isVisible()) {
console.log("Game " + title + " already in library"); console.log("Game " + title + " already in library");
db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed
} }
else { else {
await page.locator(('#freeGameBtn')).click(); if (url.includes("https://store.steampowered.com/agecheck/app")) {
try {
await page.waitForSelector('#agegate_birthday_desc', { timeout: 5000 });
// Select a random day between 1 and 31
const dayIndex = Math.floor(Math.random() * 31) + 1;
await page.selectOption('#ageDay', { value: dayIndex.toString() });
// Select a random month between January and December
const months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
const monthIndex = Math.floor(Math.random() * months.length);
await page.selectOption('#ageMonth', { value: months[monthIndex] });
// Select a year between 1900 and the current year
const currentDate = new Date();
const yearIndex = Math.floor(Math.random() * (currentDate.getFullYear() - 1900 + 1)) + 1900;
await page.fill('#ageYear', yearIndex.toString());
await Promise.all([
page.click('#view_product_page_btn'),
page.waitForNavigation({ waitUntil: 'networkidle2' })
]);
} catch (error) {
console.error("Age gate not found or failed to handle");
}
}
try {
await page.waitForSelector('#freeGameBtn', { timeout: 5000 }); // Wait for the free game button to appear
await page.click('#freeGameBtn');
console.log("purchased (using #freeGameBtn)");
} catch (error) {
try {
const button = await page.locator('.btn_green_steamui.btn_medium[data-action="add_to_account"]');
if ((await button.textContent()) === "Add to Account") {
await button.click();
console.log("purchased (using .btn_green_steamui.btn_medium with text 'Add to Account' and data-action='add_to_account')");
} else {
console.error(`Button found but text is not 'Add to Account': ${await button.textContent()}`);
}
} catch (error) {
try {
const button = await page.locator('.btn_green_steamui.btn_medium');
if ((await button.textContent()) === "Add to Account") {
await button.click();
console.log("purchased (using .btn_green_steamui.btn_medium with text 'Add to Account')");
} else {
console.error(`Button found but text is not 'Add to Account': ${await button.textContent()}`);
}
} catch (error) {
console.error(`Failed to claim game: Button not found`);
}
}
}
console.log("purchased"); console.log("purchased");
db.data[user][game_id].status = 'claimed'; db.data[user][game_id].status = 'claimed';
db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time
@ -175,6 +329,9 @@ async function isClaimedUrl(url) {
} }
} }
try { try {
await claim(); await claim();
} catch (error) { } catch (error) {
@ -188,6 +345,5 @@ try {
notify(`steam (${user}):<br>${html_game_list(notify_games)}`); notify(`steam (${user}):<br>${html_game_list(notify_games)}`);
} }
} }
if (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies())); if (cfg.debug) fs.writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies()));
if (page.video()) console.log('Recorded video:', await page.video().path());
await context.close(); await context.close();