Simply adds a pirate link to all games on the GOG store
// ==UserScript== // @name Steam/GOG Games Links to Free Download Site // @namespace Kozinc // @version 0.4.8 // @license MIT // @description Simply adds a pirate link to all games on the GOG store // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js // @match https://www.gog.com/game/* // @match https://www.gog.com/en/game/* // @match https://store.steampowered.com/app/* // @grant GM_registerMenuCommand // @grant GM_getValue // @grant GM_setValue // @grant GM.getValue // @grant GM.setValue // @grant GM_deleteValue // @grant GM_xmlhttpRequest // @run-at document-load // ==/UserScript== // Default buttonSet var buttonSet = [ { url: "https://steamrip.com/?s=", title: "SteamRIP", urlSpecial: "" }, { url: "https://www.ovagames.com/?s=", title: "OVA Games", urlSpecial: "" }, { url: "https://fitgirl-repacks.site/?s=", title: "FitGirl", urlSpecial: "" }, { url: "https://dodi-repacks.site/?s=", title: "DODI", urlSpecial: "" }, { url: "https://gload.to/?s=", title: "Gload", urlSpecial: "" }, { url: "https://search.rlsbb.ru/?s=", title: "Release BB", urlSpecial: "" }, { url: "https://scnlog.me/?s=", title: "SCNLOG", urlSpecial: "" }, { url: "https://cpgrepacks.site/?s=", title: "CPG Repacks", urlSpecial: "" }, { url: "https://www.tiny-repacks.win/?s=", title: "Tiny Repacks", urlSpecial: "" }, { url: "https://g4u.to/en/search/?str=", title: "g4u", urlSpecial: "" }, { url: "https://gog-games.to/?q=", title: "GOG-Games.to", urlSpecial: "" }, ]; var unsafeButtonSet = [ { url: "https://gogunlocked.com/?s=", title: "GOG Unlocked", urlSpecial: "" }, { url: "https://igg-games.com/?s=", title: "IGG", urlSpecial: "" }, { url: "https://pcgamestorrents.com/?s=", title: "PC games Torrent", urlSpecial: "" }, ]; var siteSet = [ { url: "https://www.gog.com/game/*", title: "GOG", urlSpecial: "" }, { url: "https://www.gog.com/en/game/*", title: "GOG", urlSpecial: "" }, { url: "https://store.steampowered.com/app/*", title: "Steam", urlSpecial: "" }, // { url: /https:\/\/igg-games.com\/.*.html/, title: "IGG" }, ]; /* * usergui.js -- https://github.com/AugmentedWeb/UserGui/raw/Release-1.0/usergui.js * v1.0.0 * https://github.com/AugmentedWeb/UserGui * Apache 2.0 licensed */ class UserGui { constructor() { const grantArr = GM_info?.script?.grant; if(typeof grantArr == "object") { if(!grantArr.includes("GM_xmlhttpRequest")) { prompt(`${this.#projectName} needs GM_xmlhttpRequest!\n\nPlease add this to your userscript's header...`, "// @grant GM_xmlhttpRequest"); } if(!grantArr.includes("GM_getValue")) { prompt(`${this.#projectName} needs GM_getValue!\n\nPlease add this to your userscript's header...`, "// @grant GM_getValue"); } if(!grantArr.includes("GM_setValue")) { prompt(`${this.#projectName} needs GM_setValue!\n\nPlease add this to your userscript's header...`, "// @grant GM_setValue"); } } } #projectName = "UserGui"; window = undefined; document = undefined; iFrame = undefined; settings = { "window" : { "title" : "No title set", "name" : "userscript-gui", "external" : false, "centered" : false, "size" : { "width" : 300, "height" : 500, "dynamicSize" : true } }, "gui" : { "centeredItems" : false, "internal" : { "darkCloseButton" : false, "style" : ` body { background-color: #ffffff; overflow: hidden; width: 100% !important; } form { padding: 10px; } #gui { height: fit-content; } .rendered-form { padding: 10px; } #header { padding: 10px; cursor: move; z-index: 10; background-color: #2196F3; color: #fff; height: fit-content; } .header-item-container { display: flex; justify-content: space-between; align-items: center; } .left-title { font-size: 14px; font-weight: bold; padding: 0; margin: 0; } #button-close-gui { vertical-align: middle; } div .form-group { margin-bottom: 15px; } #resizer { width: 10px; height: 10px; cursor: se-resize; position: absolute; bottom: 0; right: 0; } .formbuilder-button { width: fit-content; } ` }, "external" : { "popup" : true, "style" : ` .rendered-form { padding: 10px; } div .form-group { margin-bottom: 15px; } ` } }, "messages" : { "blockedPopups" : () => alert(`The GUI (graphical user interface) failed to open!\n\nPossible reason: The popups are blocked.\n\nPlease allow popups for this site. (${window.location.hostname})`) } }; // This error page will be shown if the user has not added any pages #errorPage = (title, code) => ` <style> .error-page { width: 100%; height: fit-content; background-color: black; display: flex; justify-content: center; align-items: center; text-align: center; padding: 25px } .error-page-text { font-family: monospace; font-size: x-large; color: white; } .error-page-tag { margin-top: 20px; font-size: 10px; color: #4a4a4a; font-style: italic; margin-bottom: 0px; } </style> <div class="error-page"> <div> <p class="error-page-text">${title}</p> <code>${code}</code> <p class="error-page-tag">${this.#projectName} error message</p> </div> </div>`; // The user can add multiple pages to their GUI. The pages are stored in this array. #guiPages = [ { "name" : "default_no_content_set", "content" : this.#errorPage("Content missing", "Gui.setContent(html, tabName);") } ]; // The userscript manager's xmlHttpRequest is used to bypass CORS limitations (To load Bootstrap) async #bypassCors(externalFile) { const res = await new Promise(resolve => { GM_xmlhttpRequest({ method: "GET", url: externalFile, onload: resolve }); }); return res.responseText; } // Returns one tab (as HTML) for the navigation tabs #createNavigationTab(page) { const name = page.name; if(name == undefined) { console.error(`[${this.#projectName}] Gui.addPage(html, name) <- name missing!`); return undefined; } else { const modifiedName = name.toLowerCase().replaceAll(' ', '').replace(/[^a-zA-Z0-9]/g, '') + Math.floor(Math.random() * 1000000000); const content = page.content; const indexOnArray = this.#guiPages.map(x => x.name).indexOf(name); const firstItem = indexOnArray == 0 ? true : false; return { "listItem" : ` <li class="nav-item" role="presentation"> <button class="nav-link ${firstItem ? 'active' : ''}" id="${modifiedName}-tab" data-bs-toggle="tab" data-bs-target="#${modifiedName}" type="button" role="tab" aria-controls="${modifiedName}" aria-selected="${firstItem}">${name}</button> </li> `, "panelItem" : ` <div class="tab-pane ${firstItem ? 'active' : ''}" id="${modifiedName}" role="tabpanel" aria-labelledby="${modifiedName}-tab">${content}</div> ` }; } } // Make tabs function without bootstrap.js (CSP might block bootstrap and make the GUI nonfunctional) #initializeTabs() { const handleTabClick = e => { const target = e.target; const contentID = target.getAttribute("data-bs-target"); target.classList.add("active"); this.document.querySelector(contentID).classList.add("active"); [...this.document.querySelectorAll(".nav-link")].forEach(tab => { if(tab != target) { const contentID = tab.getAttribute("data-bs-target"); tab.classList.remove("active"); this.document.querySelector(contentID).classList.remove("active"); } }); } [...this.document.querySelectorAll(".nav-link")].forEach(tab => { tab.addEventListener("click", handleTabClick); }); } // Will determine if a navbar is needed, returns either a regular GUI, or a GUI with a navbar #getContent() { // Only one page has been set, no navigation tabs will be created if(this.#guiPages.length == 1) { return this.#guiPages[0].content; } // Multiple pages has been set, dynamically creating the navigation tabs else if(this.#guiPages.length > 1) { const tabs = (list, panels) => ` <ul class="nav nav-tabs" id="userscript-tab" role="tablist"> ${list} </ul> <div class="tab-content"> ${panels} </div> `; let list = ``; let panels = ``; this.#guiPages.forEach(page => { const data = this.#createNavigationTab(page); if(data != undefined) { list += data.listItem + '\n'; panels += data.panelItem + '\n'; } }); return tabs(list, panels); } } // Returns the GUI's whole document as string async #createDocument() { const bootstrapStyling = await this.#bypassCors("https://raw.githubusercontent.com/AugmentedWeb/UserGui/Release-1.0/resources/bootstrap.css"); const externalDocument = ` <!DOCTYPE html> <html> <head> <title>${this.settings.window.title}</title> <style> ${bootstrapStyling} ${this.settings.gui.external.style} ${ this.settings.gui.centeredItems ? `.form-group { display: flex; justify-content: center; }` : "" } </style> </head> <body> ${this.#getContent()} </body> </html> `; const internalDocument = ` <!doctype html> <html lang="en"> <head> <style> ${bootstrapStyling} ${this.settings.gui.internal.style} ${ this.settings.gui.centeredItems ? `.form-group { display: flex; justify-content: center; }` : "" } </style> </head> <body> <div id="gui"> <div id="header"> <div class="header-item-container"> <h1 class="left-title">${this.settings.window.title}</h1> <div class="right-buttons"> <button type="button" class="${this.settings.gui.internal.darkCloseButton ? "btn-close" : "btn-close btn-close-white"}" aria-label="Close" id="button-close-gui"></button> </div> </div> </div> <div id="content"> ${this.#getContent()} </div> <div id="resizer"></div> </div> </body> </html> `; if(this.settings.window.external) { return externalDocument; } else { return internalDocument; } } // The user will use this function to add a page to their GUI, with their own HTML (Bootstrap 5) addPage(tabName, htmlString) { if(this.#guiPages[0].name == "default_no_content_set") { this.#guiPages = []; } this.#guiPages.push({ "name" : tabName, "content" : htmlString }); } #getCenterScreenPosition() { const guiWidth = this.settings.window.size.width; const guiHeight = this.settings.window.size.height; const x = (screen.width - guiWidth) / 2; const y = (screen.height - guiHeight) / 2; return { "x" : x, "y": y }; } #getCenterWindowPosition() { const guiWidth = this.settings.window.size.width; const guiHeight = this.settings.window.size.height; const x = (window.innerWidth - guiWidth) / 2; const y = (window.innerHeight - guiHeight) / 2; return { "x" : x, "y": y }; } #initializeInternalGuiEvents(iFrame) { // - The code below will consist mostly of drag and resize implementations // - iFrame window <-> Main window interaction requires these to be done // - Basically, iFrame document's event listeners make the whole iFrame move on the main window // Sets the iFrame's size function setFrameSize(x, y) { iFrame.style.width = `${x}px`; iFrame.style.height = `${y}px`; } // Gets the iFrame's size function getFrameSize() { const frameBounds = iFrame.getBoundingClientRect(); return { "width" : frameBounds.width, "height" : frameBounds.height }; } // Sets the iFrame's position relative to the main window's document function setFramePos(x, y) { iFrame.style.left = `${x}px`; iFrame.style.top = `${y}px`; } // Gets the iFrame's position relative to the main document function getFramePos() { const frameBounds = iFrame.getBoundingClientRect(); return { "x": frameBounds.x, "y" : frameBounds.y }; } // Gets the frame body's offsetHeight function getInnerFrameSize() { const innerFrameElem = iFrame.contentDocument.querySelector("#gui"); return { "x": innerFrameElem.offsetWidth, "y" : innerFrameElem.offsetHeight }; } // Sets the frame's size to the innerframe's size const adjustFrameSize = () => { const innerFrameSize = getInnerFrameSize(); setFrameSize(innerFrameSize.x, innerFrameSize.y); } // Variables for draggable header let dragging = false, dragStartPos = { "x" : 0, "y" : 0 }; // Variables for resizer let resizing = false, mousePos = { "x" : undefined, "y" : undefined }, lastFrame; function handleResize(isInsideFrame, e) { if(mousePos.x == undefined && mousePos.y == undefined) { mousePos.x = e.clientX; mousePos.y = e.clientY; lastFrame = isInsideFrame; } const deltaX = mousePos.x - e.clientX, deltaY = mousePos.y - e.clientY; const frameSize = getFrameSize(); const allowedSize = frameSize.width - deltaX > 160 && frameSize.height - deltaY > 90; if(isInsideFrame == lastFrame && allowedSize) { setFrameSize(frameSize.width - deltaX, frameSize.height - deltaY); } mousePos.x = e.clientX; mousePos.y = e.clientY; lastFrame = isInsideFrame; } function handleDrag(isInsideFrame, e) { const bR = iFrame.getBoundingClientRect(); const windowWidth = window.innerWidth, windowHeight = window.innerHeight; let x, y; if(isInsideFrame) { x = getFramePos().x += e.clientX - dragStartPos.x; y = getFramePos().y += e.clientY - dragStartPos.y; } else { x = e.clientX - dragStartPos.x; y = e.clientY - dragStartPos.y; } // Check out of bounds: left if(x <= 0) { x = 0 } // Check out of bounds: right if(x + bR.width >= windowWidth) { x = windowWidth - bR.width; } // Check out of bounds: top if(y <= 0) { y = 0; } // Check out of bounds: bottom if(y + bR.height >= windowHeight) { y = windowHeight - bR.height; } setFramePos(x, y); } // Dragging start (iFrame) this.document.querySelector("#header").addEventListener('mousedown', e => { e.preventDefault(); dragging = true; dragStartPos.x = e.clientX; dragStartPos.y = e.clientY; }); // Resizing start this.document.querySelector("#resizer").addEventListener('mousedown', e => { e.preventDefault(); resizing = true; }); // While dragging or resizing (iFrame) this.document.addEventListener('mousemove', e => { if(dragging) handleDrag(true, e); if(resizing) handleResize(true, e); }); // While dragging or resizing (Main window) document.addEventListener('mousemove', e => { if(dragging) handleDrag(false, e); if(resizing) handleResize(false, e); }); // Stop dragging and resizing (iFrame) this.document.addEventListener('mouseup', e => { e.preventDefault(); dragging = false; resizing = false; }); // Stop dragging and resizing (Main window) document.addEventListener('mouseup', e => { dragging = false; resizing = false; }); // Listener for the close button, closes the internal GUI this.document.querySelector("#button-close-gui").addEventListener('click', e => { e.preventDefault(); this.close(); }); const guiObserver = new MutationObserver(adjustFrameSize); const guiElement = this.document.querySelector("#gui"); guiObserver.observe(guiElement, { childList: true, subtree: true, attributes: true }); adjustFrameSize(); } async #openExternalGui(readyFunction) { const noWindow = this.window?.closed; if(noWindow || this.window == undefined) { let pos = ""; let windowSettings = ""; if(this.settings.window.centered && this.settings.gui.external.popup) { const centerPos = this.#getCenterScreenPosition(); pos = `left=${centerPos.x}, top=${centerPos.y}`; } if(this.settings.gui.external.popup) { windowSettings = `width=${this.settings.window.size.width}, height=${this.settings.window.size.height}, ${pos}`; } // Create a new window for the GUI this.window = window.open("", this.settings.windowName, windowSettings); if(!this.window) { this.settings.messages.blockedPopups(); return; } // Write the document to the new window this.window.document.open(); this.window.document.write(await this.#createDocument()); this.window.document.close(); if(!this.settings.gui.external.popup) { this.window.document.body.style.width = `${this.settings.window.size.width}px`; if(this.settings.window.centered) { const centerPos = this.#getCenterScreenPosition(); this.window.document.body.style.position = "absolute"; this.window.document.body.style.left = `${centerPos.x}px`; this.window.document.body.style.top = `${centerPos.y}px`; } } // Dynamic sizing (only height & window.outerHeight no longer works on some browsers...) this.window.resizeTo( this.settings.window.size.width, this.settings.window.size.dynamicSize ? this.window.document.body.offsetHeight + (this.window.outerHeight - this.window.innerHeight) : this.settings.window.size.height ); this.document = this.window.document; this.#initializeTabs(); // Call user's function if(typeof readyFunction == "function") { readyFunction(); } window.onbeforeunload = () => { // Close the GUI if parent window closes this.close(); } } else { // Window was already opened, bring the window back to focus this.window.focus(); } } async #openInternalGui(readyFunction) { if(this.iFrame) { return; } const fadeInSpeedMs = 250; let left = 0, top = 0; if(this.settings.window.centered) { const centerPos = this.#getCenterWindowPosition(); left = centerPos.x; top = centerPos.y; } const iframe = document.createElement("iframe"); iframe.srcdoc = await this.#createDocument(); iframe.style = ` position: fixed; top: ${top}px; left: ${left}px; width: ${this.settings.window.size.width}; height: ${this.settings.window.size.height}; border: 0; opacity: 0; transition: all ${fadeInSpeedMs/1000}s; border-radius: 5px; box-shadow: rgb(0 0 0 / 6%) 10px 10px 10px; z-index: 2147483647; `; const waitForBody = setInterval(() => { if(document?.body) { clearInterval(waitForBody); // Prepend the GUI to the document's body document.body.prepend(iframe); iframe.contentWindow.onload = () => { // Fade-in implementation setTimeout(() => iframe.style["opacity"] = "1", fadeInSpeedMs/2); setTimeout(() => iframe.style["transition"] = "none", fadeInSpeedMs + 500); this.window = iframe.contentWindow; this.document = iframe.contentDocument; this.iFrame = iframe; this.#initializeInternalGuiEvents(iframe); this.#initializeTabs(); readyFunction(); } } }, 100); } // Determines if the window is to be opened externally or internally open(readyFunction) { if(this.settings.window.external) { this.#openExternalGui(readyFunction); } else { this.#openInternalGui(readyFunction); } } // Closes the GUI if it exists close() { if(this.settings.window.external) { if(this.window) { this.window.close(); } } else { if(this.iFrame) { this.iFrame.remove(); this.iFrame = undefined; } } } saveConfig() { let config = []; if(this.document) { [...this.document.querySelectorAll(".form-group")].forEach(elem => { const inputElem = elem.querySelector("[name]"); const name = inputElem.getAttribute("name"), data = this.getData(name); if(data) { config.push({ "name" : name, "value" : data }); } }); } GM_setValue("config", config); } loadConfig() { const config = this.getConfig(); if(this.document && config) { config.forEach(elemConfig => { this.setData(elemConfig.name, elemConfig.value); }) } } getConfig() { return GM_getValue("config"); } resetConfig() { const config = this.getConfig(); if(config) { GM_setValue("config", []); } } dispatchFormEvent(name) { const type = name.split("-")[0].toLowerCase(); const properties = this.#typeProperties.find(x => type == x.type); const event = new Event(properties.event); const field = this.document.querySelector(`.field-${name}`); field.dispatchEvent(event); } setPrimaryColor(hex) { const styles = ` #header { background-color: ${hex} !important; } .nav-link { color: ${hex} !important; } .text-primary { color: ${hex} !important; } `; const styleSheet = document.createElement("style") styleSheet.innerText = styles; this.document.head.appendChild(styleSheet); } // Creates an event listener a GUI element event(name, event, eventFunction) { this.document.querySelector(`.field-${name}`).addEventListener(event, eventFunction); } // Disables a GUI element disable(name) { [...this.document.querySelector(`.field-${name}`).children].forEach(childElem => { childElem.setAttribute("disabled", "true"); }); } // Enables a GUI element enable(name) { [...this.document.querySelector(`.field-${name}`).children].forEach(childElem => { if(childElem.getAttribute("disabled")) { childElem.removeAttribute("disabled"); } }); } // Gets data from types: TEXT FIELD, TEXTAREA, DATE FIELD & NUMBER getValue(name) { return this.document.querySelector(`.field-${name}`).querySelector(`[id=${name}]`).value; } // Sets data to types: TEXT FIELD, TEXT AREA, DATE FIELD & NUMBER setValue(name, newValue) { this.document.querySelector(`.field-${name}`).querySelector(`[id=${name}]`).value = newValue; this.dispatchFormEvent(name); } // Gets data from types: RADIO GROUP getSelection(name) { return this.document.querySelector(`.field-${name}`).querySelector(`input[name=${name}]:checked`).value; } // Sets data to types: RADIO GROUP setSelection(name, newOptionsValue) { this.document.querySelector(`.field-${name}`).querySelector(`input[value=${newOptionsValue}]`).checked = true; this.dispatchFormEvent(name); } // Gets data from types: CHECKBOX GROUP getChecked(name) { return [...this.document.querySelector(`.field-${name}`).querySelectorAll(`input[name*=${name}]:checked`)] .map(checkbox => checkbox.value); } // Sets data to types: CHECKBOX GROUP setChecked(name, checkedArr) { const checkboxes = [...this.document.querySelector(`.field-${name}`).querySelectorAll(`input[name*=${name}]`)] checkboxes.forEach(checkbox => { if(checkedArr.includes(checkbox.value)) { checkbox.checked = true; } }); this.dispatchFormEvent(name); } // Gets data from types: FILE UPLOAD getFiles(name) { return this.document.querySelector(`.field-${name}`).querySelector(`input[id=${name}]`).files; } // Gets data from types: SELECT getOption(name) { const selectedArr = [...this.document.querySelector(`.field-${name} #${name}`).selectedOptions].map(({value}) => value); return selectedArr.length == 1 ? selectedArr[0] : selectedArr; } // Sets data to types: SELECT setOption(name, newOptionsValue) { if(typeof newOptionsValue == 'object') { newOptionsValue.forEach(optionVal => { this.document.querySelector(`.field-${name}`).querySelector(`option[value=${optionVal}]`).selected = true; }); } else { this.document.querySelector(`.field-${name}`).querySelector(`option[value=${newOptionsValue}]`).selected = true; } this.dispatchFormEvent(name); } #typeProperties = [ { "type": "button", "event": "click", "function": { "get" : null, "set" : null } }, { "type": "radio", "event": "change", "function": { "get" : n => this.getSelection(n), "set" : (n, nV) => this.setSelection(n, nV) } }, { "type": "checkbox", "event": "change", "function": { "get" : n => this.getChecked(n), "set" : (n, nV) => this.setChecked(n, nV) } }, { "type": "date", "event": "change", "function": { "get" : n => this.getValue(n), "set" : (n, nV) => this.setValue(n, nV) } }, { "type": "file", "event": "change", "function": { "get" : n => this.getFiles(n), "set" : null } }, { "type": "number", "event": "input", "function": { "get" : n => this.getValue(n), "set" : (n, nV) => this.setValue(n, nV) } }, { "type": "select", "event": "change", "function": { "get" : n => this.getOption(n), "set" : (n, nV) => this.setOption(n, nV) } }, { "type": "text", "event": "input", "function": { "get" : n => this.getValue(n), "set" : (n, nV) => this.setValue(n, nV) } }, { "type": "textarea", "event": "input", "function": { "get" : n => this.getValue(n), "set" : (n, nV) => this.setValue(n, nV) } }, ]; // The same as the event() function, but automatically determines the best listener type for the element // (e.g. button -> listen for "click", textarea -> listen for "input") smartEvent(name, eventFunction) { if(name.includes("-")) { const type = name.split("-")[0].toLowerCase(); const properties = this.#typeProperties.find(x => type == x.type); if(typeof properties == "object") { this.event(name, properties.event, eventFunction); } else { console.warn(`${this.#projectName}'s smartEvent function did not find any matches for the type "${type}". The event could not be made.`); } } else { console.warn(`The input name "${name}" is invalid for ${this.#projectName}'s smartEvent. The event could not be made.`); } } // Will automatically determine the suitable function for data retrivial // (e.g. file select -> use getFiles() function) getData(name) { if(name.includes("-")) { const type = name.split("-")[0].toLowerCase(); const properties = this.#typeProperties.find(x => type == x.type); if(typeof properties == "object") { const getFunction = properties.function.get; if(typeof getFunction == "function") { return getFunction(name); } else { console.error(`${this.#projectName}'s getData function can't be used for the type "${type}". The data can't be taken.`); } } else { console.warn(`${this.#projectName}'s getData function did not find any matches for the type "${type}". The event could not be made.`); } } else { console.warn(`The input name "${name}" is invalid for ${this.#projectName}'s getData function. The event could not be made.`); } } // Will automatically determine the suitable function for data retrivial (e.g. checkbox -> use setChecked() function) setData(name, newData) { if(name.includes("-")) { const type = name.split("-")[0].toLowerCase(); const properties = this.#typeProperties.find(x => type == x.type); if(typeof properties == "object") { const setFunction = properties.function.set; if(typeof setFunction == "function") { return setFunction(name, newData); } else { console.error(`${this.#projectName}'s setData function can't be used for the type "${type}". The data can't be taken.`); } } else { console.warn(`${this.#projectName}'s setData function did not find any matches for the type "${type}". The event could not be made.`); } } else { console.warn(`The input name "${name}" is invalid for ${this.#projectName}'s setData function. The event could not be made.`); } } }; const Gui = new UserGui; Gui.settings.window.title = "Pirate Games Links Settings"; Gui.settings.window.centered = true; var p = GM_getValue("enableUnsafeButtonSet", null); if(p === "true") { // unsafeButtonSet buttonSet = [...buttonSet, ...unsafeButtonSet]; } var steamDisplaySidebar = GM_getValue("steamDisplaySidebar", true); var steamDisplayCart = GM_getValue("steamDisplayCart", false); var gogDisplaySidebar = GM_getValue("gogDisplaySidebar", true); var gogDisplayCart = GM_getValue("gogDisplayCart", false); var siteSetR###lt = ""; siteSet.forEach((el) => { if(!!document.URL.match(el.url)) siteSetR###lt = el.title; }) // Load saved buttonSet preference let savedButtonSet = GM_getValue("enabledButtonSet", []); if(savedButtonSet.length === 0) { savedButtonSet = buttonSet; } Gui.addPage("Settings", ` <div class="rendered-form"> <div class=""> <h2 access="false" class="text-primary" id="control-274549">Button Settings</h2> </div> <div class="checkbox-group formbuilder-checkbox-group form-group field-checkbox-group-steamDisplay"> <div class="formbuilder-checkbox-group form-group field-checkbox-group-steamDisplay"> <label for="checkbox-group-steamDisplay" class="formbuilder-checkbox-group-label">Steam display:</label> <div class="checkbox-group-steamDisplay"> <div class="formbuilder-checkbox-inline"> <label for="checkbox-group-steamDisplay-0" class="kc-toggle"> <input name="checkbox-group-steamDisplay[]" access="false" id="checkbox-group-steamDisplay-0" value="steamDisplaySidebar" ${steamDisplaySidebar ? 'checked' : ''} type="checkbox"><span></span>Sidebar</label> </div> <div class="formbuilder-checkbox-inline"> <label for="checkbox-group-steamDisplayCart-1" class="kc-toggle"> <input name="checkbox-group-steamDisplayCart[]" access="false" id="checkbox-group-steamDisplayCart-1" value="steamDisplayCart" ${steamDisplayCart ? 'checked' : ''} type="checkbox"><span></span>Cart</label> </div> </div> </div> </div> <div class="checkbox-group formbuilder-checkbox-group form-group field-checkbox-group-gogDisplay"> <div class="formbuilder-checkbox-group form-group field-checkbox-group-gogDisplay"> <label for="checkbox-group-gogDisplay" class="formbuilder-checkbox-group-label">GOG display:</label> <div class="checkbox-group-gogDisplay"> <div class="formbuilder-checkbox-inline"> <label for="checkbox-group-gogDisplay-0" class="kc-toggle"> <input name="checkbox-group-gogDisplay[]" access="false" id="checkbox-group-gogDisplay-0" value="gogDisplaySidebar" ${gogDisplaySidebar ? 'checked' : ''} type="checkbox"><span></span>Sidebar</label> </div> <div class="formbuilder-checkbox-inline"> <label for="checkbox-group-gogDisplayCart-1" class="kc-toggle"> <input name="checkbox-group-gogDisplayCart[]" access="false" id="checkbox-group-gogDisplayCart-1" value="gogDisplayCart" ${gogDisplayCart ? 'checked' : ''} type="checkbox"><span></span>Cart</label> </div> </div> </div> </div> <div class="checkbox-group formbuilder-checkbox-group form-group field-checkbox-group-saved"> <h3>Toggle Buttons:</h3> <div class="checkbox-group-saved"> ${buttonSet.map((button, index) => ` <div class="formbuilder-checkbox"> <input name="checkbox-group-saved[]" id="checkbox-group-saved-${index}" type="checkbox" value="${button.title}" ${savedButtonSet.some(item => item.title.includes(button.title)) ? 'checked' : ''} ${savedButtonSet.some(item => item.title.includes(button.title)) ? 'checked="checked"' : ''}> <label for="checkbox-group-saved-${index}">${button.title}</label> </div> `).join('')} </div> </div> <div class="formbuilder-button form-group field-button-save-config"> <button type="button" class="btn-success btn" name="button-save-config" access="false" style="success" id="button-save-config">Save</button> </div> </div> `); function applyButtonSettings() { const enabledButtonSet = []; [...document.querySelectorAll('[id^="button-toggle-"]')].forEach((checkbox, index) => { if (checkbox.checked) { enabledButtonSet.push(index); } }); } function openSettingsGui() { Gui.open(() => { Gui.smartEvent("button-save-config", (data) => { const buttons = Gui.getData("checkbox-group-saved"); const steamDisplay = Gui.getData("checkbox-group-steamDisplay"); const gogDisplay = Gui.getData("checkbox-group-gogDisplay"); GM_setValue("enabledButtonSet", buttonSet.filter(item => buttons.includes(item.title))); GM_setValue("steamDisplaySidebar", steamDisplay.includes("steamDisplaySidebar")); GM_setValue("steamDisplayCart", steamDisplay.includes("steamDisplayCart")); GM_setValue("gogDisplaySidebar", gogDisplay.includes("gogDisplaySidebar")); GM_setValue("gogDisplayCart", gogDisplay.includes("gogDisplayCart")); // Gui.saveConfig(); location.reload(); // Reload the page to reflect changes }); Gui.loadConfig(); }); } var appName = ""; switch(siteSetR###lt) { case "GOG": appName = document.getElementsByClassName("productcard-basics__title")[0].textContent; appName = appName.trim().replace(/[^a-zA-Z0-9' ]/g, ''); if (gogDisplayCart) { savedButtonSet.forEach((el) => { $("button.cart-button")[0].parentElement.parentElement.append(furnishGOG(el.url+appName, el.title)) }) } if (gogDisplaySidebar) { /* <div class="table__row details__row"> <div class="details__category table__row-label">Genre:</div> <div class="details__content table__row-content"> <a href="" class="details__link ng-scope">Role-playing</a> </div> </div> */ const tableRow = document.createElement('div'); tableRow.classList.add('table__row', 'details__row'); // Create the category div const categoryDiv = document.createElement('div'); categoryDiv.classList.add('details__category', 'table__row-label'); categoryDiv.textContent = 'Search for ' + appName + ':'; // Create the content div const contentDiv = document.createElement('div'); contentDiv.classList.add('details__content', 'table__row-content'); savedButtonSet.forEach((el, index) => { const anchor = document.createElement('a'); anchor.href = el.url+appName; // You can set the href attribute value as needed anchor.target = '_blank'; anchor.classList.add('details__link', 'ng-scope'); anchor.textContent = el.title; contentDiv.appendChild(anchor); if (index < savedButtonSet.length - 1) { const lineBreak = document.createElement('br'); contentDiv.appendChild(lineBreak); // const comma = document.createTextNode(', '); // contentDiv.appendChild(comma); } }) tableRow.appendChild(categoryDiv); tableRow.appendChild(contentDiv); // Finally, append the entire structure to the desired parent element in the DOM document.querySelector("div.details.table.table--without-border.ng-scope").prepend(tableRow); // Or append to a specific element } break; case "Steam": appName = document.getElementsByClassName("apphub_AppName")[0].textContent; appName = appName.trim().replace(/[^a-zA-Z0-9' ]/g, ''); // $(".game_purchase_action_bg:first").css({"height": "32px"}); remove if (steamDisplayCart) { $(".game_purchase_action_bg:first").css({ "height": "50px", "max-width": "500px", "text-wrap": "wrap" }); } ////////// if (steamDisplaySidebar) { // Sidebar for Steam // $(".glance_ctn_responsive_left:first").append(' <div class="dev_row"><div class="subtitle column"><br></div></div><hr><br>'); $(".block.responsive_apppage_details_left:first").parent().prepend(' <div class="block responsive_apppage_details_left" ><div><div style="color: #8f98a0;margin-bottom: 6px;">Search for ' + appName +': </div></div> '); // Create and insert the style element for custom CSS rules var style = document.createElement('style'); style.innerHTML = ` .pirate_row { display: flex; } .pirate_row, .pirate_row .column { white-space: normal !important; } .pirate_row .column { color: #556772; } .pirate_row .subtitle { text-transform: uppercase; font-size: 10px; padding-right: 10px; min-width: 120px; } .pirate_row .summary { overflow: hidden; text-overflow: ellipsis; color: #556772; } .pirate_row:hover { background-color: #333; /* Dark grey background on hover */ } `; document.head.appendChild(style); } //////////// if (steamDisplaySidebar) { savedButtonSet.forEach((el) => { $(".block.responsive_apppage_details_left:first").append(furnishSteamSidebar(el.url+appName + el.urlSpecial, el.title, appName)) // $(".glance_ctn_responsive_left:first").append(furnishSteamSidebar(el.url+appName + el.urlSpecial, el.title, appName)) }) } if (steamDisplayCart) { savedButtonSet.forEach((el) => { $(".game_purchase_action_bg:first").append(furnishSteam(el.url+appName + el.urlSpecial, el.title)) }) } break; case "IGG": appName = $(".uk-article-title")[0].innerHTML.replace(" Free Download",""); appName = appName.trim().replace(/[^a-zA-Z0-9 ]/g, ''); savedButtonSet.forEach((el) => { $(".uk-article-meta")[0].append(" -- ") $(".uk-article-meta")[0].append(furnishIGG(el.url+appName, el.title)) }) break; } function furnishGOG(href, innerHTML) { let element = document.createElement("a"); element.target= "_blank"; element.style = "margin: 5px 0 5px 0 !important; padding: 5px 10px 5px 10px;"; element.classList.add("button"); //element.classList.add("button--small"); element.classList.add("button--big"); element.classList.add("cart-button"); element.classList.add("ng-scope"); element.href = href; element.innerHTML= innerHTML; return element; } function furnishSteam(href, innerHTML) { let element = document.createElement("a"); element.target= "_blank"; element.style = "margin-left: 10px; padding-right: 10px;"; element.href = href; element.innerHTML= innerHTML; return element; } function furnishSteamSidebar(searchUrl, appName, gameName) { // Create the main container div var devRowDiv = document.createElement('div'); devRowDiv.className = 'dev_row pirate_row'; // Create the subtitle div var subtitleDiv = document.createElement('div'); subtitleDiv.className = 'subtitle column'; subtitleDiv.innerHTML = appName + ':'; // Create the summary div var summaryDiv = document.createElement('div'); summaryDiv.className = 'summary column'; // Create the anchor element var anchor = document.createElement('a'); anchor.href = searchUrl; anchor.target = '_blank'; // anchor.innerHTML = 'Search ' + appName + ' for ' + gameName; anchor.innerHTML = appName; // Append the anchor to the summary div summaryDiv.appendChild(anchor); // Append the subtitle and summary divs to the main container div devRowDiv.appendChild(subtitleDiv); devRowDiv.appendChild(summaryDiv); // Return the created element return devRowDiv; } function furnishIGG(href, innerHTML) { let element = document.createElement("a"); element.target= "_blank"; element.href = href; element.innerHTML= innerHTML; return element; } try{ GM_registerMenuCommand = GM_registerMenuCommand || this.GM_registerMenuCommand; }catch(e){ GM_registerMenuCommand = false; } if(p !== "true"){ if(GM_registerMenuCommand){ GM_registerMenuCommand('Show unsafe websites', function(){ if(confirm('Are you sure you want to show possibly unsafe websites?\n'+ '(It can be hidden later with this menu)')){ GM_setValue("enableUnsafeButtonSet", "true"); GM_deleteValue("enabledButtonSet"); location.reload(); } }); } } else if (GM_registerMenuCommand) { GM_registerMenuCommand('Hide unsafe websites', function(){ if(confirm('Are you sure you want to hide possibly unsafe websites?\n'+ '(It can be shown later with this menu)')){ GM_deleteValue("enableUnsafeButtonSet"); GM_deleteValue("enabledButtonSet"); location.reload(); } }); } if (GM_registerMenuCommand) { GM_registerMenuCommand('Open Settings GUI', function(){ openSettingsGui(); }); } if (GM_registerMenuCommand) { GM_registerMenuCommand('Reset settings', function(){ GM_deleteValue("enableUnsafeButtonSet"); GM_deleteValue("enabledButtonSet"); GM_deleteValue("steamDisplaySidebar"); GM_deleteValue("steamDisplayCart"); GM_deleteValue("gogDisplaySidebar"); GM_deleteValue("gogDisplayCart"); location.reload(); }); }