QuillBot Pro Hub

Premium Unlocker with Mobile Support, Changelog, Animations, Sounds & Refresh Timer

// ==UserScript==
// @name         QuillBot Pro Hub
// @namespace    quillbot.kyrillosatef.com
// @version      4.2.0
// @description  Premium Unlocker with Mobile Support, Changelog, Animations, Sounds & Refresh Timer
// @author       Kyrillos Atef
// @match        https://quillbot.com/*
// @icon         https://quillbot.com/favicon.png
// @require      https://greasyfork.org/scripts/455943-ajaxhooker/code/ajaxHooker.js?version=1124435
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@11
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
  'use strict';

  const CONFIG = {
    version: "4.2.0",
    author: "Kyrillos Atef",
    primary: "#4361EE",
    premiumFeatures: [
      "Unlimited paraphrasing",
      "Paraphrase in unlimited modes",
      "Get advanced grammar recommendations",
      "Humanize text in Advanced mode",
      "Create custom summaries",
      "Access AI Detector (unlimited)",
      "Prevent accidental plagiarism"
    ],
    changelog: [
      "✅ Mobile-responsive UI (v4.2.0)",
      "📱 Panel auto-positions on small screens",
      "ℹ️ Added in-app changelog",
      "🔊 Fixed sound URLs & preloading",
      "🔒 Improved stealth & performance",
      "✨ Better toggle feedback & accessibility"
    ],
    refreshDelay: 5 // seconds
  };

  const STATE = {
    isActive: GM_getValue("premiumStatus", false),
    panelOpen: false,
    timerId: null,
    remaining: 0,
    showChangelog: false
  };

  // 🔊 Fixed sound URLs (no trailing spaces!)
  const sounds = {
    on: new Audio("https://actions.google.com/sounds/v1/cartoon/clang_and_wobble.ogg"),
    off: new Audio("https://actions.google.com/sounds/v1/cartoon/wood_plank_flicks.ogg"),
    click: new Audio("https://actions.google.com/sounds/v1/buttons/button_click.ogg")
  };

  Object.values(sounds).forEach(s => {
    try {
      s.preload = "auto";
      s.load();
    } catch (e) {
      console.warn("[QuillBot Pro] Failed to preload sound:", e.message);
    }
  });

  const log = (type, msg) => {
    const prefix = type === "success" ? "✔️" : type === "error" ? "❌" : "ℹ️";
    console.log(`${prefix} [QuillBot Pro v${CONFIG.version}] ${msg}`);
  };

  const showToast = (msg, icon = "info") => {
    if (typeof Swal !== "undefined") {
      Swal.fire({
        toast: true,
        position: "top-end",
        icon,
        title: msg,
        showConfirmButton: false,
        timer: 2200,
        timerProgressBar: true,
        background: "#fff",
        color: "#2C3E50",
        customClass: { popup: 'qb-toast' }
      });
    } else {
      let el = document.getElementById("qb-fallback-toast");
      if (!el) {
        el = document.createElement("div");
        el.id = "qb-fallback-toast";
        Object.assign(el.style, {
          position: "fixed",
          top: "16px",
          right: "16px",
          zIndex: "9999999",
          padding: "10px 14px",
          borderRadius: "8px",
          boxShadow: "0 6px 18px rgba(0,0,0,0.12)",
          background: "#fff",
          color: "#2C3E50",
          opacity: "0",
          transition: "opacity 0.3s"
        });
        document.body.appendChild(el);
      }
      el.textContent = msg;
      el.style.opacity = "1";
      setTimeout(() => { el.style.opacity = "0"; }, 2000);
    }
  };

  const playSound = (name) => {
    try {
      const audio = sounds[name];
      if (audio) {
        audio.currentTime = 0;
        audio.play().catch(() => {});
      }
    } catch (e) {}
  };

  // 🛠️ API Interceptor
  const apiInterceptor = {
    init() {
      try {
        ajaxHooker.hook((req) => {
          if (!/\/api\/v\d\/account|get-account-details/i.test(req.url)) return;

          const originalResponse = req.response;
          req.response = (res) => {
            if (typeof originalResponse === 'function') {
              try { originalResponse(res); } catch (e) {}
            }

            if (!STATE.isActive) return;

            try {
              const data = JSON.parse(res.responseText);
              const d = data.data || {};

              d.profile = {
                ...d.profile,
                premium: true,
                client_type: "premium",
                premium_tier: "premium_plus",
                subscription_expires_at: new Date(Date.now() + 365 * 864e5).toISOString()
              };

              const limitKeys = ['paraphraser', 'summarizer', 'ai_detector', 'plagiarism', 'co_writer'];
              d.limits = d.limits || {};
              limitKeys.forEach(key => {
                d.limits[key] = {
                  limit: 999999,
                  premium_limit: 999999,
                  used: Math.min(d.limits[key]?.used || 0, 999999)
                };
              });

              res.responseText = JSON.stringify(data);
              log("success", "Premium data injected for: " + req.url);
            } catch (err) {
              log("error", "Response modification failed: " + err.message);
            }
          };
        });
        log("system", "API interceptor active");
      } catch (e) {
        log("error", "Failed to initialize ajaxHooker: " + e.message);
      }
    }
  };

  // 🎨 UI Controller
  const UI = {
    injectStyles() {
      GM_addStyle(`
        :root { --qb-primary: ${CONFIG.primary}; }
        .qb-pro-hub {
          position: fixed;
          bottom: ${window.innerWidth < 500 ? '80px' : '22px'};
          right: 22px;
          z-index: 9999999;
          font-family: 'Inter', system-ui, -apple-system, sans-serif;
        }
        .qb-pro-trigger {
          width: 56px;
          height: 56px;
          border-radius: 50%;
          background: var(--qb-primary);
          color: white;
          border: none;
          display: flex;
          align-items: center;
          justify-content: center;
          cursor: pointer;
          box-shadow: 0 8px 24px rgba(67, 97, 238, 0.28);
          transition: transform 0.32s cubic-bezier(0.2, 0.9, 0.3, 1), box-shadow 0.32s;
        }
        .qb-pro-trigger:hover {
          transform: rotate(12deg) scale(1.06);
          box-shadow: 0 12px 30px rgba(67, 97, 238, 0.36);
        }
        .qb-pro-trigger:active {
          transform: scale(0.96);
        }
        .qb-pro-panel {
          position: absolute;
          bottom: 72px;
          right: 0;
          width: 320px;
          max-width: calc(100vw - 44px);
          background: white;
          border-radius: 14px;
          padding: 18px;
          box-shadow: 0 16px 46px rgba(2, 6, 23, 0.18);
          opacity: 0;
          transform: translateY(18px) scale(0.98);
          transition: all 0.36s cubic-bezier(0.2, 1, 0.22, 1);
          pointer-events: none;
          font-size: 14px;
        }
        @media (max-width: 500px) {
          .qb-pro-panel {
            bottom: 72px;
            left: auto;
            right: 0;
            width: calc(100vw - 44px);
            max-width: none;
            font-size: 13px;
            padding: 16px;
          }
        }
        .qb-pro-panel.open {
          opacity: 1;
          transform: translateY(0) scale(1);
          pointer-events: auto;
        }
        .qb-pro-panel h3 {
          margin: 0 0 8px;
          font-size: 16px;
          font-weight: 700;
          color: #2C3E50;
          display: flex;
          justify-content: space-between;
          align-items: center;
        }
        .qb-changelog-toggle {
          background: none;
          border: none;
          color: #7f8c8d;
          cursor: pointer;
          font-size: 14px;
          padding: 2px;
        }
        .qb-changelog {
          margin-top: 12px;
          padding: 10px;
          background: #f8f9ff;
          border-radius: 8px;
          font-size: 12px;
          line-height: 1.4;
          display: none;
        }
        .qb-changelog.show {
          display: block;
        }
        .qb-pro-status {
          display: flex;
          align-items: center;
          gap: 8px;
          padding: 8px 12px;
          border-radius: 10px;
          font-weight: 600;
          margin-bottom: 12px;
        }
        .qb-features {
          font-size: 13px;
          color: #333;
          line-height: 1.45;
          margin-bottom: 12px;
        }
        .qb-pro-toggle-btn {
          width: 100%;
          padding: 12px;
          border-radius: 10px;
          border: none;
          font-weight: 700;
          cursor: pointer;
          transition: transform 0.12s;
        }
        .qb-pro-toggle-btn:active {
          transform: scale(0.995);
        }
        .qb-refresh {
          display: flex;
          gap: 8px;
          align-items: center;
          justify-content: space-between;
          margin-top: 10px;
          font-size: 13px;
          color: #95a5a6;
        }
        .qb-small-btn {
          padding: 6px 10px;
          border-radius: 8px;
          border: none;
          background: #f3f6ff;
          color: var(--qb-primary);
          cursor: pointer;
          font-weight: 600;
          font-size: 12px;
        }
        .qb-small-btn:hover {
          background: #e0eaff;
        }
      `);
    },

    createUI() {
      const hub = document.createElement("div");
      hub.className = "qb-pro-hub";
      hub.innerHTML = `
        <button class="qb-pro-trigger" aria-label="QuillBot Pro Hub" title="QuillBot Pro Hub">
          <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" width="22" height="22">
            <path d="M12 2L2 7l10 5 10-5-10-5z"/>
            <path d="M2 17l10 5 10-5"/>
          </svg>
        </button>
        <div class="qb-pro-panel" role="dialog" aria-label="QuillBot Pro Panel">
          <h3>
            ⚡ QuillBot Pro Hub
            <button class="qb-changelog-toggle" id="qb-changelog-btn" aria-label="Show changelog">ℹ️</button>
          </h3>
          <div class="qb-pro-status" id="qb-status"></div>
          <div class="qb-features">${CONFIG.premiumFeatures.map(f => `• ${f}`).join("<br>")}</div>
          <div class="qb-changelog" id="qb-changelog">${CONFIG.changelog.map(item => `• ${item}`).join("<br>")}</div>
          <button class="qb-pro-toggle-btn" id="qb-toggle"></button>
          <div class="qb-refresh" id="qb-refresh-row" style="display:none">
            <div>🔄 Refreshing in <strong id="qb-remaining"></strong>s</div>
            <div style="display:flex;gap:6px">
              <button class="qb-small-btn" id="qb-reload-now">Now</button>
              <button class="qb-small-btn" id="qb-cancel">Cancel</button>
            </div>
          </div>
          <div style="margin-top:10px;font-size:12px;color:#9aa6b2">by ${CONFIG.author} • v${CONFIG.version}</div>
        </div>
      `;
      document.body.appendChild(hub);

      const trigger = hub.querySelector(".qb-pro-trigger");
      const panel = hub.querySelector(".qb-pro-panel");
      const statusEl = hub.querySelector("#qb-status");
      const toggleBtn = hub.querySelector("#qb-toggle");
      const changelogBtn = hub.querySelector("#qb-changelog-btn");
      const changelogEl = hub.querySelector("#qb-changelog");
      const refreshRow = hub.querySelector("#qb-refresh-row");
      const reloadNowBtn = hub.querySelector("#qb-reload-now");
      const cancelBtn = hub.querySelector("#qb-cancel");
      const remainingSpan = hub.querySelector("#qb-remaining");

      const render = () => {
        const active = STATE.isActive;
        statusEl.textContent = active ? "✅ Premium Active" : "❌ Premium Disabled";
        statusEl.style.background = active ? "rgba(67,97,238,0.08)" : "rgba(231,76,60,0.08)";
        statusEl.style.color = active ? CONFIG.primary : "#E74C3C";
        toggleBtn.textContent = active ? "Disable Premium" : "Enable Premium";
        toggleBtn.style.background = active ? "#fff0f0" : "#eaf3ff";
        toggleBtn.style.color = active ? "#E74C3C" : CONFIG.primary;
      };

      const clearRefresh = () => {
        if (STATE.timerId) {
          clearInterval(STATE.timerId);
          STATE.timerId = null;
        }
        refreshRow.style.display = "none";
      };

      const startRefreshCountdown = (seconds = CONFIG.refreshDelay) => {
        clearRefresh();
        STATE.remaining = seconds;
        remainingSpan.textContent = STATE.remaining;
        refreshRow.style.display = "flex";
        STATE.timerId = setInterval(() => {
          STATE.remaining -= 1;
          remainingSpan.textContent = STATE.remaining;
          if (STATE.remaining <= 0) {
            clearRefresh();
            showToast("Reloading page...", "info");
            setTimeout(() => location.reload(), 100);
          }
        }, 1000);
      };

      // Events
      trigger.addEventListener("click", (e) => {
        e.stopPropagation();
        STATE.panelOpen = !STATE.panelOpen;
        panel.classList.toggle("open", STATE.panelOpen);
        playSound("click");
      });

      changelogBtn.addEventListener("click", (e) => {
        e.stopPropagation();
        STATE.showChangelog = !STATE.showChangelog;
        changelogEl.classList.toggle("show", STATE.showChangelog);
      });

      document.addEventListener("click", (e) => {
        if (!hub.contains(e.target) && STATE.panelOpen) {
          panel.classList.remove("open");
          STATE.panelOpen = false;
        }
      });

      document.addEventListener("keydown", (e) => {
        if (e.key === "Escape" && STATE.panelOpen) {
          panel.classList.remove("open");
          STATE.panelOpen = false;
        }
      });

      reloadNowBtn.addEventListener("click", () => {
        clearRefresh();
        showToast("Reloading now...", "info");
        setTimeout(() => location.reload(), 100);
      });

      cancelBtn.addEventListener("click", () => {
        clearRefresh();
        showToast("Auto-reload cancelled", "warning");
      });

      toggleBtn.addEventListener("click", (e) => {
        e.stopPropagation();
        STATE.isActive = !STATE.isActive;
        GM_setValue("premiumStatus", STATE.isActive);
        render();
        playSound(STATE.isActive ? "on" : "off");
        showToast(STATE.isActive ? "✅ Premium Enabled!" : "⚠️ Premium Disabled", STATE.isActive ? "success" : "warning");
        startRefreshCountdown();
      });

      render();
    },

    init() {
      this.injectStyles();
      this.createUI();
    }
  };

  // 🚀 Initialize
  apiInterceptor.init();

  const initUI = () => {
    if (document.body) {
      UI.init();
    } else {
      const iv = setInterval(() => {
        if (document.body) {
          clearInterval(iv);
          UI.init();
        }
      }, 100);
    }
  };

  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", initUI);
  } else {
    initUI();
  }

  log("system", `QuillBot Pro Hub v${CONFIG.version} initialized. Premium active: ${STATE.isActive}`);
})();