Steam/GOG Games Links to Free Download Site

Simply adds a pirate link to all games on the GOG store

  1. // ==UserScript==
  2. // @name Steam/GOG Games Links to Free Download Site
  3. // @namespace Kozinc
  4. // @version 0.4.8
  5. // @license MIT
  6. // @description Simply adds a pirate link to all games on the GOG store
  7. // @require https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js
  8. // @match https://www.gog.com/game/*
  9. // @match https://www.gog.com/en/game/*
  10. // @match https://store.steampowered.com/app/*
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM.getValue
  15. // @grant GM.setValue
  16. // @grant GM_deleteValue
  17. // @grant GM_xmlhttpRequest
  18. // @run-at document-load
  19. // ==/UserScript==
  20.  
  21. // Default buttonSet
  22. var buttonSet = [
  23. { url: "https://steamrip.com/?s=", title: "SteamRIP", urlSpecial: "" },
  24. { url: "https://www.ovagames.com/?s=", title: "OVA Games", urlSpecial: "" },
  25. { url: "https://fitgirl-repacks.site/?s=", title: "FitGirl", urlSpecial: "" },
  26. { url: "https://dodi-repacks.site/?s=", title: "DODI", urlSpecial: "" },
  27. { url: "https://gload.to/?s=", title: "Gload", urlSpecial: "" },
  28. { url: "https://search.rlsbb.ru/?s=", title: "Release BB", urlSpecial: "" },
  29. { url: "https://scnlog.me/?s=", title: "SCNLOG", urlSpecial: "" },
  30. { url: "https://cpgrepacks.site/?s=", title: "CPG Repacks", urlSpecial: "" },
  31. { url: "https://www.tiny-repacks.win/?s=", title: "Tiny Repacks", urlSpecial: "" },
  32. { url: "https://g4u.to/en/search/?str=", title: "g4u", urlSpecial: "" },
  33. { url: "https://gog-games.to/?q=", title: "GOG-Games.to", urlSpecial: "" },
  34. ];
  35. var unsafeButtonSet = [
  36. { url: "https://gogunlocked.com/?s=", title: "GOG Unlocked", urlSpecial: "" },
  37. { url: "https://igg-games.com/?s=", title: "IGG", urlSpecial: "" },
  38. { url: "https://pcgamestorrents.com/?s=", title: "PC games Torrent", urlSpecial: "" },
  39. ];
  40.  
  41. var siteSet = [
  42. { url: "https://www.gog.com/game/*", title: "GOG", urlSpecial: "" },
  43. { url: "https://www.gog.com/en/game/*", title: "GOG", urlSpecial: "" },
  44. { url: "https://store.steampowered.com/app/*", title: "Steam", urlSpecial: "" },
  45. // { url: /https:\/\/igg-games.com\/.*.html/, title: "IGG" },
  46. ];
  47.  
  48. /*
  49. * usergui.js -- https://github.com/AugmentedWeb/UserGui/raw/Release-1.0/usergui.js
  50. * v1.0.0
  51. * https://github.com/AugmentedWeb/UserGui
  52. * Apache 2.0 licensed
  53. */
  54.  
  55. class UserGui {
  56. constructor() {
  57. const grantArr = GM_info?.script?.grant;
  58.  
  59. if(typeof grantArr == "object") {
  60. if(!grantArr.includes("GM_xmlhttpRequest")) {
  61. prompt(`${this.#projectName} needs GM_xmlhttpRequest!\n\nPlease add this to your userscript's header...`, "// @grant GM_xmlhttpRequest");
  62. }
  63.  
  64. if(!grantArr.includes("GM_getValue")) {
  65. prompt(`${this.#projectName} needs GM_getValue!\n\nPlease add this to your userscript's header...`, "// @grant GM_getValue");
  66. }
  67.  
  68. if(!grantArr.includes("GM_setValue")) {
  69. prompt(`${this.#projectName} needs GM_setValue!\n\nPlease add this to your userscript's header...`, "// @grant GM_setValue");
  70. }
  71. }
  72. }
  73.  
  74. #projectName = "UserGui";
  75. window = undefined;
  76. document = undefined;
  77. iFrame = undefined;
  78. settings = {
  79. "window" : {
  80. "title" : "No title set",
  81. "name" : "userscript-gui",
  82. "external" : false,
  83. "centered" : false,
  84. "size" : {
  85. "width" : 300,
  86. "height" : 500,
  87. "dynamicSize" : true
  88. }
  89. },
  90. "gui" : {
  91. "centeredItems" : false,
  92. "internal" : {
  93. "darkCloseButton" : false,
  94. "style" : `
  95. body {
  96. background-color: #ffffff;
  97. overflow: hidden;
  98. width: 100% !important;
  99. }
  100.  
  101. form {
  102. padding: 10px;
  103. }
  104.  
  105. #gui {
  106. height: fit-content;
  107. }
  108.  
  109. .rendered-form {
  110. padding: 10px;
  111. }
  112.  
  113. #header {
  114. padding: 10px;
  115. cursor: move;
  116. z-index: 10;
  117. background-color: #2196F3;
  118. color: #fff;
  119. height: fit-content;
  120. }
  121.  
  122. .header-item-container {
  123. display: flex;
  124. justify-content: space-between;
  125. align-items: center;
  126. }
  127.  
  128. .left-title {
  129. font-size: 14px;
  130. font-weight: bold;
  131. padding: 0;
  132. margin: 0;
  133. }
  134.  
  135. #button-close-gui {
  136. vertical-align: middle;
  137. }
  138.  
  139. div .form-group {
  140. margin-bottom: 15px;
  141. }
  142.  
  143. #resizer {
  144. width: 10px;
  145. height: 10px;
  146. cursor: se-resize;
  147. position: absolute;
  148. bottom: 0;
  149. right: 0;
  150. }
  151.  
  152. .formbuilder-button {
  153. width: fit-content;
  154. }
  155. `
  156. },
  157. "external" : {
  158. "popup" : true,
  159. "style" : `
  160. .rendered-form {
  161. padding: 10px;
  162. }
  163. div .form-group {
  164. margin-bottom: 15px;
  165. }
  166. `
  167. }
  168. },
  169. "messages" : {
  170. "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})`)
  171. }
  172. };
  173.  
  174. // This error page will be shown if the user has not added any pages
  175. #errorPage = (title, code) => `
  176. <style>
  177. .error-page {
  178. width: 100%;
  179. height: fit-content;
  180. background-color: black;
  181. display: flex;
  182. justify-content: center;
  183. align-items: center;
  184. text-align: center;
  185. padding: 25px
  186. }
  187. .error-page-text {
  188. font-family: monospace;
  189. font-size: x-large;
  190. color: white;
  191. }
  192. .error-page-tag {
  193. margin-top: 20px;
  194. font-size: 10px;
  195. color: #4a4a4a;
  196. font-style: italic;
  197. margin-bottom: 0px;
  198. }
  199. </style>
  200. <div class="error-page">
  201. <div>
  202. <p class="error-page-text">${title}</p>
  203. <code>${code}</code>
  204. <p class="error-page-tag">${this.#projectName} error message</p>
  205. </div>
  206. </div>`;
  207.  
  208. // The user can add multiple pages to their GUI. The pages are stored in this array.
  209. #guiPages = [
  210. {
  211. "name" : "default_no_content_set",
  212. "content" : this.#errorPage("Content missing", "Gui.setContent(html, tabName);")
  213. }
  214. ];
  215.  
  216. // The userscript manager's xmlHttpRequest is used to bypass CORS limitations (To load Bootstrap)
  217. async #bypassCors(externalFile) {
  218. const res = await new Promise(resolve => {
  219. GM_xmlhttpRequest({
  220. method: "GET",
  221. url: externalFile,
  222. onload: resolve
  223. });
  224. });
  225.  
  226. return res.responseText;
  227. }
  228.  
  229. // Returns one tab (as HTML) for the navigation tabs
  230. #createNavigationTab(page) {
  231. const name = page.name;
  232.  
  233. if(name == undefined) {
  234. console.error(`[${this.#projectName}] Gui.addPage(html, name) <- name missing!`);
  235. return undefined;
  236. } else {
  237. const modifiedName = name.toLowerCase().replaceAll(' ', '').replace(/[^a-zA-Z0-9]/g, '') + Math.floor(Math.random() * 1000000000);
  238.  
  239. const content = page.content;
  240. const indexOnArray = this.#guiPages.map(x => x.name).indexOf(name);
  241. const firstItem = indexOnArray == 0 ? true : false;
  242.  
  243. return {
  244. "listItem" : `
  245. <li class="nav-item" role="presentation">
  246. <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>
  247. </li>
  248. `,
  249. "panelItem" : `
  250. <div class="tab-pane ${firstItem ? 'active' : ''}" id="${modifiedName}" role="tabpanel" aria-labelledby="${modifiedName}-tab">${content}</div>
  251. `
  252. };
  253. }
  254. }
  255.  
  256. // Make tabs function without bootstrap.js (CSP might block bootstrap and make the GUI nonfunctional)
  257. #initializeTabs() {
  258. const handleTabClick = e => {
  259. const target = e.target;
  260. const contentID = target.getAttribute("data-bs-target");
  261.  
  262. target.classList.add("active");
  263. this.document.querySelector(contentID).classList.add("active");
  264.  
  265. [...this.document.querySelectorAll(".nav-link")].forEach(tab => {
  266. if(tab != target) {
  267. const contentID = tab.getAttribute("data-bs-target");
  268.  
  269. tab.classList.remove("active");
  270. this.document.querySelector(contentID).classList.remove("active");
  271. }
  272. });
  273. }
  274.  
  275. [...this.document.querySelectorAll(".nav-link")].forEach(tab => {
  276. tab.addEventListener("click", handleTabClick);
  277. });
  278. }
  279.  
  280. // Will determine if a navbar is needed, returns either a regular GUI, or a GUI with a navbar
  281. #getContent() {
  282. // Only one page has been set, no navigation tabs will be created
  283. if(this.#guiPages.length == 1) {
  284. return this.#guiPages[0].content;
  285. }
  286. // Multiple pages has been set, dynamically creating the navigation tabs
  287. else if(this.#guiPages.length > 1) {
  288. const tabs = (list, panels) => `
  289. <ul class="nav nav-tabs" id="userscript-tab" role="tablist">
  290. ${list}
  291. </ul>
  292. <div class="tab-content">
  293. ${panels}
  294. </div>
  295. `;
  296.  
  297. let list = ``;
  298. let panels = ``;
  299.  
  300. this.#guiPages.forEach(page => {
  301. const data = this.#createNavigationTab(page);
  302.  
  303. if(data != undefined) {
  304. list += data.listItem + '\n';
  305. panels += data.panelItem + '\n';
  306. }
  307. });
  308.  
  309. return tabs(list, panels);
  310. }
  311. }
  312.  
  313. // Returns the GUI's whole document as string
  314. async #createDocument() {
  315. const bootstrapStyling = await this.#bypassCors("https://raw.githubusercontent.com/AugmentedWeb/UserGui/Release-1.0/resources/bootstrap.css");
  316.  
  317. const externalDocument = `
  318. <!DOCTYPE html>
  319. <html>
  320. <head>
  321. <title>${this.settings.window.title}</title>
  322. <style>
  323. ${bootstrapStyling}
  324. ${this.settings.gui.external.style}
  325. ${
  326. this.settings.gui.centeredItems
  327. ? `.form-group {
  328. display: flex;
  329. justify-content: center;
  330. }`
  331. : ""
  332. }
  333. </style>
  334. </head>
  335. <body>
  336. ${this.#getContent()}
  337. </body>
  338. </html>
  339. `;
  340.  
  341. const internalDocument = `
  342. <!doctype html>
  343. <html lang="en">
  344. <head>
  345. <style>
  346. ${bootstrapStyling}
  347. ${this.settings.gui.internal.style}
  348. ${
  349. this.settings.gui.centeredItems
  350. ? `.form-group {
  351. display: flex;
  352. justify-content: center;
  353. }`
  354. : ""
  355. }
  356. </style>
  357. </head>
  358. <body>
  359. <div id="gui">
  360. <div id="header">
  361. <div class="header-item-container">
  362. <h1 class="left-title">${this.settings.window.title}</h1>
  363. <div class="right-buttons">
  364. <button type="button" class="${this.settings.gui.internal.darkCloseButton ? "btn-close" : "btn-close btn-close-white"}" aria-label="Close" id="button-close-gui"></button>
  365. </div>
  366. </div>
  367. </div>
  368. <div id="content">
  369. ${this.#getContent()}
  370. </div>
  371. <div id="resizer"></div>
  372. </div>
  373. </body>
  374. </html>
  375. `;
  376.  
  377. if(this.settings.window.external) {
  378. return externalDocument;
  379. } else {
  380. return internalDocument;
  381. }
  382. }
  383.  
  384. // The user will use this function to add a page to their GUI, with their own HTML (Bootstrap 5)
  385. addPage(tabName, htmlString) {
  386. if(this.#guiPages[0].name == "default_no_content_set") {
  387. this.#guiPages = [];
  388. }
  389.  
  390. this.#guiPages.push({
  391. "name" : tabName,
  392. "content" : htmlString
  393. });
  394. }
  395.  
  396. #getCenterScreenPosition() {
  397. const guiWidth = this.settings.window.size.width;
  398. const guiHeight = this.settings.window.size.height;
  399.  
  400. const x = (screen.width - guiWidth) / 2;
  401. const y = (screen.height - guiHeight) / 2;
  402.  
  403. return { "x" : x, "y": y };
  404. }
  405.  
  406. #getCenterWindowPosition() {
  407. const guiWidth = this.settings.window.size.width;
  408. const guiHeight = this.settings.window.size.height;
  409.  
  410. const x = (window.innerWidth - guiWidth) / 2;
  411. const y = (window.innerHeight - guiHeight) / 2;
  412.  
  413. return { "x" : x, "y": y };
  414. }
  415.  
  416. #initializeInternalGuiEvents(iFrame) {
  417. // - The code below will consist mostly of drag and resize implementations
  418. // - iFrame window <-> Main window interaction requires these to be done
  419. // - Basically, iFrame document's event listeners make the whole iFrame move on the main window
  420.  
  421. // Sets the iFrame's size
  422. function setFrameSize(x, y) {
  423. iFrame.style.width = `${x}px`;
  424. iFrame.style.height = `${y}px`;
  425. }
  426.  
  427. // Gets the iFrame's size
  428. function getFrameSize() {
  429. const frameBounds = iFrame.getBoundingClientRect();
  430.  
  431. return { "width" : frameBounds.width, "height" : frameBounds.height };
  432. }
  433.  
  434. // Sets the iFrame's position relative to the main window's document
  435. function setFramePos(x, y) {
  436. iFrame.style.left = `${x}px`;
  437. iFrame.style.top = `${y}px`;
  438. }
  439.  
  440. // Gets the iFrame's position relative to the main document
  441. function getFramePos() {
  442. const frameBounds = iFrame.getBoundingClientRect();
  443.  
  444. return { "x": frameBounds.x, "y" : frameBounds.y };
  445. }
  446.  
  447. // Gets the frame body's offsetHeight
  448. function getInnerFrameSize() {
  449. const innerFrameElem = iFrame.contentDocument.querySelector("#gui");
  450.  
  451. return { "x": innerFrameElem.offsetWidth, "y" : innerFrameElem.offsetHeight };
  452. }
  453.  
  454. // Sets the frame's size to the innerframe's size
  455. const adjustFrameSize = () => {
  456. const innerFrameSize = getInnerFrameSize();
  457.  
  458. setFrameSize(innerFrameSize.x, innerFrameSize.y);
  459. }
  460.  
  461. // Variables for draggable header
  462. let dragging = false,
  463. dragStartPos = { "x" : 0, "y" : 0 };
  464.  
  465. // Variables for resizer
  466. let resizing = false,
  467. mousePos = { "x" : undefined, "y" : undefined },
  468. lastFrame;
  469.  
  470. function handleResize(isInsideFrame, e) {
  471. if(mousePos.x == undefined && mousePos.y == undefined) {
  472. mousePos.x = e.clientX;
  473. mousePos.y = e.clientY;
  474.  
  475. lastFrame = isInsideFrame;
  476. }
  477.  
  478. const deltaX = mousePos.x - e.clientX,
  479. deltaY = mousePos.y - e.clientY;
  480.  
  481. const frameSize = getFrameSize();
  482. const allowedSize = frameSize.width - deltaX > 160 && frameSize.height - deltaY > 90;
  483.  
  484. if(isInsideFrame == lastFrame && allowedSize) {
  485. setFrameSize(frameSize.width - deltaX, frameSize.height - deltaY);
  486. }
  487.  
  488. mousePos.x = e.clientX;
  489. mousePos.y = e.clientY;
  490.  
  491. lastFrame = isInsideFrame;
  492. }
  493.  
  494. function handleDrag(isInsideFrame, e) {
  495. const bR = iFrame.getBoundingClientRect();
  496.  
  497. const windowWidth = window.innerWidth,
  498. windowHeight = window.innerHeight;
  499.  
  500. let x, y;
  501.  
  502. if(isInsideFrame) {
  503. x = getFramePos().x += e.clientX - dragStartPos.x;
  504. y = getFramePos().y += e.clientY - dragStartPos.y;
  505. } else {
  506. x = e.clientX - dragStartPos.x;
  507. y = e.clientY - dragStartPos.y;
  508. }
  509.  
  510. // Check out of bounds: left
  511. if(x <= 0) {
  512. x = 0
  513. }
  514.  
  515. // Check out of bounds: right
  516. if(x + bR.width >= windowWidth) {
  517. x = windowWidth - bR.width;
  518. }
  519.  
  520. // Check out of bounds: top
  521. if(y <= 0) {
  522. y = 0;
  523. }
  524.  
  525. // Check out of bounds: bottom
  526. if(y + bR.height >= windowHeight) {
  527. y = windowHeight - bR.height;
  528. }
  529.  
  530. setFramePos(x, y);
  531. }
  532.  
  533. // Dragging start (iFrame)
  534. this.document.querySelector("#header").addEventListener('mousedown', e => {
  535. e.preventDefault();
  536.  
  537. dragging = true;
  538.  
  539. dragStartPos.x = e.clientX;
  540. dragStartPos.y = e.clientY;
  541. });
  542.  
  543. // Resizing start
  544. this.document.querySelector("#resizer").addEventListener('mousedown', e => {
  545. e.preventDefault();
  546.  
  547. resizing = true;
  548. });
  549.  
  550. // While dragging or resizing (iFrame)
  551. this.document.addEventListener('mousemove', e => {
  552. if(dragging)
  553. handleDrag(true, e);
  554.  
  555. if(resizing)
  556. handleResize(true, e);
  557. });
  558.  
  559. // While dragging or resizing (Main window)
  560. document.addEventListener('mousemove', e => {
  561. if(dragging)
  562. handleDrag(false, e);
  563.  
  564. if(resizing)
  565. handleResize(false, e);
  566. });
  567.  
  568. // Stop dragging and resizing (iFrame)
  569. this.document.addEventListener('mouseup', e => {
  570. e.preventDefault();
  571.  
  572. dragging = false;
  573. resizing = false;
  574. });
  575.  
  576. // Stop dragging and resizing (Main window)
  577. document.addEventListener('mouseup', e => {
  578. dragging = false;
  579. resizing = false;
  580. });
  581.  
  582. // Listener for the close button, closes the internal GUI
  583. this.document.querySelector("#button-close-gui").addEventListener('click', e => {
  584. e.preventDefault();
  585.  
  586. this.close();
  587. });
  588.  
  589. const guiObserver = new MutationObserver(adjustFrameSize);
  590. const guiElement = this.document.querySelector("#gui");
  591.  
  592. guiObserver.observe(guiElement, {
  593. childList: true,
  594. subtree: true,
  595. attributes: true
  596. });
  597.  
  598. adjustFrameSize();
  599. }
  600.  
  601. async #openExternalGui(readyFunction) {
  602. const noWindow = this.window?.closed;
  603.  
  604. if(noWindow || this.window == undefined) {
  605. let pos = "";
  606. let windowSettings = "";
  607.  
  608. if(this.settings.window.centered && this.settings.gui.external.popup) {
  609. const centerPos = this.#getCenterScreenPosition();
  610. pos = `left=${centerPos.x}, top=${centerPos.y}`;
  611. }
  612.  
  613. if(this.settings.gui.external.popup) {
  614. windowSettings = `width=${this.settings.window.size.width}, height=${this.settings.window.size.height}, ${pos}`;
  615. }
  616.  
  617. // Create a new window for the GUI
  618. this.window = window.open("", this.settings.windowName, windowSettings);
  619.  
  620. if(!this.window) {
  621. this.settings.messages.blockedPopups();
  622. return;
  623. }
  624.  
  625. // Write the document to the new window
  626. this.window.document.open();
  627. this.window.document.write(await this.#createDocument());
  628. this.window.document.close();
  629.  
  630. if(!this.settings.gui.external.popup) {
  631. this.window.document.body.style.width = `${this.settings.window.size.width}px`;
  632.  
  633. if(this.settings.window.centered) {
  634. const centerPos = this.#getCenterScreenPosition();
  635.  
  636. this.window.document.body.style.position = "absolute";
  637. this.window.document.body.style.left = `${centerPos.x}px`;
  638. this.window.document.body.style.top = `${centerPos.y}px`;
  639. }
  640. }
  641.  
  642. // Dynamic sizing (only height & window.outerHeight no longer works on some browsers...)
  643. this.window.resizeTo(
  644. this.settings.window.size.width,
  645. this.settings.window.size.dynamicSize
  646. ? this.window.document.body.offsetHeight + (this.window.outerHeight - this.window.innerHeight)
  647. : this.settings.window.size.height
  648. );
  649.  
  650. this.document = this.window.document;
  651.  
  652. this.#initializeTabs();
  653.  
  654. // Call user's function
  655. if(typeof readyFunction == "function") {
  656. readyFunction();
  657. }
  658.  
  659. window.onbeforeunload = () => {
  660. // Close the GUI if parent window closes
  661. this.close();
  662. }
  663. }
  664.  
  665. else {
  666. // Window was already opened, bring the window back to focus
  667. this.window.focus();
  668. }
  669. }
  670.  
  671. async #openInternalGui(readyFunction) {
  672. if(this.iFrame) {
  673. return;
  674. }
  675.  
  676. const fadeInSpeedMs = 250;
  677.  
  678. let left = 0, top = 0;
  679.  
  680. if(this.settings.window.centered) {
  681. const centerPos = this.#getCenterWindowPosition();
  682.  
  683. left = centerPos.x;
  684. top = centerPos.y;
  685. }
  686.  
  687. const iframe = document.createElement("iframe");
  688. iframe.srcdoc = await this.#createDocument();
  689. iframe.style = `
  690. position: fixed;
  691. top: ${top}px;
  692. left: ${left}px;
  693. width: ${this.settings.window.size.width};
  694. height: ${this.settings.window.size.height};
  695. border: 0;
  696. opacity: 0;
  697. transition: all ${fadeInSpeedMs/1000}s;
  698. border-radius: 5px;
  699. box-shadow: rgb(0 0 0 / 6%) 10px 10px 10px;
  700. z-index: 2147483647;
  701. `;
  702.  
  703. const waitForBody = setInterval(() => {
  704. if(document?.body) {
  705. clearInterval(waitForBody);
  706.  
  707. // Prepend the GUI to the document's body
  708. document.body.prepend(iframe);
  709.  
  710. iframe.contentWindow.onload = () => {
  711. // Fade-in implementation
  712. setTimeout(() => iframe.style["opacity"] = "1", fadeInSpeedMs/2);
  713. setTimeout(() => iframe.style["transition"] = "none", fadeInSpeedMs + 500);
  714.  
  715. this.window = iframe.contentWindow;
  716. this.document = iframe.contentDocument;
  717. this.iFrame = iframe;
  718.  
  719. this.#initializeInternalGuiEvents(iframe);
  720. this.#initializeTabs();
  721.  
  722. readyFunction();
  723. }
  724. }
  725. }, 100);
  726. }
  727.  
  728. // Determines if the window is to be opened externally or internally
  729. open(readyFunction) {
  730. if(this.settings.window.external) {
  731. this.#openExternalGui(readyFunction);
  732. } else {
  733. this.#openInternalGui(readyFunction);
  734. }
  735. }
  736.  
  737. // Closes the GUI if it exists
  738. close() {
  739. if(this.settings.window.external) {
  740. if(this.window) {
  741. this.window.close();
  742. }
  743. } else {
  744. if(this.iFrame) {
  745. this.iFrame.remove();
  746. this.iFrame = undefined;
  747. }
  748. }
  749. }
  750.  
  751. saveConfig() {
  752. let config = [];
  753.  
  754. if(this.document) {
  755. [...this.document.querySelectorAll(".form-group")].forEach(elem => {
  756. const inputElem = elem.querySelector("[name]");
  757.  
  758. const name = inputElem.getAttribute("name"),
  759. data = this.getData(name);
  760.  
  761. if(data) {
  762. config.push({ "name" : name, "value" : data });
  763. }
  764. });
  765. }
  766.  
  767. GM_setValue("config", config);
  768. }
  769.  
  770. loadConfig() {
  771. const config = this.getConfig();
  772.  
  773. if(this.document && config) {
  774. config.forEach(elemConfig => {
  775. this.setData(elemConfig.name, elemConfig.value);
  776. })
  777. }
  778. }
  779.  
  780. getConfig() {
  781. return GM_getValue("config");
  782. }
  783.  
  784. resetConfig() {
  785. const config = this.getConfig();
  786.  
  787. if(config) {
  788. GM_setValue("config", []);
  789. }
  790. }
  791.  
  792. dispatchFormEvent(name) {
  793. const type = name.split("-")[0].toLowerCase();
  794. const properties = this.#typeProperties.find(x => type == x.type);
  795. const event = new Event(properties.event);
  796.  
  797. const field = this.document.querySelector(`.field-${name}`);
  798. field.dispatchEvent(event);
  799. }
  800.  
  801. setPrimaryColor(hex) {
  802. const styles = `
  803. #header {
  804. background-color: ${hex} !important;
  805. }
  806. .nav-link {
  807. color: ${hex} !important;
  808. }
  809. .text-primary {
  810. color: ${hex} !important;
  811. }
  812. `;
  813.  
  814. const styleSheet = document.createElement("style")
  815. styleSheet.innerText = styles;
  816. this.document.head.appendChild(styleSheet);
  817. }
  818.  
  819. // Creates an event listener a GUI element
  820. event(name, event, eventFunction) {
  821. this.document.querySelector(`.field-${name}`).addEventListener(event, eventFunction);
  822. }
  823.  
  824. // Disables a GUI element
  825. disable(name) {
  826. [...this.document.querySelector(`.field-${name}`).children].forEach(childElem => {
  827. childElem.setAttribute("disabled", "true");
  828. });
  829. }
  830.  
  831. // Enables a GUI element
  832. enable(name) {
  833. [...this.document.querySelector(`.field-${name}`).children].forEach(childElem => {
  834. if(childElem.getAttribute("disabled")) {
  835. childElem.removeAttribute("disabled");
  836. }
  837. });
  838. }
  839.  
  840. // Gets data from types: TEXT FIELD, TEXTAREA, DATE FIELD & NUMBER
  841. getValue(name) {
  842. return this.document.querySelector(`.field-${name}`).querySelector(`[id=${name}]`).value;
  843. }
  844.  
  845. // Sets data to types: TEXT FIELD, TEXT AREA, DATE FIELD & NUMBER
  846. setValue(name, newValue) {
  847. this.document.querySelector(`.field-${name}`).querySelector(`[id=${name}]`).value = newValue;
  848.  
  849. this.dispatchFormEvent(name);
  850. }
  851.  
  852. // Gets data from types: RADIO GROUP
  853. getSelection(name) {
  854. return this.document.querySelector(`.field-${name}`).querySelector(`input[name=${name}]:checked`).value;
  855. }
  856.  
  857. // Sets data to types: RADIO GROUP
  858. setSelection(name, newOptionsValue) {
  859. this.document.querySelector(`.field-${name}`).querySelector(`input[value=${newOptionsValue}]`).checked = true;
  860.  
  861. this.dispatchFormEvent(name);
  862. }
  863.  
  864. // Gets data from types: CHECKBOX GROUP
  865. getChecked(name) {
  866. return [...this.document.querySelector(`.field-${name}`).querySelectorAll(`input[name*=${name}]:checked`)]
  867. .map(checkbox => checkbox.value);
  868. }
  869.  
  870. // Sets data to types: CHECKBOX GROUP
  871. setChecked(name, checkedArr) {
  872. const checkboxes = [...this.document.querySelector(`.field-${name}`).querySelectorAll(`input[name*=${name}]`)]
  873.  
  874. checkboxes.forEach(checkbox => {
  875. if(checkedArr.includes(checkbox.value)) {
  876. checkbox.checked = true;
  877. }
  878. });
  879.  
  880. this.dispatchFormEvent(name);
  881. }
  882.  
  883. // Gets data from types: FILE UPLOAD
  884. getFiles(name) {
  885. return this.document.querySelector(`.field-${name}`).querySelector(`input[id=${name}]`).files;
  886. }
  887.  
  888. // Gets data from types: SELECT
  889. getOption(name) {
  890. const selectedArr = [...this.document.querySelector(`.field-${name} #${name}`).selectedOptions].map(({value}) => value);
  891.  
  892. return selectedArr.length == 1 ? selectedArr[0] : selectedArr;
  893. }
  894.  
  895. // Sets data to types: SELECT
  896. setOption(name, newOptionsValue) {
  897. if(typeof newOptionsValue == 'object') {
  898. newOptionsValue.forEach(optionVal => {
  899. this.document.querySelector(`.field-${name}`).querySelector(`option[value=${optionVal}]`).selected = true;
  900. });
  901. } else {
  902. this.document.querySelector(`.field-${name}`).querySelector(`option[value=${newOptionsValue}]`).selected = true;
  903. }
  904.  
  905. this.dispatchFormEvent(name);
  906. }
  907.  
  908. #typeProperties = [
  909. {
  910. "type": "button",
  911. "event": "click",
  912. "function": {
  913. "get" : null,
  914. "set" : null
  915. }
  916. },
  917. {
  918. "type": "radio",
  919. "event": "change",
  920. "function": {
  921. "get" : n => this.getSelection(n),
  922. "set" : (n, nV) => this.setSelection(n, nV)
  923. }
  924. },
  925. {
  926. "type": "checkbox",
  927. "event": "change",
  928. "function": {
  929. "get" : n => this.getChecked(n),
  930. "set" : (n, nV) => this.setChecked(n, nV)
  931. }
  932. },
  933. {
  934. "type": "date",
  935. "event": "change",
  936. "function": {
  937. "get" : n => this.getValue(n),
  938. "set" : (n, nV) => this.setValue(n, nV)
  939. }
  940. },
  941. {
  942. "type": "file",
  943. "event": "change",
  944. "function": {
  945. "get" : n => this.getFiles(n),
  946. "set" : null
  947. }
  948. },
  949. {
  950. "type": "number",
  951. "event": "input",
  952. "function": {
  953. "get" : n => this.getValue(n),
  954. "set" : (n, nV) => this.setValue(n, nV)
  955. }
  956. },
  957. {
  958. "type": "select",
  959. "event": "change",
  960. "function": {
  961. "get" : n => this.getOption(n),
  962. "set" : (n, nV) => this.setOption(n, nV)
  963. }
  964. },
  965. {
  966. "type": "text",
  967. "event": "input",
  968. "function": {
  969. "get" : n => this.getValue(n),
  970. "set" : (n, nV) => this.setValue(n, nV)
  971. }
  972. },
  973. {
  974. "type": "textarea",
  975. "event": "input",
  976. "function": {
  977. "get" : n => this.getValue(n),
  978. "set" : (n, nV) => this.setValue(n, nV)
  979. }
  980. },
  981. ];
  982.  
  983. // The same as the event() function, but automatically determines the best listener type for the element
  984. // (e.g. button -> listen for "click", textarea -> listen for "input")
  985. smartEvent(name, eventFunction) {
  986. if(name.includes("-")) {
  987. const type = name.split("-")[0].toLowerCase();
  988. const properties = this.#typeProperties.find(x => type == x.type);
  989.  
  990. if(typeof properties == "object") {
  991. this.event(name, properties.event, eventFunction);
  992.  
  993. } else {
  994. console.warn(`${this.#projectName}'s smartEvent function did not find any matches for the type "${type}". The event could not be made.`);
  995. }
  996.  
  997. } else {
  998. console.warn(`The input name "${name}" is invalid for ${this.#projectName}'s smartEvent. The event could not be made.`);
  999. }
  1000. }
  1001.  
  1002. // Will automatically determine the suitable function for data retrivial
  1003. // (e.g. file select -> use getFiles() function)
  1004. getData(name) {
  1005. if(name.includes("-")) {
  1006. const type = name.split("-")[0].toLowerCase();
  1007. const properties = this.#typeProperties.find(x => type == x.type);
  1008.  
  1009. if(typeof properties == "object") {
  1010. const getFunction = properties.function.get;
  1011.  
  1012. if(typeof getFunction == "function") {
  1013. return getFunction(name);
  1014.  
  1015. } else {
  1016. console.error(`${this.#projectName}'s getData function can't be used for the type "${type}". The data can't be taken.`);
  1017. }
  1018.  
  1019. } else {
  1020. console.warn(`${this.#projectName}'s getData function did not find any matches for the type "${type}". The event could not be made.`);
  1021. }
  1022.  
  1023. } else {
  1024. console.warn(`The input name "${name}" is invalid for ${this.#projectName}'s getData function. The event could not be made.`);
  1025. }
  1026. }
  1027.  
  1028. // Will automatically determine the suitable function for data retrivial (e.g. checkbox -> use setChecked() function)
  1029. setData(name, newData) {
  1030. if(name.includes("-")) {
  1031. const type = name.split("-")[0].toLowerCase();
  1032. const properties = this.#typeProperties.find(x => type == x.type);
  1033.  
  1034. if(typeof properties == "object") {
  1035. const setFunction = properties.function.set;
  1036.  
  1037. if(typeof setFunction == "function") {
  1038. return setFunction(name, newData);
  1039.  
  1040. } else {
  1041. console.error(`${this.#projectName}'s setData function can't be used for the type "${type}". The data can't be taken.`);
  1042. }
  1043.  
  1044. } else {
  1045. console.warn(`${this.#projectName}'s setData function did not find any matches for the type "${type}". The event could not be made.`);
  1046. }
  1047.  
  1048. } else {
  1049. console.warn(`The input name "${name}" is invalid for ${this.#projectName}'s setData function. The event could not be made.`);
  1050. }
  1051. }
  1052. };
  1053.  
  1054. const Gui = new UserGui;
  1055. Gui.settings.window.title = "Pirate Games Links Settings";
  1056. Gui.settings.window.centered = true;
  1057.  
  1058. var p = GM_getValue("enableUnsafeButtonSet", null);
  1059. if(p === "true") {
  1060. // unsafeButtonSet
  1061. buttonSet = [...buttonSet, ...unsafeButtonSet];
  1062. }
  1063.  
  1064. var steamDisplaySidebar = GM_getValue("steamDisplaySidebar", true);
  1065. var steamDisplayCart = GM_getValue("steamDisplayCart", false);
  1066.  
  1067.  
  1068. var gogDisplaySidebar = GM_getValue("gogDisplaySidebar", true);
  1069. var gogDisplayCart = GM_getValue("gogDisplayCart", false);
  1070.  
  1071. var siteSetResult = "";
  1072.  
  1073. siteSet.forEach((el) => {
  1074. if(!!document.URL.match(el.url)) siteSetResult = el.title;
  1075. })
  1076.  
  1077. // Load saved buttonSet preference
  1078. let savedButtonSet = GM_getValue("enabledButtonSet", []);
  1079. if(savedButtonSet.length === 0) {
  1080. savedButtonSet = buttonSet;
  1081. }
  1082.  
  1083. Gui.addPage("Settings", `
  1084. <div class="rendered-form">
  1085. <div class="">
  1086. <h2 access="false" class="text-primary" id="control-274549">Button Settings</h2>
  1087. </div>
  1088. <div class="checkbox-group formbuilder-checkbox-group form-group field-checkbox-group-steamDisplay">
  1089. <div class="formbuilder-checkbox-group form-group field-checkbox-group-steamDisplay">
  1090. <label for="checkbox-group-steamDisplay" class="formbuilder-checkbox-group-label">Steam display:</label>
  1091. <div class="checkbox-group-steamDisplay">
  1092. <div class="formbuilder-checkbox-inline">
  1093. <label for="checkbox-group-steamDisplay-0" class="kc-toggle">
  1094. <input name="checkbox-group-steamDisplay[]" access="false" id="checkbox-group-steamDisplay-0" value="steamDisplaySidebar" ${steamDisplaySidebar ? 'checked' : ''} type="checkbox"><span></span>Sidebar</label>
  1095. </div>
  1096. <div class="formbuilder-checkbox-inline">
  1097. <label for="checkbox-group-steamDisplayCart-1" class="kc-toggle">
  1098. <input name="checkbox-group-steamDisplayCart[]" access="false" id="checkbox-group-steamDisplayCart-1" value="steamDisplayCart" ${steamDisplayCart ? 'checked' : ''} type="checkbox"><span></span>Cart</label>
  1099. </div>
  1100. </div>
  1101. </div>
  1102. </div>
  1103. <div class="checkbox-group formbuilder-checkbox-group form-group field-checkbox-group-gogDisplay">
  1104. <div class="formbuilder-checkbox-group form-group field-checkbox-group-gogDisplay">
  1105. <label for="checkbox-group-gogDisplay" class="formbuilder-checkbox-group-label">GOG display:</label>
  1106. <div class="checkbox-group-gogDisplay">
  1107. <div class="formbuilder-checkbox-inline">
  1108. <label for="checkbox-group-gogDisplay-0" class="kc-toggle">
  1109. <input name="checkbox-group-gogDisplay[]" access="false" id="checkbox-group-gogDisplay-0" value="gogDisplaySidebar" ${gogDisplaySidebar ? 'checked' : ''} type="checkbox"><span></span>Sidebar</label>
  1110. </div>
  1111. <div class="formbuilder-checkbox-inline">
  1112. <label for="checkbox-group-gogDisplayCart-1" class="kc-toggle">
  1113. <input name="checkbox-group-gogDisplayCart[]" access="false" id="checkbox-group-gogDisplayCart-1" value="gogDisplayCart" ${gogDisplayCart ? 'checked' : ''} type="checkbox"><span></span>Cart</label>
  1114. </div>
  1115. </div>
  1116. </div>
  1117. </div>
  1118. <div class="checkbox-group formbuilder-checkbox-group form-group field-checkbox-group-saved">
  1119. <h3>Toggle Buttons:</h3>
  1120. <div class="checkbox-group-saved">
  1121. ${buttonSet.map((button, index) => `
  1122. <div class="formbuilder-checkbox">
  1123. <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"' : ''}>
  1124. <label for="checkbox-group-saved-${index}">${button.title}</label>
  1125. </div>
  1126. `).join('')}
  1127. </div>
  1128. </div>
  1129. <div class="formbuilder-button form-group field-button-save-config">
  1130. <button type="button" class="btn-success btn" name="button-save-config" access="false" style="success" id="button-save-config">Save</button>
  1131. </div>
  1132. </div>
  1133. `);
  1134.  
  1135. function applyButtonSettings() {
  1136. const enabledButtonSet = [];
  1137. [...document.querySelectorAll('[id^="button-toggle-"]')].forEach((checkbox, index) => {
  1138. if (checkbox.checked) {
  1139. enabledButtonSet.push(index);
  1140. }
  1141. });
  1142.  
  1143. }
  1144.  
  1145. function openSettingsGui() {
  1146. Gui.open(() => {
  1147. Gui.smartEvent("button-save-config", (data) => {
  1148. const buttons = Gui.getData("checkbox-group-saved");
  1149. const steamDisplay = Gui.getData("checkbox-group-steamDisplay");
  1150. const gogDisplay = Gui.getData("checkbox-group-gogDisplay");
  1151. GM_setValue("enabledButtonSet", buttonSet.filter(item => buttons.includes(item.title)));
  1152. GM_setValue("steamDisplaySidebar", steamDisplay.includes("steamDisplaySidebar"));
  1153. GM_setValue("steamDisplayCart", steamDisplay.includes("steamDisplayCart"));
  1154. GM_setValue("gogDisplaySidebar", gogDisplay.includes("gogDisplaySidebar"));
  1155. GM_setValue("gogDisplayCart", gogDisplay.includes("gogDisplayCart"));
  1156. // Gui.saveConfig();
  1157. location.reload(); // Reload the page to reflect changes
  1158. });
  1159. Gui.loadConfig();
  1160. });
  1161. }
  1162.  
  1163.  
  1164.  
  1165.  
  1166. var appName = "";
  1167. switch(siteSetResult) {
  1168. case "GOG":
  1169. appName = document.getElementsByClassName("productcard-basics__title")[0].textContent;
  1170. appName = appName.trim().replace(/[^a-zA-Z0-9' ]/g, '');
  1171. if (gogDisplayCart) {
  1172. savedButtonSet.forEach((el) => {
  1173. $("button.cart-button")[0].parentElement.parentElement.append(furnishGOG(el.url+appName, el.title))
  1174. })
  1175. }
  1176. if (gogDisplaySidebar) {
  1177. /*
  1178. <div class="table__row details__row">
  1179. <div class="details__category table__row-label">Genre:</div>
  1180. <div class="details__content table__row-content">
  1181. <a href="" class="details__link ng-scope">Role-playing</a>
  1182. </div>
  1183. </div>
  1184. */
  1185. const tableRow = document.createElement('div');
  1186. tableRow.classList.add('table__row', 'details__row');
  1187.  
  1188. // Create the category div
  1189. const categoryDiv = document.createElement('div');
  1190. categoryDiv.classList.add('details__category', 'table__row-label');
  1191. categoryDiv.textContent = 'Search for ' + appName + ':';
  1192.  
  1193. // Create the content div
  1194. const contentDiv = document.createElement('div');
  1195. contentDiv.classList.add('details__content', 'table__row-content');
  1196.  
  1197. savedButtonSet.forEach((el, index) => {
  1198. const anchor = document.createElement('a');
  1199. anchor.href = el.url+appName; // You can set the href attribute value as needed
  1200. anchor.target = '_blank';
  1201. anchor.classList.add('details__link', 'ng-scope');
  1202. anchor.textContent = el.title;
  1203. contentDiv.appendChild(anchor);
  1204.  
  1205. if (index < savedButtonSet.length - 1) {
  1206. const lineBreak = document.createElement('br');
  1207. contentDiv.appendChild(lineBreak);
  1208. // const comma = document.createTextNode(', ');
  1209. // contentDiv.appendChild(comma);
  1210. }
  1211. })
  1212. tableRow.appendChild(categoryDiv);
  1213. tableRow.appendChild(contentDiv);
  1214.  
  1215. // Finally, append the entire structure to the desired parent element in the DOM
  1216. document.querySelector("div.details.table.table--without-border.ng-scope").prepend(tableRow); // Or append to a specific element
  1217. }
  1218. break;
  1219. case "Steam":
  1220. appName = document.getElementsByClassName("apphub_AppName")[0].textContent;
  1221. appName = appName.trim().replace(/[^a-zA-Z0-9' ]/g, '');
  1222. // $(".game_purchase_action_bg:first").css({"height": "32px"}); remove
  1223.  
  1224. if (steamDisplayCart) {
  1225. $(".game_purchase_action_bg:first").css({
  1226. "height": "50px",
  1227. "max-width": "500px",
  1228. "text-wrap": "wrap"
  1229. });
  1230. }
  1231.  
  1232. //////////
  1233. if (steamDisplaySidebar) {
  1234. // Sidebar for Steam
  1235. // $(".glance_ctn_responsive_left:first").append(' <div class="dev_row"><div class="subtitle column"><br></div></div><hr><br>');
  1236. $(".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> ');
  1237.  
  1238.  
  1239. // Create and insert the style element for custom CSS rules
  1240. var style = document.createElement('style');
  1241. style.innerHTML = `
  1242. .pirate_row {
  1243. display: flex;
  1244. }
  1245. .pirate_row, .pirate_row .column {
  1246. white-space: normal !important;
  1247. }
  1248. .pirate_row .column {
  1249. color: #556772;
  1250. }
  1251. .pirate_row .subtitle {
  1252. text-transform: uppercase;
  1253. font-size: 10px;
  1254. padding-right: 10px;
  1255. min-width: 120px;
  1256. }
  1257. .pirate_row .summary {
  1258. overflow: hidden;
  1259. text-overflow: ellipsis;
  1260. color: #556772;
  1261. }
  1262. .pirate_row:hover {
  1263. background-color: #333; /* Dark grey background on hover */
  1264. }
  1265. `;
  1266. document.head.appendChild(style);
  1267. }
  1268. ////////////
  1269.  
  1270. if (steamDisplaySidebar) {
  1271. savedButtonSet.forEach((el) => {
  1272. $(".block.responsive_apppage_details_left:first").append(furnishSteamSidebar(el.url+appName + el.urlSpecial, el.title, appName))
  1273. // $(".glance_ctn_responsive_left:first").append(furnishSteamSidebar(el.url+appName + el.urlSpecial, el.title, appName))
  1274. })
  1275. }
  1276. if (steamDisplayCart) {
  1277. savedButtonSet.forEach((el) => {
  1278. $(".game_purchase_action_bg:first").append(furnishSteam(el.url+appName + el.urlSpecial, el.title))
  1279. })
  1280. }
  1281.  
  1282. break;
  1283. case "IGG":
  1284. appName = $(".uk-article-title")[0].innerHTML.replace(" Free Download","");
  1285. appName = appName.trim().replace(/[^a-zA-Z0-9 ]/g, '');
  1286. savedButtonSet.forEach((el) => {
  1287. $(".uk-article-meta")[0].append(" -- ")
  1288. $(".uk-article-meta")[0].append(furnishIGG(el.url+appName, el.title))
  1289. })
  1290. break;
  1291. }
  1292.  
  1293. function furnishGOG(href, innerHTML) {
  1294. let element = document.createElement("a");
  1295. element.target= "_blank";
  1296. element.style = "margin: 5px 0 5px 0 !important; padding: 5px 10px 5px 10px;";
  1297. element.classList.add("button");
  1298. //element.classList.add("button--small");
  1299. element.classList.add("button--big");
  1300. element.classList.add("cart-button");
  1301. element.classList.add("ng-scope");
  1302. element.href = href;
  1303. element.innerHTML= innerHTML;
  1304. return element;
  1305. }
  1306. function furnishSteam(href, innerHTML) {
  1307. let element = document.createElement("a");
  1308. element.target= "_blank";
  1309. element.style = "margin-left: 10px; padding-right: 10px;";
  1310. element.href = href;
  1311. element.innerHTML= innerHTML;
  1312. return element;
  1313. }
  1314. function furnishSteamSidebar(searchUrl, appName, gameName) {
  1315. // Create the main container div
  1316. var devRowDiv = document.createElement('div');
  1317. devRowDiv.className = 'dev_row pirate_row';
  1318.  
  1319. // Create the subtitle div
  1320. var subtitleDiv = document.createElement('div');
  1321. subtitleDiv.className = 'subtitle column';
  1322. subtitleDiv.innerHTML = appName + ':';
  1323.  
  1324. // Create the summary div
  1325. var summaryDiv = document.createElement('div');
  1326. summaryDiv.className = 'summary column';
  1327.  
  1328. // Create the anchor element
  1329. var anchor = document.createElement('a');
  1330. anchor.href = searchUrl;
  1331. anchor.target = '_blank';
  1332. // anchor.innerHTML = 'Search ' + appName + ' for ' + gameName;
  1333. anchor.innerHTML = appName;
  1334.  
  1335. // Append the anchor to the summary div
  1336. summaryDiv.appendChild(anchor);
  1337.  
  1338. // Append the subtitle and summary divs to the main container div
  1339. devRowDiv.appendChild(subtitleDiv);
  1340. devRowDiv.appendChild(summaryDiv);
  1341.  
  1342. // Return the created element
  1343. return devRowDiv;
  1344. }
  1345.  
  1346. function furnishIGG(href, innerHTML) {
  1347. let element = document.createElement("a");
  1348. element.target= "_blank";
  1349. element.href = href;
  1350. element.innerHTML= innerHTML;
  1351. return element;
  1352. }
  1353.  
  1354.  
  1355.  
  1356. try{ GM_registerMenuCommand = GM_registerMenuCommand || this.GM_registerMenuCommand; }catch(e){ GM_registerMenuCommand = false; }
  1357.  
  1358. if(p !== "true"){
  1359. if(GM_registerMenuCommand){
  1360. GM_registerMenuCommand('Show unsafe websites', function(){
  1361. if(confirm('Are you sure you want to show possibly unsafe websites?\n'+
  1362. '(It can be hidden later with this menu)')){
  1363. GM_setValue("enableUnsafeButtonSet", "true");
  1364. GM_deleteValue("enabledButtonSet");
  1365. location.reload();
  1366. }
  1367. });
  1368. }
  1369. } else if (GM_registerMenuCommand) {
  1370. GM_registerMenuCommand('Hide unsafe websites', function(){
  1371. if(confirm('Are you sure you want to hide possibly unsafe websites?\n'+
  1372. '(It can be shown later with this menu)')){
  1373. GM_deleteValue("enableUnsafeButtonSet");
  1374. GM_deleteValue("enabledButtonSet");
  1375. location.reload();
  1376. }
  1377. });
  1378. }
  1379.  
  1380. if (GM_registerMenuCommand) {
  1381. GM_registerMenuCommand('Open Settings GUI', function(){
  1382. openSettingsGui();
  1383. });
  1384. }
  1385. if (GM_registerMenuCommand) {
  1386. GM_registerMenuCommand('Reset settings', function(){
  1387. GM_deleteValue("enableUnsafeButtonSet");
  1388. GM_deleteValue("enabledButtonSet");
  1389. GM_deleteValue("steamDisplaySidebar");
  1390. GM_deleteValue("steamDisplayCart");
  1391. GM_deleteValue("gogDisplaySidebar");
  1392. GM_deleteValue("gogDisplayCart");
  1393. location.reload();
  1394. });
  1395. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址