QuillBot Pro Hub

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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}`);
})();