Steam Cracked & Deals Hub

Adds custom URL buttons on Steam pages for cracked and deal sites.

// ==UserScript==
// @name         Steam Cracked & Deals Hub
// @description  Adds custom URL buttons on Steam pages for cracked and deal sites.
// @version      2.0
// @license      MIT
// @author       6969RandomGuy6969
// @match        https://store.steampowered.com/app/*
// @icon         https://i.imgur.com/8CoJnwB.png
// @namespace    https://greasyfork.org/users/1167434
// ==/UserScript==

(function () {
  'use strict';

  const gameName = decodeURIComponent(window.location.pathname.split("/")[3].replace(/_/g, " "));
  const ignoreButton = document.querySelector("#ignoreBtn");

  // Helpers for local storage
  function saveCustomSites(key, data) {
    localStorage.setItem("steam_links_custom_" + key, JSON.stringify(data));
  }

  function loadCustomSites(key) {
    try {
      return JSON.parse(localStorage.getItem("steam_links_custom_" + key)) || [];
    } catch {
      return [];
    }
  }

  function addCustomSite(key, site) {
    const current = loadCustomSites(key);
    current.push(site);
    saveCustomSites(key, current);
  }

  function removeCustomSite(key, site) {
    const current = loadCustomSites(key);
    const updated = current.filter(s => s.url !== site.url || s.text !== site.text);
    saveCustomSites(key, updated);
  }

  // UI Component Creators
  function createContainer() {
    const container = document.createElement("div");
    container.style.marginTop = "50px";
    container.style.padding = "20px";
    container.style.background = "rgba(0, 0, 0, 0.2)";
    container.style.borderRadius = "10px";
    container.style.display = "flex";
    container.style.flexDirection = "column";
    container.style.gap = "30px";
    return container;
  }

  function createHeader(text) {
    const header = document.createElement("div");
    header.style.fontWeight = "bold";
    header.style.fontSize = "21px";
    header.style.marginBottom = "15px";
    header.style.textTransform = "uppercase";
    header.textContent = text;
    return header;
  }

  function createGridContainer() {
    const grid = document.createElement("div");
    grid.style.display = "grid";
    grid.style.gridTemplateColumns = "repeat(auto-fit, minmax(160px, 1fr))";
    grid.style.gap = "10px";
    return grid;
  }

  function createButton(url, buttonText, isRemovable = false, onRemove = null, isEditMode = false) {
    const buttonContainer = document.createElement("div");
    buttonContainer.style.display = "flex";
    buttonContainer.style.alignItems = "center";
    buttonContainer.style.position = "relative";

    const button = document.createElement("a");
    button.className = "btnv6_blue_hoverfade btn_medium";
    button.style.display = "flex";
    button.style.alignItems = "center";
    button.style.justifyContent = "center";
    button.style.minWidth = "160px";
    button.style.height = "40px";
    button.style.textAlign = "center";
    button.style.cursor = "pointer";
    button.style.backgroundColor = "rgba(103, 193, 245, 0.2)";
    button.style.color = "#67c1f5";
    button.style.fontSize = "14px";
    button.style.borderRadius = "6px";

    const formattedName = gameName.replace(/\s+/g, "+");
    const finalURL = url.includes("{{GAME_NAME}}")
      ? url.replace("{{GAME_NAME}}", formattedName)
      : url + encodeURIComponent(gameName);

    button.href = finalURL;
    button.target = "_blank";
    button.textContent = buttonText;

    buttonContainer.appendChild(button);

    if (isRemovable) {
      const deleteButton = document.createElement("button");
      deleteButton.textContent = "✖";
      deleteButton.style.position = "absolute";
      deleteButton.style.top = "-5px";
      deleteButton.style.right = "-5px";
      deleteButton.style.background = "red";
      deleteButton.style.color = "white";
      deleteButton.style.border = "none";
      deleteButton.style.width = "20px";
      deleteButton.style.height = "20px";
      deleteButton.style.borderRadius = "50%";
      deleteButton.style.cursor = "pointer";
      deleteButton.style.display = isEditMode ? "block" : "none";

      deleteButton.onclick = () => {
        buttonContainer.remove();
        if (onRemove) onRemove();
      };

      buttonContainer.appendChild(deleteButton);
      buttonContainer._deleteButton = deleteButton; // store ref
    }

    return buttonContainer;
  }

  function createInputSection(category, grid) {
    let isEditMode = false;
    const deleteRefs = [];

    const addButton = document.createElement("button");
    addButton.textContent = "+";
    addButton.style.background = "#67c1f5";
    addButton.style.color = "white";
    addButton.style.border = "none";
    addButton.style.padding = "5px 10px";
    addButton.style.cursor = "pointer";
    addButton.style.borderRadius = "4px";

    const inputContainer = document.createElement("div");
    inputContainer.style.display = "none";
    inputContainer.style.flexDirection = "column";
    inputContainer.style.gap = "5px";
    inputContainer.style.marginTop = "10px";

    const inputName = document.createElement("input");
    inputName.placeholder = "Site Name";
    inputName.style.flex = "1";

    const inputURL = document.createElement("input");
    inputURL.placeholder = "Paste search URL like https://steamrip.com/?s=red+dead";
    inputURL.style.flex = "2";

    const confirmButton = document.createElement("button");
    confirmButton.textContent = "✅ Save";
    confirmButton.style.background = "green";
    confirmButton.style.color = "white";
    confirmButton.style.border = "none";
    confirmButton.style.padding = "5px 10px";
    confirmButton.style.cursor = "pointer";
    confirmButton.style.borderRadius = "4px";

    const cancelButton = document.createElement("button");
    cancelButton.textContent = "❌ Cancel";
    cancelButton.style.background = "red";
    cancelButton.style.color = "white";
    cancelButton.style.border = "none";
    cancelButton.style.padding = "5px 10px";
    cancelButton.style.cursor = "pointer";
    cancelButton.style.borderRadius = "4px";

    function toggleEditMode(enable) {
      isEditMode = enable;
      inputContainer.style.display = isEditMode ? "flex" : "none";
      addButton.textContent = isEditMode ? "➕ Add" : "+";

      deleteRefs.forEach(btn => {
        btn.style.display = isEditMode ? "block" : "none";
      });
    }

    confirmButton.onclick = () => {
      const name = inputName.value.trim();
      const url = inputURL.value.trim();

      if (name && url.includes("?") && url.includes("=")) {
        const base = url.split("=")[0] + "=";
        const site = {
          url: base + "{{GAME_NAME}}",
          text: name
        };

        const btn = createButton(site.url, site.text, true, () => {
          removeCustomSite(category, site);
        }, isEditMode);

        if (btn._deleteButton) deleteRefs.push(btn._deleteButton);
        grid.appendChild(btn);
        addCustomSite(category, site);

        inputName.value = "";
        inputURL.value = "";
        toggleEditMode(false);
      } else {
        alert("Paste full working search URL like https://steamrip.com/?s=red+dead");
      }
    };

    cancelButton.onclick = () => toggleEditMode(false);
    addButton.onclick = () => toggleEditMode(!isEditMode);

    inputContainer.appendChild(inputName);
    inputContainer.appendChild(inputURL);
    inputContainer.appendChild(confirmButton);
    inputContainer.appendChild(cancelButton);

    const section = document.createElement("div");
    section.appendChild(addButton);
    section.appendChild(inputContainer);

    loadCustomSites(category).forEach(site => {
      const btn = createButton(site.url, site.text, true, () => {
        removeCustomSite(category, site);
      }, isEditMode);

      if (btn._deleteButton) {
        btn._deleteButton.style.display = "none";
        deleteRefs.push(btn._deleteButton);
      }

      grid.appendChild(btn);
    });

    return section;
  }

  // Main UI
  const categoryContainer = createContainer();

  const crackedHeader = createHeader("CRACKED:");
  const crackedGrid = createGridContainer();
  const crackedSites = [
    { url: "https://www.skidrowreloaded.com/?s={{GAME_NAME}}", text: "SkidrowReloaded" },
    { url: "https://igg-games.com/?s={{GAME_NAME}}", text: "IGG-Games" },
    { url: "https://x1337x.ws/srch?search={{GAME_NAME}}", text: "x1337x" },
    { url: "https://game3rb.com/?s={{GAME_NAME}}", text: "Game3rb" },
    { url: "https://online-fix.me/index.php?do=search&subaction=search&story={{GAME_NAME}}", text: "Onlinefix" },
    { url: "https://fitgirl-repacks.site/?s={{GAME_NAME}}", text: "Fitgirl Repacks" },
    { url: "https://dodi-repacks.site/?s={{GAME_NAME}}", text: "Dodi Repack" }
  ];
  crackedSites.forEach(site => crackedGrid.appendChild(createButton(site.url, site.text)));
  const crackedInputSection = createInputSection("cracked", crackedGrid);

  const dealsHeader = createHeader("DEALS:");
  const dealsGrid = createGridContainer();
  const dealsSites = [
    { url: "https://store.epicgames.com/en-US/browse?q={{GAME_NAME}}", text: "Epic Games" },
    { url: "https://www.gog.com/en/games?query={{GAME_NAME}}", text: "GOG" },
    { url: "https://www.humblebundle.com/store/search?sort=discount&search={{GAME_NAME}}", text: "Humble Store" },
    { url: "https://www.gamestop.com/search/?q={{GAME_NAME}}", text: "GameStop" },
    { url: "https://www.cdkeys.com/catalogsearch/result/?q={{GAME_NAME}}", text: "CDKeys" },
    { url: "https://store.ubi.com/us/search?q={{GAME_NAME}}", text: "UPlay" },
    { url: "https://www.origin.com/store/search?fq=search&searchString={{GAME_NAME}}", text: "Origin" },
    { url: "https://www.greenmangaming.com/search?query={{GAME_NAME}}", text: "Green Man Gaming" },
    { url: "https://www.fanatical.com/en/search?search={{GAME_NAME}}", text: "Fanatical" },
    { url: "https://www.microsoft.com/en-us/store/search/games?q={{GAME_NAME}}", text: "Microsoft Store" }
  ];
  dealsSites.forEach(site => dealsGrid.appendChild(createButton(site.url, site.text)));
  const dealsInputSection = createInputSection("deals", dealsGrid);

  categoryContainer.appendChild(crackedHeader);
  categoryContainer.appendChild(crackedGrid);
  categoryContainer.appendChild(crackedInputSection);
  categoryContainer.appendChild(dealsHeader);
  categoryContainer.appendChild(dealsGrid);
  categoryContainer.appendChild(dealsInputSection);

  if (ignoreButton) {
    ignoreButton.parentNode.insertBefore(categoryContainer, ignoreButton.nextSibling);
  }
})();