Merge e1e9986847 into 99c1f05302
This commit is contained in:
commit
76bd0b78c9
5 changed files with 350 additions and 65 deletions
22
.vscode/launch.json
vendored
Normal file
22
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Steam",
|
||||||
|
"outputCapture": "std",
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**"
|
||||||
|
],
|
||||||
|
"program": "${workspaceFolder}/steam-games.js",
|
||||||
|
"env": {
|
||||||
|
"STEAM_JSON": "1",
|
||||||
|
"STEAM_GAMERPOWER": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -92,6 +92,11 @@ Available options/variables and their default values:
|
||||||
| GOG_EMAIL | | GOG email for login. Overrides EMAIL. |
|
| GOG_EMAIL | | GOG email for login. Overrides EMAIL. |
|
||||||
| GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. |
|
| GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. |
|
||||||
| GOG_NEWSLETTER | 0 | Do not unsubscribe from newsletter after claiming a game if 1. |
|
| GOG_NEWSLETTER | 0 | Do not unsubscribe from newsletter after claiming a game if 1. |
|
||||||
|
| STEAM_USERNAME | | Steam username for login. |
|
||||||
|
| STEAM_PASSWORD | | Steam password for login. Overrides PASSWORD. |
|
||||||
|
| STEAM_JSON | 0 | Claims steam games from json. STEAM_JSON_URL can be defined. |
|
||||||
|
| STEAM_JSON_URL | [steam-games.json](https://raw.githubusercontent.com/vogler/free-games-claimer/main/steam-games.json) | A list of steam urls in json format to claim the games. |
|
||||||
|
| STEAM_GAMERPOWER | 0 | Claims steam games using [gamerpower api](https://www.gamerpower.com/api/giveaways?platform=steam&type=game). |
|
||||||
| LG_EMAIL | | Legacy Games: email to use for redeeming (if not set, defaults to PG_EMAIL) |
|
| LG_EMAIL | | Legacy Games: email to use for redeeming (if not set, defaults to PG_EMAIL) |
|
||||||
|
|
||||||
See `src/config.js` for all options.
|
See `src/config.js` for all options.
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,21 @@ export const cfg = {
|
||||||
gog_email: process.env.GOG_EMAIL || process.env.EMAIL,
|
gog_email: process.env.GOG_EMAIL || process.env.EMAIL,
|
||||||
gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD,
|
gog_password: process.env.GOG_PASSWORD || process.env.PASSWORD,
|
||||||
gog_newsletter: process.env.GOG_NEWSLETTER == '1', // do not unsubscribe from newsletter after claiming a game
|
gog_newsletter: process.env.GOG_NEWSLETTER == '1', // do not unsubscribe from newsletter after claiming a game
|
||||||
|
// OTP only via GOG_EMAIL, can't add app...
|
||||||
|
// auth xbox
|
||||||
|
xbox_email: process.env.XBOX_EMAIL || process.env.EMAIL,
|
||||||
|
xbox_password: process.env.XBOX_PASSWORD || process.env.PASSWORD,
|
||||||
|
xbox_otpkey: process.env.XBOX_OTPKEY,
|
||||||
|
// experimmental - likely to change
|
||||||
|
pg_redeem: process.env.PG_REDEEM == '1', // prime-gaming: redeem keys on external stores
|
||||||
|
pg_claimdlc: process.env.PG_CLAIMDLC == '1', // prime-gaming: claim in-game content
|
||||||
|
|
||||||
|
steam_username: process.env.STEAM_USERNAME,
|
||||||
|
steam_password: process.env.STEAM_PASSWORD || process.env.PASSWORD,
|
||||||
|
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_gamerpower: true,
|
||||||
|
steam_gamerpower_url: process.env.STEAM_GAMERPOWER_URL || 'https://www.gamerpower.com/api/giveaways?platform=steam&type=game',
|
||||||
// auth AliExpress
|
// auth AliExpress
|
||||||
ae_email: process.env.AE_EMAIL || process.env.EMAIL,
|
ae_email: process.env.AE_EMAIL || process.env.EMAIL,
|
||||||
ae_password: process.env.AE_PASSWORD || process.env.PASSWORD,
|
ae_password: process.env.AE_PASSWORD || process.env.PASSWORD,
|
||||||
|
|
|
||||||
351
steam-games.js
351
steam-games.js
|
|
@ -1,73 +1,316 @@
|
||||||
import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra
|
import { chromium } from 'patchright';
|
||||||
import { jsonDb, prompt } from './src/util.js';
|
import dotenv from 'dotenv';
|
||||||
|
import path from 'path';
|
||||||
|
import { existsSync, writeFileSync } from 'fs';
|
||||||
|
import { resolve, jsonDb, datetime, prompt, notify, html_game_list, handleSIGINT } from './src/util.js';
|
||||||
import { cfg } from './src/config.js';
|
import { cfg } from './src/config.js';
|
||||||
|
|
||||||
const db = await jsonDb('steam-games.json', {});
|
const notify_games = [];
|
||||||
|
|
||||||
const user = cfg.steam_id || await prompt({ message: 'Enter Steam community id ("View my profile", then copy from URL)' });
|
const screenshot = (...a) => resolve(cfg.dir.screenshots, 'steam', ...a);
|
||||||
|
const URL_CLAIM = 'https://store.steampowered.com/?l=english';
|
||||||
|
const URL_LOGIN = 'https://store.steampowered.com/login/';
|
||||||
|
|
||||||
// using https://github.com/apify/fingerprint-suite worked, but has no launchPersistentContext...
|
console.log(datetime(), 'started checking steam');
|
||||||
// from https://github.com/apify/fingerprint-suite/issues/162
|
|
||||||
import { FingerprintInjector } from 'fingerprint-injector';
|
|
||||||
import { FingerprintGenerator } from 'fingerprint-generator';
|
|
||||||
|
|
||||||
const { fingerprint, headers } = new FingerprintGenerator().getFingerprint({
|
const db = await jsonDb('steam.json', {});
|
||||||
devices: ["desktop"],
|
handleSIGINT();
|
||||||
operatingSystems: ["windows"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const context = await firefox.launchPersistentContext(cfg.dir.browser, {
|
const context = await chromium.launchPersistentContext(cfg.dir.browser, {
|
||||||
headless: cfg.headless,
|
headless: cfg.headless,
|
||||||
// viewport: { width: cfg.width, height: cfg.height },
|
locale: "en-US",
|
||||||
locale: 'en-US', // ignore OS locale to be sure to have english text for locators -> done via /en in URL
|
recordVideo: cfg.record ? { dir: 'data/record/', size: { width: cfg.width, height: cfg.height } } : undefined,
|
||||||
userAgent: fingerprint.navigator.userAgent,
|
recordHar: cfg.record ? { path: `data/record/eg-${datetime()}.har` } : undefined,
|
||||||
viewport: {
|
handleSIGINT: false,
|
||||||
width: fingerprint.screen.width,
|
args: [
|
||||||
height: fingerprint.screen.height,
|
'--hide-crash-restore-bubble',
|
||||||
},
|
],
|
||||||
extraHTTPHeaders: {
|
|
||||||
'accept-language': headers['accept-language'],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
// await stealth(context);
|
|
||||||
await new FingerprintInjector().attachFingerprintToPlaywright(context, { fingerprint, headers });
|
|
||||||
|
|
||||||
context.setDefaultTimeout(cfg.debug ? 0 : 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
|
||||||
|
|
||||||
try {
|
async function doLogin(page) {
|
||||||
await page.goto(`https://steamcommunity.com/id/${user}/games?tab=all`);
|
const username = cfg.steam_username || await prompt({ message: 'Enter username' });
|
||||||
const games = page.locator('div[data-featuretarget="gameslist-root"] > div.Panel > div.Panel > div');
|
const password = username && (cfg.steam_password || await prompt({ type: 'password', message: 'Enter password' }));
|
||||||
await games.last().waitFor();
|
if (username && password) {
|
||||||
await page.keyboard.press('End');
|
await page.type('input[type=text]:visible', username);
|
||||||
await page.waitForLoadState('networkidle');
|
await page.type('input[type=password]:visible', password);
|
||||||
console.log('All Games:', await games.count());
|
await Promise.all([page.click('button[type=submit]'), page.waitForNavigation()]);
|
||||||
for (const game of await games.all()) {
|
}
|
||||||
const title = await game.locator('span a').innerText();
|
|
||||||
let time, last, achievements, size;
|
|
||||||
const ltime = game.locator('span:has-text("total played")');
|
|
||||||
if (await ltime.count()) time = (await ltime.first().innerText()).split('\n')[1];
|
|
||||||
const llast = game.locator('span:has-text("last played")');
|
|
||||||
if (await llast.count()) last = (await llast.first().innerText()).split('\n')[1];
|
|
||||||
const lachievements = game.locator('a:has-text("achievements") + span');
|
|
||||||
if (await lachievements.count()) achievements = (await lachievements.first().innerText()).split('\n');
|
|
||||||
const lsize = game.locator('span:has(+ button)');
|
|
||||||
if (await lsize.count()) size = await lsize.first().innerText();
|
|
||||||
const url = await game.locator('a').first().getAttribute('href');
|
|
||||||
const img = await game.locator('img').first().getAttribute('src');
|
|
||||||
const stat = { title, time, last, achievements, size, url, img };
|
|
||||||
console.log(stat);
|
|
||||||
db.data[title] = stat;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// await page.pause();
|
|
||||||
|
async function claim() {
|
||||||
|
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.
|
||||||
|
console.log('Cookies added');
|
||||||
|
|
||||||
|
const loginText = await page.textContent('a.global_action_link');
|
||||||
|
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.');
|
||||||
|
await doLogin();
|
||||||
|
loginText = await page.textContent('a.global_action_link');
|
||||||
|
user = await page.locator("#account_pulldown").first().innerText();
|
||||||
|
result = await Promise.race([loginText, user]);
|
||||||
|
}
|
||||||
|
console.log('You are logged in as ' + user);
|
||||||
|
|
||||||
|
db.data[user] ||= {};
|
||||||
|
if (cfg.steam_json) {
|
||||||
|
console.log('Starting to claim from Steam JSON');
|
||||||
|
await claimJson(user);
|
||||||
|
console.log('Finished claiming from Steam JSON');
|
||||||
|
}
|
||||||
|
if (cfg.steam_gamerpower) {
|
||||||
|
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/steam.json`, JSON.stringify(db.data[user], null, 2));
|
||||||
|
console.log('Data written to file for user:', user);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function claimJson(user) {
|
||||||
|
console.log("Claiming JSON");
|
||||||
|
const response = await page.goto(cfg.steam_json_url);
|
||||||
|
const items = await response.json();
|
||||||
|
for (const item of items) {
|
||||||
|
if (!await isClaimedUrl(item.url)) {
|
||||||
|
console.log(item);
|
||||||
|
if (item.hasOwnProperty("startDate")) {
|
||||||
|
const date = Date.parse(item.startDate);
|
||||||
|
if (date >= Date.now()) {
|
||||||
|
console.log("game not available yet " + new Date(date));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await claimGame(item.url, user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function claimGamerpower(user) {
|
||||||
|
console.log("Claiming Gamerpower");
|
||||||
|
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();
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
console.log(item.open_giveaway_url);
|
||||||
|
try {
|
||||||
|
await page.goto(item.open_giveaway_url, { waitUntil: 'domcontentloaded' });
|
||||||
|
const url = page.url();
|
||||||
|
if (url.includes("https://store.steampowered.com/app")) {
|
||||||
|
if (!await isClaimedUrl(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error in claimGamerpower:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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' });
|
||||||
|
const title = await page.locator('#appHubAppName').first().innerText();
|
||||||
|
const pattern = "/app/";
|
||||||
|
let game_id = page.url().substring(page.url().indexOf(pattern) + pattern.length);
|
||||||
|
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!
|
||||||
|
const notify_game = { title, url: url, status: 'failed' };
|
||||||
|
notify_games.push(notify_game); // status is updated below
|
||||||
|
const alreadyOwned = await page.locator('.game_area_already_owned').first();
|
||||||
|
if (await alreadyOwned.isVisible()) {
|
||||||
|
console.log("Game " + title + " already in library");
|
||||||
|
db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
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");
|
||||||
|
db.data[user][game_id].status = 'claimed';
|
||||||
|
db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time
|
||||||
|
}
|
||||||
|
notify_game.status = db.data[user][game_id].status; // claimed or failed
|
||||||
|
const p = screenshot(`${game_id}.png`);
|
||||||
|
if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long...
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isClaimedUrl(url) {
|
||||||
|
try {
|
||||||
|
const pattern = "/app/";
|
||||||
|
let game_id = url.substring(url.indexOf(pattern) + pattern.length);
|
||||||
|
game_id = game_id.substring(0, game_id.indexOf("/"));
|
||||||
|
const status = db.data[user][game_id]["status"];
|
||||||
|
return status === "existed" || status === "claimed";
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
await claim();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
process.exitCode ||= 1;
|
|
||||||
console.error('--- Exception:');
|
|
||||||
console.error(error); // .toString()?
|
console.error(error); // .toString()?
|
||||||
|
process.exitCode ||= 1;
|
||||||
|
if (error.message && process.exitCode != 130)
|
||||||
|
notify(`steam failed: ${error.message.split('\n')[0]}`);
|
||||||
} finally {
|
} finally {
|
||||||
await db.write(); // write out json db
|
await db.write(); // write out json db
|
||||||
|
if (notify_games.filter(g => g.status != 'existed').length) { // don't notify if all were already claimed
|
||||||
|
notify(`steam (${user}):<br>${html_game_list(notify_games)}`);
|
||||||
}
|
}
|
||||||
if (page.video()) console.log('Recorded video:', await page.video().path());
|
}
|
||||||
|
if (cfg.debug) fs.writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies()));
|
||||||
await context.close();
|
await context.close();
|
||||||
Loading…
Add table
Add a link
Reference in a new issue