Untitled

 avatar
unknown
javascript
7 days ago
13 kB
24
No Index
// ==UserScript==
// @name         GOG Games Hider + Control Panel + Fetch Limit Overwrite
// @namespace    https://gog.com/
// @version      2.3
// @description  Hide/dim games, Early Access filter, hide button, panel toggle, and fetch limit overwrite
// @match        https://www.gog.com/en/*
// @grant        none
// ==/UserScript==

(function () {
  "use strict";

  const STORAGE_KEY = "hiddenGogGames";
  const MODE_KEY = "gogHideMode"; // 'none' | 'dim' | 'hide'
  const OPACITY_KEY = "gogDimOpacity";
  const PANEL_OPEN_KEY = "gogPanelOpen";
  const LIMIT_TOGGLE_KEY = "gogLimitToggle";
  const LIMIT_VALUE_KEY = "gogLimitValue";

  // Load settings
  const loadJSON = (key, fallback) => {
    try {
      const v = localStorage.getItem(key);
      return v === null ? fallback : JSON.parse(v);
    } catch {
      return fallback;
    }
  };
  const saveJSON = (key, val) => localStorage.setItem(key, JSON.stringify(val));

  const hiddenIds = new Set(loadJSON(STORAGE_KEY, []));
  let mode = localStorage.getItem(MODE_KEY) || "dim";
  let dimOpacity = parseFloat(localStorage.getItem(OPACITY_KEY)) || 0.1;
  let panelOpen = localStorage.getItem(PANEL_OPEN_KEY) !== "false";
  let limitToggle = localStorage.getItem(LIMIT_TOGGLE_KEY) === "true";
  let limitValue = parseInt(localStorage.getItem(LIMIT_VALUE_KEY)) || 100;

  const saveMode = (v) => localStorage.setItem(MODE_KEY, v);
  const saveOpacity = (v) => localStorage.setItem(OPACITY_KEY, String(v));
  const savePanelOpen = (v) =>
    localStorage.setItem(PANEL_OPEN_KEY, v ? "true" : "false");
  const saveLimitToggle = (v) =>
    localStorage.setItem(LIMIT_TOGGLE_KEY, v ? "true" : "false");
  const saveLimitValue = (v) =>
    localStorage.setItem(LIMIT_VALUE_KEY, String(v));

  // --- Style application ---
  const applyStyle = (tile, type) => {
    const parent = tile.parentElement;
    if (!parent) return;
    if (type === "hide") parent.style.display = "none";
    else if (type === "dim") {
      parent.style.display = "";
      parent.style.opacity = String(dimOpacity);
    }
  };
  const resetStyle = (tile) => {
    const p = tile.parentElement;
    if (!p) return;
    p.style.display = "";
    p.style.opacity = "";
  };
  const isEarlyAccess = (tile) =>
    tile.innerText.toLowerCase().includes("early access");

  // --- Hide Button ---
  const createHideButton = (tile, id) => {
    if (tile.querySelector(".gog-hide-btn")) return;
    const btn = document.createElement("button");
    btn.textContent = "Hide";
    btn.className = "gog-hide-btn";
    Object.assign(btn.style, {
      position: "absolute",
      top: "4px",
      right: "4px",
      zIndex: "10000000",
      background: "rgba(0,0,0,0.6)",
      color: "white",
      border: "none",
      padding: "2px 6px",
      borderRadius: "4px",
      cursor: "pointer",
      fontSize: "18px",
    });
    btn.addEventListener("click", (e) => {
      e.stopPropagation();
      e.preventDefault();
      hiddenIds.add(id);
      saveJSON(STORAGE_KEY, [...hiddenIds]);
      if (mode !== "none") applyStyle(tile, mode);
      btn.remove();
    });
    if (getComputedStyle(tile).position === "static")
      tile.style.position = "relative";
    tile.appendChild(btn);
  };

  // --- Unhide Button (for already hidden tiles) ---
  const createUnhideButton = (tile, id) => {
    if (tile.querySelector(".gog-unhide-btn")) return;

    const btn = document.createElement("button");
    btn.textContent = "Unhide";
    btn.className = "gog-unhide-btn";

    Object.assign(btn.style, {
      position: "absolute",
      bottom: "4px",
      left: "4px",
      zIndex: "10000000",
      background: "rgba(0,0,0,0.6)",
      color: "white",
      border: "none",
      padding: "2px 6px",
      borderRadius: "4px",
      cursor: "pointer",
      fontSize: "14px",
    });

    btn.addEventListener("click", (e) => {
      e.stopPropagation();
      e.preventDefault();

      hiddenIds.delete(id);
      saveJSON(STORAGE_KEY, [...hiddenIds]);

      // Reset styles and remove button
      resetStyle(tile);
      btn.remove();

      // Recreate the hide button
      createHideButton(tile, id);
    });

    if (getComputedStyle(tile).position === "static")
      tile.style.position = "relative";

    tile.appendChild(btn);
  };

  // --- Process Tiles ---
  const processGameTiles = () => {
    document.querySelectorAll("[data-product-id]").forEach((tile) => {
      const id = tile.getAttribute("data-product-id");
      if (!id) return;

      resetStyle(tile);

      if (hiddenIds.has(id)) {
        // Apply dim/hide effect if mode is active
        if (mode !== "none") {
          applyStyle(tile, mode);
        }

        // Only show Unhide button when mode = "none"
        if (mode === "none") {
          createUnhideButton(tile, id);
        }

        return;
      }

      // Visible item → ensure hide button exists
      createHideButton(tile, id);
    });
  };

  const observer = new MutationObserver(processGameTiles);
  observer.observe(document.body, { childList: true, subtree: true });
  processGameTiles();

  // --- Panel ---
  const panel = document.createElement("div");
  Object.assign(panel.style, {
    position: "fixed",
    top: "60px",
    right: "10px",
    background: "rgba(30,30,30,0.92)",
    color: "white",
    padding: "10px",
    borderRadius: "8px",
    zIndex: "999999",
    fontSize: "14px",
    display: "flex",
    flexDirection: "column",
    gap: "8px",
    alignItems: "center",
    minWidth: "190px",
    boxShadow: "0 6px 18px rgba(0,0,0,0.4)",
    opacity: panelOpen ? "1" : "0",
    pointerEvents: panelOpen ? "auto" : "none",
    transition: "opacity 0.25s ease",
  });

  // --- Toggle button ---
  const toggleBtn = document.createElement("button");
  toggleBtn.textContent = "?";
  Object.assign(toggleBtn.style, {
    position: "fixed",
    top: "10px",
    right: "10px",
    width: "34px",
    height: "34px",
    background: "rgba(50,50,50,0.95)",
    borderRadius: "50%",
    border: "none",
    cursor: "pointer",
    fontSize: "20px",
    color: "white",
    zIndex: "1000000",
    boxShadow: "0 4px 12px rgba(0,0,0,0.4)",
  });
  toggleBtn.addEventListener("click", () => {
    panelOpen = !panelOpen;
    savePanelOpen(panelOpen);
    panel.style.opacity = panelOpen ? "1" : "0";
    panel.style.pointerEvents = panelOpen ? "auto" : "none";
  });

  // --- Mode dropdown ---
  const modeLabel = document.createElement("div");
  modeLabel.textContent = "Filter Mode:";
  modeLabel.style.alignSelf = "flex-start";
  const modeSelect = document.createElement("select");
  ["none", "dim", "hide"].forEach((opt) => {
    const o = document.createElement("option");
    o.value = opt;
    o.textContent =
      opt === "none" ? "No Filter" : opt === "dim" ? "Dim" : "Hide";
    if (opt === mode) o.selected = true;
    modeSelect.appendChild(o);
  });
  modeSelect.style.padding = "6px";
  modeSelect.style.width = "100%";
  modeSelect.addEventListener("change", () => {
    mode = modeSelect.value;
    saveMode(mode);
    updateOpacityUI();

    // Remove all unhide buttons when mode ≠ "none"
    if (mode !== "none") {
      document
        .querySelectorAll(".gog-unhide-btn")
        .forEach((btn) => btn.remove());
    }

    processGameTiles();
  });

  const opacityContainer = document.createElement("div");
  opacityContainer.style.display = mode === "dim" ? "flex" : "none";
  opacityContainer.style.flexDirection = "column";
  opacityContainer.style.width = "100%";
  const opacityLabel = document.createElement("div");
  opacityLabel.textContent = `Dim Opacity: ${dimOpacity}`;
  const opacitySlider = document.createElement("input");
  opacitySlider.type = "range";
  opacitySlider.min = "0.05";
  opacitySlider.max = "1";
  opacitySlider.step = "0.01";
  opacitySlider.value = dimOpacity;
  opacitySlider.addEventListener("input", () => {
    dimOpacity = parseFloat(opacitySlider.value);
    opacityLabel.textContent = `Dim Opacity: ${dimOpacity}`;
    saveOpacity(dimOpacity);
    processGameTiles();
  });
  opacityContainer.appendChild(opacityLabel);
  opacityContainer.appendChild(opacitySlider);
  const updateOpacityUI = () => {
    opacityContainer.style.display = mode === "dim" ? "flex" : "none";
  };

  // --- Export / Import hidden IDs ---
  const exportBtn = document.createElement("button");
  exportBtn.textContent = "Export Hidden IDs";
  Object.assign(exportBtn.style, {
    padding: "6px 8px",
    background: "#007acc",
    color: "white",
    border: "none",
    cursor: "pointer",
    borderRadius: "6px",
    width: "100%",
  });
  exportBtn.addEventListener("click", () => {
    const blob = new Blob([JSON.stringify([...hiddenIds], null, 2)], {
      type: "application/json",
    });
    const url = URL.createObjectURL(blob);
    const a = document.createElement("a");
    a.href = url;
    a.download = "hidden_gog_games.json";
    a.click();
    URL.revokeObjectURL(url);
  });
  const importBtn = document.createElement("button");
  importBtn.textContent = "Import Hidden IDs";
  Object.assign(importBtn.style, {
    padding: "6px 8px",
    background: "#28a745",
    color: "white",
    border: "none",
    cursor: "pointer",
    borderRadius: "6px",
    width: "100%",
  });
  importBtn.addEventListener("click", () => {
    const json = prompt("Paste the JSON array of hidden IDs:");
    if (!json) return;
    try {
      const imported = JSON.parse(json);
      if (Array.isArray(imported)) {
        imported.forEach((id) => hiddenIds.add(id));
        saveJSON(STORAGE_KEY, [...hiddenIds]);
        processGameTiles();
        alert("Imported successfully!");
      } else {
        alert("Invalid format: expected JSON array.");
      }
    } catch {
      alert("Invalid JSON.");
    }
  });

  // --- Limit Overwrite Toggle ---
  const limitContainer = document.createElement("div");
  limitContainer.style.display = "flex";
  limitContainer.style.flexDirection = "column";
  limitContainer.style.width = "100%";
  limitContainer.style.gap = "8px";
  const limitToggleBtn = document.createElement("button");
  limitToggleBtn.textContent = limitToggle
    ? "Overwrite Limit: ON"
    : "Overwrite Limit: OFF";
  Object.assign(limitToggleBtn.style, {
    padding: "6px 8px",
    borderRadius: "6px",
    border: "none",
    cursor: "pointer",
    width: "100%",
    background: limitToggle ? "#116622" : "#444",
    color: "white",
  });
  const limitInput = document.createElement("input");
  limitInput.type = "number";
  limitInput.min = "10";
  limitInput.max = "100";
  limitInput.value = limitValue;
  limitInput.style.width = "100%";
  limitInput.style.display = limitToggle ? "block" : "none";
  const limitNote = document.createElement("div");
  limitNote.textContent =
    "Limit must be between 10 and 100 (needs refresh to apply)";
  limitNote.style.fontSize = "12px";
  limitNote.style.color = "#ccc";
  limitNote.style.display = limitToggle ? "block" : "none";
  limitNote.style.maxWidth = "190px";
  limitToggleBtn.addEventListener("click", () => {
    limitToggle = !limitToggle;
    saveLimitToggle(limitToggle);
    limitToggleBtn.textContent = limitToggle
      ? "Overwrite Limit: ON"
      : "Overwrite Limit: OFF";
    limitToggleBtn.style.background = limitToggle ? "#116622" : "#444";
    limitInput.style.display = limitToggle ? "block" : "none";
    limitNote.style.display = limitToggle ? "block" : "none";
  });
  limitInput.addEventListener("input", () => {
    let val = parseInt(limitInput.value);
    if (val < 10) val = 10;
    if (val > 100) val = 100;
    limitInput.value = val;
    limitValue = val;
    saveLimitValue(limitValue);
  });
  limitContainer.appendChild(limitToggleBtn);
  limitContainer.appendChild(limitInput);
  limitContainer.appendChild(limitNote);

  // --- Assemble panel ---
  panel.appendChild(modeLabel);
  panel.appendChild(modeSelect);
  panel.appendChild(opacityContainer);
  panel.appendChild(exportBtn);
  panel.appendChild(importBtn);
  panel.appendChild(limitContainer);
  document.body.appendChild(panel);
  document.body.appendChild(toggleBtn);

  // --- Intercept fetch/XHR ---
  const originalFetch = window.fetch;
  window.fetch = function (input, init) {
    let url = typeof input === "string" ? input : input.url;
    if (limitToggle && url.includes("catalog.gog.com/v1/catalog")) {
      const u = new URL(url);
      u.searchParams.set("limit", String(limitValue));
      input = u.toString();
    }
    return originalFetch(input, init);
  };
  const open = XMLHttpRequest.prototype.open;
  XMLHttpRequest.prototype.open = function (method, url) {
    if (limitToggle && url.includes("catalog.gog.com/v1/catalog")) {
      const u = new URL(url);
      u.searchParams.set("limit", String(limitValue));
      url = u.toString();
    }
    return open.apply(this, [method, url]);
  };
})();
Editor is loading...
Leave a Comment