DropGalaxy Auto Skip

Auto skip ads and auto download on DropGalaxy

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         DropGalaxy Auto Skip
// @namespace    http://tampermonkey.net/
// @version      1.7.2
// @description  Auto skip ads and auto download on DropGalaxy
// @author       kleptomaniac14
// @match        https://dropgalaxy.com/*
// @match        https://dropgalaxy.co/*
// @match        https://financemonk.net/*
// @icon         https://www.google.com/s2/favicons?domain=dropgalaxy.com
// @grant        GM.getValue
// @grant        GM.setValue
// @grant        GM.deleteValue
// @license      GNU GPLv2
// ==/UserScript==

// Setting esversion to 11 to use optional chaining.
/* jshint esversion: 11 */

(function () {
  "use strict";

  // Constants
  const MAX_IDENTIFICATION_RETRIES = 3;
  const STORAGE_SCRIPT_KEY = "dg_auto_skip_config";
  const STORAGE_CACHE_KEY = "dg_auto_skip_cache";
  const DG_AUTO_SKIP_CONFIG = {
    fallbackDlProvider: "vikingfile", // 'vikingfile, 'gofile
    // Provider to use in case DDL is not available.
    cacheDlLinks: false,
    cacheExpiry: "1week", // '1hour', '1day', '1week', '1month', 'forever'
    alertCaptchaAvailable: false, // Alert when CAPTCHA is available
  };

  // Global Variables
  let intervalId = null;
  let identificationRetries = 0;
  let config = {};
  let cache = {};
  let currentPageLinks = null; // Store current page links for toggle button
  let shouldStopScript = false;

  // Utils
  const log = (message, ...rest) =>
    console.log(
      `[DropGalaxy Auto Skip] [ID: ${intervalId}] ${message}`,
      ...rest
    );

  const getFileMetadata = () => {
    const filename = document.getElementsByTagName("h1")?.[0]?.innerText;
    const fileSize =
      document.getElementsByClassName("fa-hdd")?.[0]?.parentElement
        ?.children?.[1]?.innerText;
    const reportAbuseHref =
      document.getElementsByClassName("fa-flag")?.[0]?.parentElement
        ?.children?.[1]?.href;

    let fileId = null;
    if (reportAbuseHref) {
      const url = new URL(reportAbuseHref);
      fileId = url.searchParams.get("id");
    }

    return { filename, fileSize, fileId };
  };

  const validateCache = () => {
    const now = Date.now();
    const eightHours = 8 * 60 * 60 * 1000;

    Object.keys(cache).forEach((fileId) => {
      const entry = cache[fileId];

      if (entry?.expiry && entry.expiry < now) {
        delete cache[fileId];
      } else if (entry?.createdAt && now - entry.createdAt > eightHours) {
        // Remove the DDL link after 8 hours, keep mirrors if any
        if (entry.urls) {
          entry.urls.ddl = "";
        }
      }
    });
  };

  const loadConfig = async () => {
    const configString = await GM.getValue(STORAGE_SCRIPT_KEY);

    try {
      config = JSON.parse(configString);
    } catch (error) {
      log("Error parsing config, using default", error);
      config = DG_AUTO_SKIP_CONFIG;
    }

    if (!config || typeof config !== "object") {
      config = DG_AUTO_SKIP_CONFIG;
    }
  };

  const loadCache = async () => {
    const cacheString = await GM.getValue(STORAGE_CACHE_KEY);

    try {
      cache = JSON.parse(cacheString);
    } catch (error) {
      log("Error parsing cache, starting fresh", error);
      cache = {};
    }
    if (!cache || typeof cache !== "object") {
      cache = {};
    }

    validateCache();
  };

  const saveCache = async ({ fileId, filename, fileSize, links }) => {
    if (!Array.isArray(links) || links.length === 0) return;

    // For each link, extract their domain, identify if it is DDL or alternative
    // and save to cache with expiry
    const now = Date.now();
    const urls = {
      ddl: "",
      vikingfile: "",
      gofile: "",
    };

    links.forEach((link) => {
      const url = new URL(link);
      const domain = url.hostname;

      if (domain.includes("a2zupload")) {
        urls.ddl = link;
      } else if (domain.includes("vikingfile")) {
        urls.vikingfile = link;
      } else if (domain.includes("gofile")) {
        urls.gofile = link;
      }
    });

    const expiry = (() => {
      switch (config.cacheExpiry) {
        case "1hour":
          return now + 3600 * 1000;
        case "1day":
          return now + 24 * 3600 * 1000;
        case "1week":
          return now + 7 * 24 * 3600 * 1000;
        case "1month":
          return now + 30 * 24 * 3600 * 1000;
        case "forever":
          return null;
      }
    })();

    cache[fileId] = { filename, fileSize, urls, expiry, createdAt: now };

    validateCache();

    await GM.setValue(STORAGE_CACHE_KEY, JSON.stringify(cache));
    log("Cache saved");
  };

  const handleSaveConfig = async (newConfig) => {
    await GM.setValue(STORAGE_SCRIPT_KEY, JSON.stringify(newConfig));
    config = newConfig;
    log("Settings saved");
  };

  const handleResetConfig = async () => {
    if (confirm("Reset settings?")) {
      await GM.deleteValue(STORAGE_SCRIPT_KEY);
      log("Settings reset");
    }
  };

  const checkCachedFile = () => {
    const { fileId } = getFileMetadata();

    const hasValidURLs = Object.values(cache[fileId]?.urls ?? {}).some(
      (url) => url
    );

    if (hasValidURLs) {
      shouldStopScript = true;
      showModalWithLinks(cache[fileId].urls);
      return true;
    }

    return false;
  };

  const showModalWithLinks = async (urls) => {
    // Store links globally for toggle button
    currentPageLinks = urls;

    // Check if modal already exists, if so remove it (toggle behavior)
    const existingModal = document.getElementById("dg-links-modal");
    if (existingModal) {
      existingModal.remove();
      return; // Exit to toggle off
    }

    // Create modal
    const modal = document.createElement("div");
    modal.id = "dg-links-modal";
    modal.style.cssText = `
            position: fixed; top: 70px; right: 20px; left: 20px; width: auto; max-width: 320px;
            background: var(--bg-color, #ffffff); border: 1px solid var(--border-color, #ddd); border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 9999999999; padding: 0; color: var(--text-color, #000000);
        `;

    // Adjust for desktop (wider screens)
    if (window.innerWidth > 600) {
      modal.style.left = "auto";
      modal.style.width = "320px";
    }

    const modalContent = document.createElement("div");
    modalContent.style.cssText = `
            background: var(--bg-color, #ffffff); padding: 20px; border-radius: 8px; position: relative;
            color: var(--text-color, #000000);
        `;

    // Close button
    const closeBtn = document.createElement("button");
    closeBtn.innerHTML = "×";
    closeBtn.style.cssText = `
            position: absolute; top: 5px; right: 10px; background: none; border: none; font-size: 18px; cursor: pointer; color: var(--text-muted, #999999);
        `;
    closeBtn.onclick = () => {
      modal.remove();
      // Show links button again when modal is closed
      if (currentPageLinks) {
        window.toggleLinksButton(true);
      }
    };

    // Title
    const title = document.createElement("h3");
    title.textContent = "Available Download Links";
    title.style.cssText =
      "margin: 0 0 15px 0; font-size: 16px; color: var(--text-color, #000000);";

    // File info section
    const { fileId } = getFileMetadata();
    const cachedFile = cache[fileId];

    if (cachedFile && (cachedFile?.filename || cachedFile?.fileSize)) {
      const fileInfo = document.createElement("div");
      fileInfo.style.cssText =
        "background: var(--input-bg, #f8f9fa); padding: 10px; border-radius: 5px; margin-bottom: 15px; border: 1px solid var(--border-color, #ddd);";

      if (cachedFile?.filename) {
        const filename = document.createElement("div");
        filename.textContent = `File: ${cachedFile.filename}`;
        filename.style.cssText =
          "font-weight: bold; color: var(--text-color, #000000); margin-bottom: 5px; word-break: break-word;";
        fileInfo.appendChild(filename);
      }

      if (cachedFile?.fileSize) {
        const fileSize = document.createElement("div");
        fileSize.textContent = `Size: ${cachedFile.fileSize}`;
        fileSize.style.cssText =
          "color: var(--text-muted, #666666); font-size: 12px;";
        fileInfo.appendChild(fileSize);
      }

      modalContent.appendChild(fileInfo);
    }

    // Links list
    const linksList = document.createElement("div");
    linksList.style.cssText =
      "display: flex; flex-direction: column; gap: 10px; margin-bottom: 10px;";

    // Add links
    if (urls.ddl) {
      const a = document.createElement("a");
      a.href = urls.ddl;
      a.textContent = "Direct Download Link";
      a.target = "_blank";
      a.style.cssText =
        "background: #22a76d; color: white; padding: 10px; border-radius: 5px; text-align: center; text-decoration: none; font-weight: bold;";
      linksList.appendChild(a);
    }

    if (urls.vikingfile) {
      const a = document.createElement("a");
      a.href = urls.vikingfile;
      a.textContent = "VikingFile Mirror";
      a.target = "_blank";
      a.style.cssText =
        "background: #007bff; color: white; padding: 10px; border-radius: 5px; text-align: center; text-decoration: none; font-weight: bold;";
      linksList.appendChild(a);
    }

    if (urls.gofile) {
      const a = document.createElement("a");
      a.href = urls.gofile;
      a.textContent = "GoFile Mirror";
      a.target = "_blank";
      a.style.cssText =
        "background: #6c757d; color: white; padding: 10px; border-radius: 5px; text-align: center; text-decoration: none; font-weight: bold;";
      linksList.appendChild(a);
    }

    // Show message if no links are available
    if (!urls.ddl && !urls.vikingfile && !urls.gofile) {
      const noLinks = document.createElement("div");
      noLinks.textContent = "No links available.";
      noLinks.style.cssText =
        "color: var(--text-muted, #999999); text-align: center; padding: 10px;";
      linksList.appendChild(noLinks);
    }

    // Remove From Cache button click handler
    const handleRemoveFromCache = async (e) => {
      e.stopPropagation();
      const { fileId } = getFileMetadata();
      if (!fileId) return;

      if (cache && cache[fileId]) {
        delete cache[fileId];
        try {
          await GM.setValue(STORAGE_CACHE_KEY, JSON.stringify(cache));
          log(`Removed ${fileId} from cache`);
        } catch (err) {
          log("Error saving cache after removal", err);
        }
      }

      // Close modal and restart page watcher
      modal.remove();
      shouldStopScript = false;
      if (typeof startPageWatcher === "function") startPageWatcher();
    };

    // Remove From Cache button
    const removeBtn = document.createElement("button");
    removeBtn.innerText = "Remove From Cache";
    removeBtn.style.cssText = `
            background: #dc3545; color: white; padding: 8px; border-radius: 5px; text-align: center; border: none; cursor: pointer; font-weight: bold; width: 100%; margin-top: 8px;
        `;
    removeBtn.onclick = handleRemoveFromCache;

    // Assemble modal
    modalContent.appendChild(closeBtn);
    modalContent.appendChild(title);
    modalContent.appendChild(linksList);
    modalContent.appendChild(removeBtn);
    modal.appendChild(modalContent);
    document.body.appendChild(modal);

    // Close on outside click
    setTimeout(() => {
      document.addEventListener("click", function clickHandler(e) {
        if (!modal.contains(e.target)) {
          modal.remove();
          document.removeEventListener("click", clickHandler);
        }
      });
    }, 100);
  };

  const showCacheBrowser = async () => {
    // Check if modal already exists, if so remove it (toggle behavior)
    const existingModal = document.getElementById("dg-cache-browser-modal");
    if (existingModal) {
      existingModal.remove();
      return;
    }

    // Create modal
    const modal = document.createElement("div");
    modal.id = "dg-cache-browser-modal";
    modal.style.cssText = `
      position: fixed; top: 70px; right: 20px; left: 20px; width: auto; max-width: 500px; max-height: 70vh;
      background: var(--bg-color, #ffffff); border: 1px solid var(--border-color, #ddd); border-radius: 8px;
      box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 9999999999; padding: 0; color: var(--text-color, #000000);
      display: flex; flex-direction: column;
    `;

    // Adjust for desktop (wider screens)
    if (window.innerWidth > 600) {
      modal.style.left = "auto";
      modal.style.width = "500px";
    }

    const modalContent = document.createElement("div");
    modalContent.style.cssText = `
      background: var(--bg-color, #ffffff); padding: 20px; border-radius: 8px; position: relative;
      color: var(--text-color, #000000); display: flex; flex-direction: column; max-height: 70vh;
    `;

    // Close button
    const closeBtn = document.createElement("button");
    closeBtn.innerHTML = "×";
    closeBtn.style.cssText = `
      position: absolute; top: 5px; right: 10px; background: none; border: none; font-size: 18px; cursor: pointer; color: var(--text-muted, #999999);
    `;
    closeBtn.onclick = () => modal.remove();

    // Title
    const title = document.createElement("h3");
    title.textContent = "Cached Files";
    title.style.cssText =
      "margin: 0 0 15px 0; font-size: 16px; color: var(--text-color, #000000);";

    // Search box
    const searchBox = document.createElement("input");
    searchBox.type = "text";
    searchBox.placeholder = "Search files...";
    searchBox.style.cssText = `
      width: 100%; padding: 8px; margin-bottom: 15px; border: 1px solid var(--border-color, #ddd);
      border-radius: 4px; background: var(--input-bg, #ffffff); color: var(--text-color, #000000);
      font-size: 12px;
    `;

    // Files container
    const filesContainer = document.createElement("div");
    filesContainer.style.cssText = `
      flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 10px;
    `;

    // Function to render files
    const renderFiles = (searchTerm = "") => {
      filesContainer.innerHTML = "";
      const cacheEntries = Object.entries(cache);

      if (cacheEntries.length === 0) {
        const emptyMsg = document.createElement("div");
        emptyMsg.textContent = "No cached files.";
        emptyMsg.style.cssText =
          "text-align: center; color: var(--text-muted, #999999); padding: 20px;";
        filesContainer.appendChild(emptyMsg);
        return;
      }

      const filteredEntries = cacheEntries.filter(([fileId, data]) => {
        if (!searchTerm) return true;
        const term = searchTerm.toLowerCase();
        return (
          data.filename?.toLowerCase().includes(term) ||
          data.fileSize?.toLowerCase().includes(term) ||
          fileId.toLowerCase().includes(term)
        );
      });

      if (filteredEntries.length === 0) {
        const noResults = document.createElement("div");
        noResults.textContent = "No files match your search.";
        noResults.style.cssText =
          "text-align: center; color: var(--text-muted, #999999); padding: 20px;";
        filesContainer.appendChild(noResults);
        return;
      }

      filteredEntries.forEach(([fileId, data]) => {
        const fileCard = document.createElement("div");
        fileCard.style.cssText = `
          background: var(--input-bg, #f8f9fa); padding: 12px; border-radius: 5px;
          border: 1px solid var(--border-color, #ddd); cursor: pointer;
          transition: background 0.2s;
        `;
        fileCard.onmouseenter = () =>
          (fileCard.style.background = "var(--border-color, #e9ecef)");
        fileCard.onmouseleave = () =>
          (fileCard.style.background = "var(--input-bg, #f8f9fa)");

        // Filename
        const filename = document.createElement("div");
        filename.textContent = data.filename || "Unknown File";
        filename.style.cssText =
          "font-weight: bold; color: var(--text-color, #000000); margin-bottom: 5px; word-break: break-word; font-size: 13px;";

        // File size
        const fileSize = document.createElement("div");
        fileSize.textContent = `Size: ${data.fileSize || "Unknown"}`;
        fileSize.style.cssText =
          "color: var(--text-muted, #666666); font-size: 11px; margin-bottom: 8px;";

        // Links row
        const linksRow = document.createElement("div");
        linksRow.style.cssText =
          "display: flex; gap: 5px; flex-wrap: wrap; margin-bottom: 8px;";

        // Add link buttons
        if (data.urls?.ddl) {
          const ddlBtn = document.createElement("a");
          ddlBtn.href = data.urls.ddl;
          ddlBtn.textContent = "DDL";
          ddlBtn.target = "_blank";
          ddlBtn.style.cssText =
            "background: #22a76d; color: white; padding: 4px 8px; border-radius: 3px; text-decoration: none; font-size: 10px; font-weight: bold;";
          linksRow.appendChild(ddlBtn);
        }

        if (data.urls?.vikingfile) {
          const vikingBtn = document.createElement("a");
          vikingBtn.href = data.urls.vikingfile;
          vikingBtn.textContent = "Viking";
          vikingBtn.target = "_blank";
          vikingBtn.style.cssText =
            "background: #007bff; color: white; padding: 4px 8px; border-radius: 3px; text-decoration: none; font-size: 10px; font-weight: bold;";
          linksRow.appendChild(vikingBtn);
        }

        if (data.urls?.gofile) {
          const gofileBtn = document.createElement("a");
          gofileBtn.href = data.urls.gofile;
          gofileBtn.textContent = "GoFile";
          gofileBtn.target = "_blank";
          gofileBtn.style.cssText =
            "background: #6c757d; color: white; padding: 4px 8px; border-radius: 3px; text-decoration: none; font-size: 10px; font-weight: bold;";
          linksRow.appendChild(gofileBtn);
        }

        // Delete button
        const deleteBtn = document.createElement("button");
        deleteBtn.textContent = "Delete";
        deleteBtn.style.cssText =
          "background: #dc3545; color: white; padding: 4px 8px; border-radius: 3px; border: none; cursor: pointer; font-size: 10px; font-weight: bold;";
        deleteBtn.onclick = async (e) => {
          e.stopPropagation();
          if (confirm(`Delete cached file: ${data.filename}?`)) {
            delete cache[fileId];
            await GM.setValue(STORAGE_CACHE_KEY, JSON.stringify(cache));
            log(`Deleted ${fileId} from cache`);
            renderFiles(searchBox.value);
          }
        };
        linksRow.appendChild(deleteBtn);

        fileCard.appendChild(filename);
        fileCard.appendChild(fileSize);
        fileCard.appendChild(linksRow);
        filesContainer.appendChild(fileCard);
      });
    };

    // Search box event
    searchBox.oninput = () => renderFiles(searchBox.value);

    // Clear All button
    const clearAllBtn = document.createElement("button");
    clearAllBtn.textContent = "Clear All Cache";
    clearAllBtn.style.cssText = `
      background: #dc3545; color: white; padding: 8px; border-radius: 5px; text-align: center;
      border: none; cursor: pointer; font-weight: bold; width: 100%; margin-top: 10px; font-size: 12px;
    `;
    clearAllBtn.onclick = async () => {
      if (confirm("Are you sure you want to clear all cached files?")) {
        cache = {};
        await GM.setValue(STORAGE_CACHE_KEY, JSON.stringify(cache));
        log("Cache cleared");
        renderFiles();
      }
    };

    // Assemble modal
    modalContent.appendChild(closeBtn);
    modalContent.appendChild(title);
    modalContent.appendChild(searchBox);
    modalContent.appendChild(filesContainer);
    modalContent.appendChild(clearAllBtn);
    modal.appendChild(modalContent);
    document.body.appendChild(modal);

    // Initial render
    renderFiles();

    // Close on outside click
    setTimeout(() => {
      document.addEventListener("click", function clickHandler(e) {
        if (!modal.contains(e.target) && e.target.id !== "cache-browser-btn") {
          modal.remove();
          document.removeEventListener("click", clickHandler);
        }
      });
    }, 100);
  };

  const showSettingsUI = () => {
    // Create floating settings button
    const settingsBtn = document.createElement("button");
    settingsBtn.innerHTML = "⚙️";
    settingsBtn.title = "Settings";
    settingsBtn.style.cssText = `
        position: fixed; top: 20px; right: 20px; width: 40px; height: 40px;
        border-radius: 50%; background: #22a76d; border: none; color: white;
        cursor: pointer; z-index: 9999999999; font-size: 16px;
    `;

    // Create floating links button
    const linksBtn = document.createElement("button");
    linksBtn.innerHTML = "🔗";
    linksBtn.title = "Download Links";
    linksBtn.style.cssText = `
        position: fixed; top: 20px; right: 120px; width: 40px; height: 40px;
        border-radius: 50%; background: #007bff; border: none; color: white;
        cursor: pointer; z-index: 9999999999; font-size: 16px; display: none;
    `;

    // Links button click handler
    linksBtn.onclick = () => {
      if (currentPageLinks) {
        showModalWithLinks(currentPageLinks);
      }
    };

    // Create floating cache browser button
    const cacheBtn = document.createElement("button");
    cacheBtn.id = "cache-browser-btn";
    cacheBtn.innerHTML = "📦";
    cacheBtn.title = "Cache Browser";
    cacheBtn.style.cssText = `
        position: fixed; top: 20px; right: 70px; width: 40px; height: 40px;
        border-radius: 50%; background: #6c757d; border: none; color: white;
        cursor: pointer; z-index: 9999999999; font-size: 16px;
    `;

    // Cache button click handler
    cacheBtn.onclick = () => showCacheBrowser();

    // Function to show/hide links button based on availability
    window.toggleLinksButton = (show = false) => {
      linksBtn.style.display = show ? "block" : "none";
    };

    // Create modal
    const modal = document.createElement("div");
    modal.style.cssText = `
        display: none; position: fixed; top: 70px; right: 20px; width: 280px;
        background: var(--bg-color, #ffffff); border: 1px solid var(--border-color, #ddd); border-radius: 8px; 
        box-shadow: 0 4px 12px rgba(0,0,0,0.3); z-index: 9999999999; padding: 0;
        color: var(--text-color, #000000);
    `;

    const modalContent = document.createElement("div");
    modalContent.style.cssText = `
        background: var(--bg-color, #ffffff); padding: 20px; border-radius: 8px; position: relative;
        color: var(--text-color, #000000);
    `;

    modalContent.innerHTML = `
        <button id="close-btn" style="position: absolute; top: 5px; right: 10px; background: none; border: none; font-size: 18px; cursor: pointer; color: var(--text-muted, #999999);">×</button>
        <h3 style="margin: 0 0 15px 0; font-size: 16px; color: var(--text-color, #000000);">Settings</h3>
        <label style="display: block; margin-bottom: 10px; color: var(--text-color, #000000);">
            Fallback DL Provider:
            <select id="provider-select" style="width: 100%; margin-top: 5px; padding: 5px; font-size: 12px; background: var(--input-bg, #ffffff); color: var(--text-color, #000000); border: 1px solid var(--border-color, #ddd);">
                <option value="">None</option>
                <option value="vikingfile">VikingFile</option>
                <option value="gofile">GoFile</option>
            </select>
            <small style="color: var(--text-muted, #666666); font-size: 10px; display: block; margin-top: 3px;">Provider to use in case DDL is not available.</small>
        </label>
        <div style="display: block; margin-bottom: 10px;">
            <label id="cache-label" style="cursor: pointer; font-size: 12px; color: var(--text-color, #000000);">
                <input type="checkbox" id="cache-toggle" style="margin-right: 6px; transform: scale(0.9);"> Cache Links
            </label>
            <small id="cache-helper" style="color: var(--text-muted, #666666); font-size: 10px; display: block; margin-top: 3px; margin-left: 16px; cursor: pointer;">Save the Direct Links if visiting the same link again.</small>
        </div>
        <div style="display: block; margin-bottom: 10px;">
            <label id="captcha-alert-label" style="cursor: pointer; font-size: 12px; color: var(--text-color, #000000);">
                <input type="checkbox" id="captcha-alert-toggle" style="margin-right: 6px; transform: scale(0.9);"> Alert Captcha Available
            </label>
            <small id="captcha-alert-helper" style="color: var(--text-muted, #666666); font-size: 10px; display: block; margin-top: 3px; margin-left: 16px; cursor: pointer;">Show alert when CAPTCHA is available for solving.</small>
        </div>
        <label style="display: block; margin-bottom: 15px; font-size: 12px; color: var(--text-color, #000000);">
            Cache Expiry:
            <select id="expiry-select" style="width: 100%; margin-top: 5px; padding: 5px; font-size: 12px; background: var(--input-bg, #ffffff); color: var(--text-color, #000000); border: 1px solid var(--border-color, #ddd);">
                <option value="1hour">1 Hour</option>
                <option value="1day">1 Day</option>
                <option value="1week">1 Week</option>
                <option value="1month">1 Month</option>
                <option value="forever">Forever</option>
            </select>
        </label>
        <div style="display: flex; gap: 8px;">
            <button id="save-btn" style="background: #22a76d; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 11px; flex: 1;">Save</button>
            <button id="reset-btn" style="background: #dc3545; color: white; border: none; padding: 8px 12px; border-radius: 4px; cursor: pointer; font-size: 11px; flex: 1;">Reset</button>
        </div>
    `;

    modal.appendChild(modalContent);
    document.body.appendChild(settingsBtn);
    document.body.appendChild(linksBtn);
    document.body.appendChild(cacheBtn);
    document.body.appendChild(modal);

    // Setup theme detection and CSS custom properties
    const setupTheme = () => {
      const isDarkTheme = () => {
        // Check if the page has a dark background
        const bodyBg = getComputedStyle(document.body).backgroundColor;
        const docBg = getComputedStyle(
          document.documentElement
        ).backgroundColor;

        // Check if body or html has dark background
        const rgbValues = bodyBg.match(/\d+/g) ||
          docBg.match(/\d+/g) || [255, 255, 255];
        const brightness =
          (parseInt(rgbValues[0]) * 299 +
            parseInt(rgbValues[1]) * 587 +
            parseInt(rgbValues[2]) * 114) /
          1000;

        // Also check for common dark theme indicators
        const isDark =
          brightness < 128 ||
          document.body.classList.contains("dark") ||
          document.body.classList.contains("dark-theme") ||
          document.documentElement.classList.contains("dark") ||
          document.documentElement.classList.contains("dark-theme");

        return isDark;
      };

      // Set CSS custom properties based on theme
      const root = document.documentElement;
      if (isDarkTheme()) {
        root.style.setProperty("--bg-color", "#2d2d2d");
        root.style.setProperty("--text-color", "#ffffff");
        root.style.setProperty("--text-muted", "#cccccc");
        root.style.setProperty("--input-bg", "#404040");
        root.style.setProperty("--border-color", "#555555");
      } else {
        root.style.setProperty("--bg-color", "#ffffff");
        root.style.setProperty("--text-color", "#000000");
        root.style.setProperty("--text-muted", "#666666");
        root.style.setProperty("--input-bg", "#ffffff");
        root.style.setProperty("--border-color", "#dddddd");
      }
    };

    setupTheme();

    // Add checkbox change handler after modal is created
    const addCheckboxHandler = () => {
      const checkbox = modal.querySelector("#cache-toggle");
      const expirySelect = modal.querySelector("#expiry-select");
      const cacheLabel = modal.querySelector("#cache-label");
      const cacheHelper = modal.querySelector("#cache-helper");
      const captchaAlertCheckbox = modal.querySelector("#captcha-alert-toggle");
      const captchaAlertLabel = modal.querySelector("#captcha-alert-label");
      const captchaAlertHelper = modal.querySelector("#captcha-alert-helper");

      if (checkbox && expirySelect) {
        const toggleCheckbox = () => {
          checkbox.checked = !checkbox.checked;
          expirySelect.disabled = !checkbox.checked;
          expirySelect.style.opacity = checkbox.checked ? "1" : "0.5";
        };

        // Checkbox change event
        checkbox.onchange = () => {
          expirySelect.disabled = !checkbox.checked;
          expirySelect.style.opacity = checkbox.checked ? "1" : "0.5";
        };

        // Label click event (this handles the text "Cache Links")
        if (cacheLabel) {
          cacheLabel.onclick = (e) => {
            // Only toggle if we didn't click directly on the checkbox
            if (e.target !== checkbox) {
              toggleCheckbox();
              e.preventDefault();
              e.stopPropagation();
            }
          };
        }

        // Helper text click event
        if (cacheHelper) {
          cacheHelper.onclick = (e) => {
            toggleCheckbox();
            e.preventDefault();
            e.stopPropagation();
          };
        }
      }

      // Captcha alert checkbox handlers
      if (captchaAlertCheckbox) {
        const toggleCaptchaAlert = () => {
          captchaAlertCheckbox.checked = !captchaAlertCheckbox.checked;
        };

        // Label click event
        if (captchaAlertLabel) {
          captchaAlertLabel.onclick = (e) => {
            if (e.target !== captchaAlertCheckbox) {
              toggleCaptchaAlert();
              e.preventDefault();
              e.stopPropagation();
            }
          };
        }

        // Helper text click event
        if (captchaAlertHelper) {
          captchaAlertHelper.onclick = (e) => {
            toggleCaptchaAlert();
            e.preventDefault();
            e.stopPropagation();
          };
        }
      }
    };

    // Event handlers
    settingsBtn.onclick = async () => {
      // Load current settings
      const configString = await GM.getValue(STORAGE_SCRIPT_KEY);
      let config = DG_AUTO_SKIP_CONFIG;
      if (configString) {
        try {
          config = {
            ...DG_AUTO_SKIP_CONFIG,
            ...JSON.parse(configString),
          };
        } catch (e) {
          log("Error loading settings");
        }
      }

      modal.querySelector("#provider-select").value =
        config.fallbackDlProvider || "";
      modal.querySelector("#cache-toggle").checked =
        config.cacheDlLinks || false;
      modal.querySelector("#expiry-select").value =
        config.cacheExpiry || "1week";
      modal.querySelector("#captcha-alert-toggle").checked =
        config.alertCaptchaAvailable || false;

      // Update expiry dropdown state based on checkbox
      const updateExpiryState = () => {
        const checkbox = modal.querySelector("#cache-toggle");
        const expirySelect = modal.querySelector("#expiry-select");
        expirySelect.disabled = !checkbox.checked;
        expirySelect.style.opacity = checkbox.checked ? "1" : "0.5";
      };

      updateExpiryState();
      modal.style.display = "block";

      // Add checkbox handler after modal is displayed
      addCheckboxHandler();
    };

    // Save Handler
    modal.querySelector("#save-btn").onclick = () => {
      const newConfig = {
        fallbackDlProvider:
          modal.querySelector("#provider-select").value || null,
        cacheDlLinks: modal.querySelector("#cache-toggle").checked,
        cacheExpiry: modal.querySelector("#expiry-select").value || "1week",
        alertCaptchaAvailable: modal.querySelector("#captcha-alert-toggle")
          .checked,
      };
      handleSaveConfig(newConfig);
      modal.style.display = "none";
    };

    // Reset Handler
    modal.querySelector("#reset-btn").onclick = () => handleResetConfig();

    // Close Handler
    modal.querySelector("#close-btn").onclick = () =>
      (modal.style.display = "none");

    // Close on outside click
    document.addEventListener("click", (e) => {
      if (!settingsBtn.contains(e.target) && !modal.contains(e.target)) {
        modal.style.display = "none";
      }
    });
  };

  // One Time Setup
  loadConfig();
  loadCache();
  showSettingsUI();
  log("DropGalaxy Script Loaded");

  // Page Handlers
  const handlePage1 = () => {
    log("Handling Page 1");

    if (checkCachedFile()) {
      return;
    }

    // Click on Download Button
    const downloadBtn = document.getElementById("method_free");
    downloadBtn?.click();
  };

  const handlePage3 = () => {
    log("Handling Page 3");

    // Click on Download Button
    const downloadForm = document.getElementById("dllink");
    const url = downloadForm?.action;

    const externalLinksContainer = document.getElementById("ppdlinks");
    const externalLinks = [];
    externalLinksContainer?.childNodes?.forEach((element) => {
      if (element?.tagName === "A") {
        externalLinks.push(element.href);
      }
    });

    const { filename, fileSize, fileId } = getFileMetadata();

    saveCache({
      fileId,
      filename,
      fileSize,
      links: [...externalLinks, url],
    });

    // Add element to show the DDL
    const ddlElement = document.createElement("a");
    ddlElement.href = url;
    ddlElement.innerText = "Direct Download Link";
    ddlElement.className = "btn btn-block btn-lg btn-primary";
    ddlElement.style =
      "background: #22a76d; color: white; margin-bottom: 20px; padding-inline: 0; border: none;";

    downloadForm?.appendChild(ddlElement);

    // Open DDL in current tab
    window.location.assign(url);
  };

  const handlePage2 = () => {
    log("Handling Page 2");

    if (checkCachedFile()) {
      return;
    }

    const falseDownloadBtn = document.getElementById("downloadbtn");
    const tokenStatus = document.getElementById("tokennstatus");
    const countdown = document.getElementById("countdown");
    let isUserAlerted = false;

    // Keep clicking until enabled
    const downloadIntervalId = setInterval(() => {
      const isCaptchaAvailable =
        tokenStatus?.innerText === "click on- verify you are human...";
      log("Waiting for user to solve CAPTCHA...");

      // Alert if CAPTCHA is available and setting is enabled
      if (
        isCaptchaAvailable &&
        config.alertCaptchaAvailable &&
        !isUserAlerted
      ) {
        log("CAPTCHA is available, alerting user");
        alert("CAPTCHA is available! Please solve it.");
        isUserAlerted = true;
      }

      if (
        // If download button is enabled and CAPTCHA is solved, submit the form
        tokenStatus?.innerText === "ready! click on create download link" &&
        falseDownloadBtn?.disabled === false
      ) {
        log("Download Button Enabled, submitting form");
        // downloadBtn.click();
        document.getElementById("ff1")?.submit();
        clearInterval(downloadIntervalId);
      }
    }, 500);
  };

  const handlePage = (pageWatcherIntervalId) => {
    const page1Identifier = document.getElementById("method_free");
    const page2Identifier = document.getElementById("countdown");
    const page3Identifier = document.getElementById("dllink");

    const adblockPageIdentifier = document.querySelector(
      "body > div.container.pt-5.page.message > div > div > div"
    );
    const isAdblockPage =
      adblockPageIdentifier?.innerText === "\nAdblock Detected!";

    // If page is recognized, clear the interval to stop checking
    if (
      pageWatcherIntervalId &&
      (page1Identifier || page2Identifier || page3Identifier || isAdblockPage)
    ) {
      log("Page Identified, stopping page watcher");
      clearInterval(pageWatcherIntervalId);
      // identificationRetries = 0;
      // no need to reset retries, as it will be reset on next page load
    }

    if (page1Identifier) {
      handlePage1();
    } else if (page2Identifier) {
      handlePage2();
    } else if (page3Identifier) {
      handlePage3();
    } else if (isAdblockPage) {
      // handleAdblockPage();
      // Not implemented
    } else if (MAX_IDENTIFICATION_RETRIES > identificationRetries) {
      log("Unknown Page or Waiting for identification");
      identificationRetries++;
    } else {
      log("Max Identification Retries Reached, Stopping Page Watcher");
      clearInterval(pageWatcherIntervalId);
    }
  };

  // Keep checking the page as soon as it loads
  const startPageWatcher = () => {
    if (intervalId) {
      clearInterval(intervalId);
    }
    intervalId = setInterval(() => {
      if (shouldStopScript) {
        clearInterval(intervalId);
        return;
      }
      handlePage(intervalId);
    }, 500);
  };

  // Start watcher initially
  startPageWatcher();
})();