NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
// ==UserScript== // @namespace https://openuserjs.org/users/SB100 // @name GGn IGDb Game Recommendations // @description Show game recommendations from IGDb on Game pages // @updateURL https://openuserjs.org/meta/SB100/GGn_IGDb_Game_Recommendations.meta.js // @version 1.0.1 // @author SB100 // @copyright 2021, SB100 (https://openuserjs.org/users/SB100) // @license MIT // @include https://gazellegames.net/torrents.php?id=* // @grant GM_xmlhttpRequest // @connect www.igdb.com // ==/UserScript== // ==OpenUserJS== // @author SB100 // ==/OpenUserJS== /* jshint esversion: 6 */ const URL_IGDB_BASE = 'https://www.igdb.com'; const URL_IGDB_SEARCH = 'https://www.igdb.com/search?type=1&q='; const URL_GGN_SEARCH = 'https://gazellegames.net/torrents.php?order_by=relevance&order_way=desc&artistname=Games&searchstr='; /** * Turn a HTML string into a HTML element so that we can run querySelector calls against it */ function htmlToElement(html) { var template = document.createElement('template'); html = html.trim(); template.innerHTML = html; return template.content; } function query(url) { let resolver; let rejecter; const p = new Promise((resolveFn, rejectFn) => { resolver = resolveFn; rejecter = rejectFn; }); GM_xmlhttpRequest({ method: 'get', url: url, timeout: 5000, onloadstart: function () {}, onload: result => resolver(result), onerror: result => rejecter(result), ontimeout: result => rejecter(result), }); return p; } function searchIGDb(gameName, year) { const url = `${URL_IGDB_SEARCH}${encodeURIComponent(gameName)}`; return query(url).then(result => { if (result.status !== 200) { return; } return htmlToElement(result.response); }).then(doc => { const jsonElem = doc.querySelector('#results_json'); const json = jsonElem && JSON.parse(jsonElem.dataset.json); if (!json || json.length === 0) { return []; } return json.filter(item => item.type === 'game').map(item => ({ name: item.data.name, nameFormatted: item.data.name.toLowerCase().replace(/[^\w\s]/g, ''), url: item.data.url, year: item.data.release_year, })); }); } function findRecommendationsFromIGDbPage(bestMatch) { const url = `${URL_IGDB_BASE}${bestMatch.url}`; return query(url).then(result => { if (result.status !== 200) { return; } return htmlToElement(result.response); }).then(doc => { const recElems = Array.from(doc.querySelectorAll('.col-md-9 > .game-list-inline .game-list-inline-item a')); return recElems.map(a => ({ image: a.querySelector('img').src, name: a.querySelector('span').innerText })); }); } function getGameNameAndYearFromGGn() { const elem = document.getElementById('display_name').cloneNode(true); const groupPlatform = elem.querySelector('#groupplatform'); elem.removeChild(groupPlatform); const gameMatch = elem.innerText.match(/\-\s([^\(]+)/); const gameName = gameMatch && gameMatch[1].trim().toLowerCase().replace(/[^\w\s]/g, ''); const yearMatch = elem.innerText.match(/\(([\d]+)\)/); const year = yearMatch && yearMatch[1].length === 4 && parseInt(yearMatch[1], 10); return { gameName, year } } function findBestMatch(json, gameName, year) { // match both name and year for (let i = 0, len = json.length; i < len; i += 1) { if (json[i].nameFormatted === gameName && json[i].year === year) { return json[i]; } } // just match name for (let i = 0, len = json.length; i < len; i += 1) { if (json[i].nameFormatted === gameName) { return json[i]; } } return null; } function createRecommendationsBox(recommendations) { const builtRecs = recommendations.map(rec => { return `<li class='recommendation__item' title='${rec.name}'> <a href='${URL_GGN_SEARCH}${encodeURIComponent(rec.name)}'> <img src='${rec.image}' alt='${rec.name} Cover' /> <div class='recommendation__name'>${rec.name}</div> </a> </li>` }).join('') const div = document.createElement('div'); div.classList.add('box'); div.innerHTML = `<div class='head'>Recommendations from IGDb</div> <div class='body similar'> <ul class='nobullet recommendation__cont'> ${builtRecs} </ul> </div> `; const allBoxes = Array.from(document.querySelectorAll('.main_column > .box')); const lastBox = allBoxes[allBoxes.length - 1]; // append after lastBox.parentNode.insertBefore(div, lastBox.nextSibling); } function createStyleTag() { const css = `.recommendation__cont { display: flex; flex-wrap: wrap; justify-content: space-between; } .recommendation__cont::after { content: ""; flex: auto; flex-basis: 130px; flex-grow: 0 } .recommendation__item a { display: inline-block; position: relative; } .recommendation__name { display: none; position: absolute; left: 0; right: 0; bottom: 0; padding: 7px; background: rgba(0,0,0,0.8); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .recommendation__item:hover .recommendation__name { display: block; }`; const style = document.createElement('style'); style.type = 'text/css'; style.appendChild(document.createTextNode(css)); document.head.appendChild(style); } (async function () { 'use strict'; const { gameName, year } = getGameNameAndYearFromGGn(); const results = await searchIGDb(gameName); if (!results || results.length === 0) { console.log('[GGn IGDb Game Recommendations] No IGDb results'); return; } const bestMatch = findBestMatch(results, gameName, year); if (!bestMatch) { console.log('[GGn IGDb Game Recommendations] No game match found'); return; } const recommendations = await findRecommendationsFromIGDbPage(bestMatch); if (!recommendations) { console.log('[GGn IGDb Game Recommendations] No recommendations found'); return; } createStyleTag(); createRecommendationsBox(recommendations); })();