Highlights games on GameSieve that are owned on GOG
// ==UserScript==
// @name GOG Owned Games Highlighter for GameSieve
// @namespace http://tampermonkey.net/
// @version 1.1
// @description Highlights games on GameSieve that are owned on GOG
// @author shakeyourbunny
// @license MIT
// @match https://gamesieve.com/*
// @grant GM.xmlHttpRequest
// @grant GM.setValue
// @grant GM.getValue
// @connect gog.com
// ==/UserScript==
(function() {
'use strict';
// Configuration
const DEBUG = false; // Set to true to enable detailed console logging
const OWNED_GAMES_TTL = 60 * 60 * 1000; // 1 hour in milliseconds
const HIGHLIGHT_COLOR = '#bc9aca'; // GOG color
// Logging function
const log = (message, forceLog = false) => {
if (DEBUG || forceLog) {
console.log(`[GOG Highlighter] ${message}`);
}
};
// Error logging function (always shown)
const error = (message) => {
console.error(`[GOG Highlighter ERROR] ${message}`);
};
// Function to fetch owned games from GOG
const fetchOwnedGames = async () => {
log('Fetching owned games from GOG');
return new Promise((resolve, reject) => {
GM.xmlHttpRequest({
method: 'GET',
url: 'https://www.gog.com/user/data/games',
onload: function(response) {
// Check if we got a redirect (not logged in)
if (response.finalUrl !== 'https://www.gog.com/user/data/games') {
error('You are not logged into GOG. Please log in to see owned games.');
reject('Not logged in');
return;
}
try {
const data = JSON.parse(response.responseText);
if (data && data.owned && data.owned.length > 0) {
log(`Found ${data.owned.length} owned games`, true);
resolve(data.owned);
} else {
error('No owned games found or invalid response format');
reject('No owned games');
}
} catch (e) {
error(`Failed to parse GOG response: ${e.message}`);
reject(e);
}
},
onerror: function(response) {
error(`Failed to fetch owned games: ${response.statusText}`);
reject(response.statusText);
}
});
});
};
// Cache management functions
const saveOwnedGames = async (games) => {
await GM.setValue('ownedGames', games);
await GM.setValue('ownedGamesTimestamp', Date.now());
log(`Saved ${games.length} owned games to cache`);
};
const getOwnedGames = async () => {
const games = await GM.getValue('ownedGames', null);
const timestamp = await GM.getValue('ownedGamesTimestamp', 0);
const isExpired = Date.now() - timestamp > OWNED_GAMES_TTL;
if (!games || isExpired) {
log('Owned games cache expired or not found, fetching fresh data');
try {
const freshGames = await fetchOwnedGames();
await saveOwnedGames(freshGames);
return freshGames;
} catch (e) {
log(`Error fetching fresh owned games: ${e}`, true);
if (games) {
log('Using expired cache as fallback', true);
return games;
}
return [];
}
}
log(`Using cached owned games (${games.length} games)`);
return games;
};
// Function to highlight owned games on GameSieve
const highlightOwnedGames = async () => {
log('Starting highlighting process', true);
try {
const ownedIds = await getOwnedGames();
if (ownedIds.length === 0) {
log('No owned games found to highlight', true);
return;
}
// Convert owned IDs to a Set for fast lookup
const ownedIdsSet = new Set(ownedIds.map(id => id.toString()));
// Select game list on gamesieve.com
const gameList = document.querySelector('body > main > article.main > ol');
if (!gameList) {
log('Game list not found on page', true);
return;
}
// Process each game in the list
const gameItems = gameList.querySelectorAll('li[id]');
let highlightedCount = 0;
gameItems.forEach(item => {
const gogId = item.getAttribute('id');
if (!gogId) return;
// If the game is owned, highlight it
if (ownedIdsSet.has(gogId)) {
item.style.backgroundColor = HIGHLIGHT_COLOR;
highlightedCount++;
// Add a visual indicator to the game title for accessibility
const titleElement = item.querySelector('h3 .title');
if (titleElement && !titleElement.textContent.includes(' ✓')) {
titleElement.textContent += ' ✓';
}
}
});
log(`Highlighted ${highlightedCount} owned games out of ${gameItems.length} games on page`, true);
} catch (e) {
error(`Failed to highlight games: ${e.message}`);
}
};
// Function to observe DOM changes for dynamic content loading
const observePageChanges = () => {
const observer = new MutationObserver((mutations) => {
let shouldRecheck = false;
mutations.forEach((mutation) => {
// Check if new game items were added
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
// Check if it's a game item or contains game items
if (node.tagName === 'LI' && node.hasAttribute('id')) {
shouldRecheck = true;
} else if (node.querySelector && node.querySelector('li[id]')) {
shouldRecheck = true;
}
}
});
}
});
if (shouldRecheck) {
log('New content detected, re-highlighting games');
setTimeout(highlightOwnedGames, 100); // Small delay to ensure DOM is settled
}
});
// Observe the main content area for changes
const mainContent = document.querySelector('body > main');
if (mainContent) {
observer.observe(mainContent, {
childList: true,
subtree: true
});
log('Started observing page changes');
}
return observer;
};
// Initialize the script
const initialize = () => {
log('Script started', true);
highlightOwnedGames();
observePageChanges();
};
// Run when the page is fully loaded
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initialize);
} else {
// Page is already loaded
initialize();
}
})();