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

Added possibility to claim other freegames from GOG #206

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
19 changes: 19 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if I want to add vscode configurations.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought it's maybe good to have it if someone uses codespaces to fix something quickly

"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch GOG",
"outputCapture": "std",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/gog.js",
"env": {
"GOG_GIVEAWAY": "1",
"GOG_FREEGAMES": "1"
}
}
]
}
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. |
| GOG_GIVEAWAY | 1 | Claims giveaway game(s). Is enabled by default. |
| GOG_FREEGAMES | 0 | Claims other free games that are not demos or prologue games. Is disabled by default. |
4n4n4s marked this conversation as resolved.
Show resolved Hide resolved
| GOG_FREEGAMES_URL | [freegames_url](https://www.gog.com/en/games?priceRange=0,0&languages=en&order=asc:title&hideDLCs=true&excludeTags=demo&excludeTags=freegame) | URL to get games to claim additionally. You can add filters for language or certain categories that you don't like. The filter to hide owned games (hideOwned=true) is automatically added so please do not add it.|
4n4n4s marked this conversation as resolved.
Show resolved Hide resolved


See `config.js` for all options.

Expand Down
3 changes: 3 additions & 0 deletions config.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ export const cfg = {
gog_email: process.env.GOG_EMAIL || process.env.EMAIL,
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_giveaway: process.env.GOG_GIVEAWAY == '1',
gog_freegames: false || process.env.GOG_FREEGAMES == '1',
gog_freegames_url: process.env.GOG_FREEGAMES_URL || "https://www.gog.com/en/games?priceRange=0,0&languages=en&order=asc:title&hideDLCs=true&excludeTags=demo&excludeTags=freegame",
// OTP only via GOG_EMAIL, can't add app...

// experimmental - likely to change
Expand Down
169 changes: 158 additions & 11 deletions gog.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdate
import path from 'path';
import { resolve, jsonDb, datetime, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js';
import { cfg } from './config.js';
import path from "path";
import { existsSync } from "fs";

const screenshot = (...a) => resolve(cfg.dir.screenshots, 'gog', ...a);

Expand Down Expand Up @@ -89,6 +91,27 @@ try {
console.log(`Signed in as ${user}`);
db.data[user] ||= {};

if (cfg.gog_giveaway) {
await claimGiveaway();
}
if (cfg.gog_freegames) {
await claimFreegames();
}
} catch (error) {
process.exitCode ||= 1;
console.error('--- Exception:');
console.error(error); // .toString()?
if (error.message && process.exitCode != 130)
notify(`gog 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(`gog (${user}):<br>${html_game_list(notify_games)}`);
}
}

async function claimGiveaway() {
console.log("Claiming giveaway");
const banner = page.locator('#giveaway');
if (!await banner.count()) {
console.log('Currently no free giveaway!');
Expand Down Expand Up @@ -136,17 +159,141 @@ try {
await page.locator('li:has-text("Promotions and hot deals") label').uncheck();
}
}
} catch (error) {
process.exitCode ||= 1;
console.error('--- Exception:');
console.error(error); // .toString()?
if (error.message && process.exitCode != 130)
notify(`gog 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(`gog (${user}):<br>${html_game_list(notify_games)}`);
}

async function claimGame(url){
await page.goto(url, { waitUntil: 'networkidle' });

const title = await page.locator("h1").first().innerText();

const ageGateButton = page.locator("button.age-gate__button").first();
if (await ageGateButton.isVisible()) {
await ageGateButton.click();
}

const game_id = page
.url()
.split("/")
.filter((x) => !!x)
.pop();
db.data[user][game_id] ||= { title, time: datetime(), url: page.url() }; // this will be set on the initial run only!
console.log("Current free game:", title);
const notify_game = { title, url, status: "failed" };
notify_games.push(notify_game); // status is updated below

const playforFree = page
.locator('a.cart-button:visible')
.first();
const addToCart = page
.locator('button.cart-button:visible')
.first();
const inLibrary = page
.locator("button.go-to-library-button")
.first()
const inCart = page
.locator('.cart-button__state-in-cart:visible')
.first();

await Promise.any([playforFree.waitFor(), addToCart.waitFor(), inCart.waitFor(), inLibrary.waitFor()]);

if (await inLibrary.isVisible()) {
console.log("Already in library! Nothing to claim.");
notify_game.status = "existed";
db.data[user][game_id].status ||= "existed"; // does not overwrite claimed or failed
await db.write();
} else if (await inCart.isVisible() || await addToCart.isVisible() || await playforFree.isVisible()) {
if (await inCart.isVisible()) {
console.log("Not in library yet! But in cart.");
await inCart.click();
} else if (await addToCart.isVisible()) {
console.log("Not in library yet! Click ADD TO CART.");

await addToCart.click();
await inCart.isVisible();
await inCart.click();
} else if (await playforFree.isVisible()) {
console.log("Play For Free. Can't be added to library!" + url);
return;
}

await page.waitForURL('**/checkout/**');
if (await page.locator('.order-message--error').isVisible()) {
console.log("skipping : " + await page.locator('.order-message--error').innerText());
await page.locator('span[data-cy="product-remove-button"]').click();
return;
}

await page.locator('button[data-cy="payment-checkout-button"]').click();

await page.waitForURL('**/order/status/**');
await page.locator('p[data-cy="order-message"]').isVisible();

notify_game.status = "claimed";
db.data[user][game_id].status = "claimed";
db.data[user][game_id].time = datetime(); // claimed time overwrites failed/dryrun time
console.log("Claimed successfully!");
await db.write();
}

const p = path.resolve(cfg.dir.screenshots, 'gog', `${game_id}.png`);
if (!existsSync(p)) await page.screenshot({ path: p, fullPage: false }); // fullPage is quite long...
}
if (page.video()) console.log('Recorded video:', await page.video().path())

async function claimFreegames(){
console.log("claiming freegames from " + cfg.gog_freegames_url + ". (adding fitler for ownedgames manually)")
await page.goto(cfg.gog_freegames_url, { waitUntil: 'networkidle' });
await page.locator('label[selenium-id="hideOwnedCheckbox"]').click(); // when you add it to url immediately it shows more results
await page.waitForTimeout(2500);
var allLinks = [];
var hasMorePages = true;
do {
const links = await page.locator(".product-tile").all()
const gameUrls = await Promise.all(
links.map(async (game) => {
var urlSlug = await game.getAttribute("href");
return urlSlug;
})
);
for (const url of gameUrls) {
allLinks.push(url);
}
if (await page.locator('.small-pagination__item--next.disabled').isVisible()){
hasMorePages = false
console.log("last page")
} else {
await page.locator(".small-pagination__item--next").first().click();
console.log("next page - waiting")
await page.waitForTimeout(5000); // wait until page is loaded it takes some time with filters
}

} while (hasMorePages)
console.log("Found total games: " + allLinks.length)
allLinks = allLinks.filter(function (str) { return !str.endsWith("_prologue") });
allLinks = allLinks.filter(function (str) { return !str.endsWith("_demo") });
console.log("Filtered count: " + allLinks.length)

for (const url of allLinks)
{
if (isNotClaimedUrl(url))
{
console.log(url)
await claimGame(url);
}
}
}

function isNotClaimedUrl(url) {
try {
var status = db.data[user][url.split("/").filter((x) => !!x).pop()]["status"];
if (status === "existed" || status === "claimed") {
4n4n4s marked this conversation as resolved.
Show resolved Hide resolved
return false;
} else {
return true
}
} catch (error) {
return true
}
}

if (page.video()) console.log('Recorded video:', await page.video().path())
await context.close();
Loading