Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

build(deps): bump docker/setup-buildx-action from 2 to 3 #3

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
uses: docker/setup-qemu-action@v2
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v3
-
name: Login to Docker Hub
uses: docker/login-action@v2
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@ Available options/variables and their default values:
| GOG_EMAIL | | GOG email for login. Overrides EMAIL. |
| GOG_PASSWORD | | GOG password for login. Overrides PASSWORD. |
| GOG_NEWSLETTER | 0 | Do not unsubscribe from newsletter after claiming a game if 1. |
| STEAM_USERNAME | | Steam username for login. Overrides EMAIL. |
| STEAM_PASSWORD | | Steam password for login. Overrides PASSWORD. |
| STEAM_JSON | [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. |


See `config.js` for all options.

Expand Down
5 changes: 5 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,9 @@ export const cfg = {
// 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
steam_username: process.env.STEAM_USERNAME,
steam_password: process.env.STEAM_PASSWORD || process.env.PASSWORD,
steam_json: process.env.STEAM_JSON || 'https://raw.githubusercontent.com/vogler/free-games-claimer/main/steam-games.json',
};
154 changes: 154 additions & 0 deletions steam-games.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra
import { resolve, jsonDb, datetime, prompt, stealth, notify, html_game_list, handleSIGINT } from './util.js';
import path from 'path';
import { existsSync, writeFileSync } from 'fs';
import { cfg } from './config.js';
import { config } from 'dotenv';

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/'

console.log(datetime(), 'started checking steam');

const db = await jsonDb('steam.json', {});

handleSIGINT();

// https://playwright.dev/docs/auth#multi-factor-authentication
const context = await firefox.launchPersistentContext(cfg.dir.browser, {
// chrome will not work in linux arm64, only chromium
// 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', // see replace of Headless in util.newStealthContext. TODO Windows UA enough to avoid 'device not supported'? update if browser is updated?
// userAgent for firefox: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:106.0) Gecko/20100101 Firefox/106.0
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, // will record a .webm video for each page navigated; without size, video would be scaled down to fit 800x800
recordHar: cfg.record ? { path: `data/record/eg-${datetime()}.har` } : undefined, // will record a HAR file with network requests and responses; can be imported in Chrome devtools
args: [ // https://peter.sh/experiments/chromium-command-line-switches
// don't want to see bubble 'Restore pages? Chrome didn't shut down correctly.'
// '--restore-last-session', // does not apply for crash/killed
'--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
// console.debug('userAgent:', await page.evaluate(() => navigator.userAgent));

const notify_games = [];
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 password = username && (cfg.steam_password || await prompt({type: 'password', message: 'Enter password'}));
if (username && password) {
await page.type('input[type=text]:visible', username);
await page.type('input[type=password]:visible', password);
await page.waitForTimeout(2000);
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(){
await page.goto(URL_CLAIM, { waitUntil: 'domcontentloaded' }); // default 'load' takes forever
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.

const signIn = page.locator('a:has-text("Sign In")').first();
while (await signIn.isVisible()) {
console.error('Not signed in to steam.');

await doLogin();
}

user = await page.locator("#account_pulldown").first().innerText();
console.error('You are logged in as ' + user);
db.data[user] ||= {};

const response = await page.goto(cfg.steam_json);
const items = await response.json();
for (const item of items) {
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))
continue;
}
}

await page.goto(item.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: item.url, status: 'failed' };
notify_games.push(notify_game); // status is updated below

const alradyOwned = await page.locator('.game_area_already_owned').first()
if (await alradyOwned.isVisible())
{
console.log("Game " + title + " already in library")
db.data[user][game_id].status ||= 'existed'; // does not overwrite claimed or failed
}
else
{
await page.locator(('#freeGameBtn')).click();
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...
}
}

try {
await claim()
} catch (error) {
console.error(error); // .toString()?
process.exitCode ||= 1;
if (error.message && process.exitCode != 130)
notify(`steam failed: ${error.message.split('\n')[0]}`);
} finally {
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 (cfg.debug) writeFileSync(path.resolve(cfg.dir.browser, 'cookies.json'), JSON.stringify(await context.cookies()));
await context.close();
5 changes: 5 additions & 0 deletions steam-games.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[
{
"url": "https://store.steampowered.com/app/2447060/Monster_Tiles_TD/"
}
]
Loading