- // ==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 siteSetResult = "";
-
- siteSet.forEach((el) => {
- if(!!document.URL.match(el.url)) siteSetResult = 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(siteSetResult) {
- 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();
- });
- }