Added possibility to claim other freegames from GOG
This commit is contained in:
parent
d51f7310d9
commit
a8809fcd79
4 changed files with 183 additions and 11 deletions
19
.vscode/launch.json
vendored
Normal file
19
.vscode/launch.json
vendored
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -87,6 +87,10 @@ 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. |
|
||||||
|
| GOG_GIVEAWAYS | 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. |
|
||||||
|
| 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.|
|
||||||
|
|
||||||
|
|
||||||
See `config.js` for all options.
|
See `config.js` for all options.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,9 @@ 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
|
||||||
|
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...
|
// OTP only via GOG_EMAIL, can't add app...
|
||||||
|
|
||||||
// experimmental - likely to change
|
// experimmental - likely to change
|
||||||
|
|
|
||||||
168
gog.js
168
gog.js
|
|
@ -1,6 +1,8 @@
|
||||||
import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra
|
import { firefox } from 'playwright-firefox'; // stealth plugin needs no outdated playwright-extra
|
||||||
import { resolve, jsonDb, datetime, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js';
|
import { resolve, jsonDb, datetime, filenamify, prompt, notify, html_game_list, handleSIGINT } from './util.js';
|
||||||
import { cfg } from './config.js';
|
import { cfg } from './config.js';
|
||||||
|
import path from "path";
|
||||||
|
import { existsSync } from "fs";
|
||||||
|
|
||||||
const screenshot = (...a) => resolve(cfg.dir.screenshots, 'gog', ...a);
|
const screenshot = (...a) => resolve(cfg.dir.screenshots, 'gog', ...a);
|
||||||
|
|
||||||
|
|
@ -87,6 +89,26 @@ try {
|
||||||
console.log(`Signed in as ${user}`);
|
console.log(`Signed in as ${user}`);
|
||||||
db.data[user] ||= {};
|
db.data[user] ||= {};
|
||||||
|
|
||||||
|
if (cfg.gog_giveaway) {
|
||||||
|
await claimGiveaway();
|
||||||
|
}
|
||||||
|
if (cfg.gog_freegames) {
|
||||||
|
await claimFreegames();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error); // .toString()?
|
||||||
|
process.exitCode ||= 1;
|
||||||
|
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');
|
const banner = page.locator('#giveaway');
|
||||||
if (!await banner.count()) {
|
if (!await banner.count()) {
|
||||||
console.log('Currently no free giveaway!');
|
console.log('Currently no free giveaway!');
|
||||||
|
|
@ -130,19 +152,143 @@ try {
|
||||||
if (status == 'claimed' && !cfg.gog_newsletter) {
|
if (status == 'claimed' && !cfg.gog_newsletter) {
|
||||||
console.log("Unsubscribe from 'Promotions and hot deals' newsletter");
|
console.log("Unsubscribe from 'Promotions and hot deals' newsletter");
|
||||||
await page.goto('https://www.gog.com/en/account/settings/subscriptions');
|
await page.goto('https://www.gog.com/en/account/settings/subscriptions');
|
||||||
await page.locator('li:has-text("Marketing communications through Trusted Partners") label').uncheck();
|
|
||||||
await page.locator('li:has-text("Promotions and hot deals") label').uncheck();
|
await page.locator('li:has-text("Promotions and hot deals") label').uncheck();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error(error); // .toString()?
|
|
||||||
process.exitCode ||= 1;
|
|
||||||
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...
|
||||||
|
}
|
||||||
|
|
||||||
|
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") {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await context.close();
|
await context.close();
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue