DropGalaxy Auto Skip

Auto skip ads and auto download on DropGalaxy

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 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();
})();