Greasy Fork

Greasy Fork is available in English.

Twitter/X Media Batch Downloader

Batch download all images and videos from a Twitter/X account, including withheld accounts, in original quality.

当前为 2025-06-19 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Twitter/X Media Batch Downloader
// @description  Batch download all images and videos from a Twitter/X account, including withheld accounts, in original quality.
// @icon         https://raw.githubusercontent.com/afkarxyz/Twitter-X-Media-Batch-Downloader/refs/heads/main/Archived/icon.svg
// @version      3.8
// @author       afkarxyz
// @namespace    https://github.com/afkarxyz/userscripts/
// @supportURL   https://github.com/afkarxyz/userscripts/issues
// @license      MIT
// @match        https://twitter.com/*
// @match        https://x.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_download
// @connect      api.gallerydl.web.id
// @connect      backup.gallerydl.web.id
// @connect      pbs.twimg.com
// @connect      video.twimg.com
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jszip.min.js
// ==/UserScript==

;(() => {
  const API_URLS = {
    DEFAULT: "https://api.gallerydl.web.id",
    BACKUP: "https://backup.gallerydl.web.id"
  }

  const ICONS = {
    ARROW_LEFT: `<svg style="width: 12px; height: 12px; margin-right: 6px; display: inline-block; vertical-align: middle;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
      <path fill="currentColor" d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"/>
    </svg>`,
    ARROW_RIGHT: `<svg style="width: 12px; height: 12px; margin-left: 6px; display: inline-block; vertical-align: middle;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 512">
      <path fill="currentColor" d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"/>
    </svg>`,
    SPARKLES: `<svg style="width: 12px; height: 12px; margin-right: 6px; display: inline-block; vertical-align: middle;" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
      <path fill="currentColor" d="M327.5 85.2c-4.5 1.7-7.5 6-7.5 10.8s3 9.1 7.5 10.8L384 128l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L448 128l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L448 64 426.8 7.5C425.1 3 420.8 0 416 0s-9.1 3-10.8 7.5L384 64 327.5 85.2zM205.1 73.3c-2.6-5.7-8.3-9.3-14.5-9.3s-11.9 3.6-14.5 9.3L123.3 187.3 9.3 240C3.6 242.6 0 248.3 0 254.6s3.6 11.9 9.3 14.5l114.1 52.7L176 435.8c2.6 5.7 8.3 9.3 14.5 9.3s11.9-3.6 14.5-9.3l52.7-114.1 114.1-52.7c5.7-2.6 9.3-8.3 9.3-14.5s-3.6-11.9-9.3-14.5L257.8 187.4 205.1 73.3zM384 384l-56.5 21.2c-4.5 1.7-7.5 6-7.5 10.8s3 9.1 7.5 10.8L384 448l21.2 56.5c1.7 4.5 6 7.5 10.8 7.5s9.1-3 10.8-7.5L448 448l56.5-21.2c4.5-1.7 7.5-6 7.5-10.8s-3-9.1-7.5-10.8L448 384l-21.2-56.5c-1.7-4.5-6-7.5-10.8-7.5s-9.1 3-10.8 7.5L384 384z"/>
    </svg>`,
    TRASH: {
      PATH: "M135.2 17.7C140.6 6.8 151.7 0 163.8 0L284.2 0c12.1 0 23.2 6.8 28.6 17.7L320 32l96 0c17.7 0 32 14.3 32 32s-14.3 32-32 32L32 96C14.3 96 0 81.7 0 64S14.3 32 32 32l96 0 7.2-14.3zM32 128l384 0 0 320c0 35.3-28.7 64-64 64L96 512c-35.3 0-64-28.7-64-64l0-320zm96 64c-8.8 0-16 7.2-16 16l0 224c0 8.8 7.2 16 16 16s16-7.2 16-16l0-224c0-8.8-7.2-16-16-16zm96 0c-8.8 0-16 7.2-16 16l0 224c0 8.8 7.2 16 16 16s16-7.2 16-16l0-224c0-8.8-7.2-16-16-16zm96 0c-8.8 0-16 7.2-16 16l0 224c0 8.8 7.2 16 16 16s16-7.2 16-16l0-224c0-8.8-7.2-16-16-16z",
      VIEWBOX: "0 0 448 512"
    },
    RESET: {
      PATH: "M463.5 224l8.5 0c13.3 0 24-10.7 24-24l0-128c0-9.7-5.8-18.5-14.8-22.2s-19.3-1.7-26.2 5.2L413.4 96.6c-87.6-86.5-228.7-86.2-315.8 1c-87.5 87.5-87.5 229.3 0 316.8s229.3 87.5 316.8 0c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0c-62.5 62.5-163.8 62.5-226.3 0s-62.5-163.8 0-226.3c62.2-62.2 162.7-62.5 225.3-1L327 183c-6.9 6.9-8.9 17.2-5.2 26.2s12.5 14.8 22.2 14.8l119.5 0z",
      VIEWBOX: "0 0 512 512"
    },
    PATREON: {
      PATH: "M489.7 153.8c-.1-65.4-51-119-110.7-138.3C304.8-8.5 207-5 136.1 28.4C50.3 68.9 23.3 157.7 22.3 246.2C21.5 319 28.7 510.6 136.9 512c80.3 1 92.3-102.5 129.5-152.3c26.4-35.5 60.5-45.5 102.4-55.9c72-17.8 121.1-74.7 121-150z",
      VIEWBOX: "0 0 512 512"
    },
    INFO: {
      PATH: "M256 48a208 208 0 1 1 0 416 208 208 0 1 1 0-416zm0 464A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336c-13.3 0-24 10.7-24 24s10.7 24 24 24l80 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-8 0 0-88c0-13.3-10.7-24-24-24l-48 0c-13.3 0-24 10.7-24 24s10.7 24 24 24l24 0 0 64-24 0zm40-144a32 32 0 1 0 0-64 32 32 0 1 0 0 64z",
      VIEWBOX: "0 0 512 512"
    },
    DOWNLOAD: {
      SECONDARY_PATH: "M0 256C0 397.4 114.6 512 256 512s256-114.6 256-256c0-17.7-14.3-32-32-32s-32 14.3-32 32c0 106-86 192-192 192S64 362 64 256c0-17.7-14.3-32-32-32s-32 14.3-32 32z",
      PRIMARY_PATH: "M390.6 185.4c12.5 12.5 12.5 32.8 0 45.3l-112 112c-12.5 12.5-32.8 12.5-45.3 0l-112-112c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L224 242.7 224 32c0-17.7 14.3-32 32-32s32 14.3 32 32l0 210.7 57.4-57.4c12.5-12.5 32.8-12.5 45.3 0z",
      VIEWBOX: "0 0 512 512"
    },
    FETCH: {
      PATH: "M374.6 214.6l-128 128c-12.5 12.5-32.8 12.5-45.3 0l-128-128c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0L192 242.7 192 32c0-17.7 14.3-32 32-32s32 14.3 32 32l0 210.7 73.4-73.4c12.5-12.5 32.8-12.5 45.3 0s12.5 32.8 0 45.3zM64 352l0 64c0 17.7 14.3 32 32 32l256 0c17.7 0 32-14.3 32-32l0-64c0-17.7 14.3-32 32-32s32 14.3 32 32l0 64c0 53-43 96-96 96L96 512c-53 0-96-43-96-96l0-64c0-17.7 14.3-32 32-32s32 14.3 32 32z",
      VIEWBOX: "0 0 448 512"
    },
    MEDIA_TYPE: {
      ALL: {
        PATH: "M256 48c-8.8 0-16 7.2-16 16l0 224c0 8.7 6.9 15.8 15.6 16l69.1-94.2c4.5-6.2 11.7-9.8 19.4-9.8s14.8 3.6 19.4 9.8L380 232.4l56-85.6c4.4-6.8 12-10.9 20.1-10.9s15.7 4.1 20.1 10.9L578.7 303.8c7.6-1.3 13.3-7.9 13.3-15.8l0-224c0-8.8-7.2-16-16-16L256 48zM192 64c0-35.3 28.7-64 64-64L576 0c35.3 0 64 28.7 64 64l0 224c0 35.3-28.7 64-64 64l-320 0c-35.3 0-64-28.7-64-64l0-224zm-56 64l24 0 0 48 0 88 0 112 0 8 0 80 192 0 0-80 48 0 0 80 48 0c8.8 0 16-7.2 16-16l0-64 48 0 0 64c0 35.3-28.7 64-64 64l-48 0-24 0-24 0-192 0-24 0-24 0-48 0c-35.3 0-64-28.7-64-64L0 192c0-35.3 28.7-64 64-64l48 0 24 0zm-24 48l-48 0c-8.8 0-16 7.2-16 16l0 48 64 0 0-64zm0 288l0-64-64 0 0 48c0 8.8 7.2 16 16 16l48 0zM48 352l64 0 0-64-64 0 0 64zM304 80a32 32 0 1 1 0 64 32 32 0 1 1 0-64z",
        VIEWBOX: "0 0 640 512"
      },
      IMAGE: {
        PATH: "M448 80c8.8 0 16 7.2 16 16l0 319.8-5-6.5-136-176c-4.5-5.9-11.6-9.3-19-9.3s-14.4 3.4-19 9.3L202 340.7l-30.5-42.7C167 291.7 159.8 288 152 288s-15 3.7-19.5 10.1l-80 112L48 416.3l0-.3L48 96c0-8.8 7.2-16 16-16l384 0zM64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zm80 192a48 48 0 1 0 0-96 48 48 0 1 0 0 96z",
        VIEWBOX: "0 0 512 512"
      },
      VIDEO: {
        PATH: "M352 432l-192 0 0-112 0-40 192 0 0 40 0 112zm0-200l-192 0 0-40 0-112 192 0 0 112 0 40zM64 80l48 0 0 88-64 0 0-72c0-8.8 7.2-16 16-16zM48 216l64 0 0 80-64 0 0-80zm64 216l-48 0c-8.8 0-16-7.2-16-16l0-72 64 0 0 88zM400 168l0-88 48 0c8.8 0 16 7.2 16 16l0 72-64 0zm0 48l64 0 0 80-64 0 0-80zm0 128l64 0 0 72c0 8.8-7.2 16-16 16l-48 0 0-88zM448 32L64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l384 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64z",
        VIEWBOX: "0 0 512 512"
      },
      GIF: {
        PATH: "M512 80c8.8 0 16 7.2 16 16l0 320c0 8.8-7.2 16-16 16L64 432c-8.8 0-16-7.2-16-16L48 96c0-8.8 7.2-16 16-16l448 0zM64 32C28.7 32 0 60.7 0 96L0 416c0 35.3 28.7 64 64 64l448 0c35.3 0 64-28.7 64-64l0-320c0-35.3-28.7-64-64-64L64 32zm296 160c-13.3 0-24 10.7-24 24l0 144c0 13.3 10.7 24 24 24s24-10.7 24-24l0-144c0-13.3-10.7-24-24-24zm56 24l0 80 0 64c0 13.3 10.7 24 24 24s24-10.7 24-24l0-40 40 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-40 0 0-32 64 0c13.3 0 24-10.7 24-24s-10.7-24-24-24l-88 0c-13.3 0-24 10.7-24 24zM128 256c0-26.5 21.5-48 48-48c8 0 15.4 1.9 22 5.3c11.8 6.1 26.3 1.5 32.3-10.3s1.5-26.3-10.3-32.3c-13.2-6.8-28.2-10.7-44-10.7c-53 0-96 43-96 96s43 96 96 96c19.6 0 37.5-6.1 52.8-15.8c7-4.4 11.2-12.1 11.2-20.3l0-51.9c0-13.3-10.7-24-24-24l-32 0c-13.3 0-24 10.7-24 24s10.7 24 24 24l8 0 0 13.1c-5.3 1.9-10.6 2.9-16 2.9c-26.5 0-48-21.5-48-48z",
        VIEWBOX: "0 0 576 512"
      }
    }
  }

  const defaultSettings = {
    patreonAuth: "",
    authToken: "",
    batchEnabled: false,
    batchSize: 100,
    startingBatch: 0,
    timelineType: "media",
    mediaType: "all",
    concurrentDownloads: 25,
    cacheDuration: 360,
    apiServer: "default"
  }

  const batchSizes = [25, 50, 100, 200]
  const concurrentSizes = [5, 10, 20, 25, 50]
  const cacheDurations = [60, 120, 180, 240, 300, 360, 720, 1440]
  function getSettings() {
    return {
      patreonAuth: GM_getValue("patreonAuth", defaultSettings.patreonAuth),
      authToken: GM_getValue("authToken", defaultSettings.authToken),
      batchEnabled: GM_getValue("batchEnabled", defaultSettings.batchEnabled),
      batchSize: GM_getValue("batchSize", defaultSettings.batchSize),
      startingBatch: GM_getValue("startingBatch", defaultSettings.startingBatch),
      timelineType: GM_getValue("timelineType", defaultSettings.timelineType),
      mediaType: GM_getValue("mediaType", defaultSettings.mediaType),
      concurrentDownloads: GM_getValue("concurrentDownloads", defaultSettings.concurrentDownloads),
      cacheDuration: GM_getValue("cacheDuration", defaultSettings.cacheDuration),
      apiServer: GM_getValue("apiServer", defaultSettings.apiServer),
    }
  }

  function saveSettings(settings) {
    GM_setValue("patreonAuth", settings.patreonAuth)
    GM_setValue("authToken", settings.authToken)
    GM_setValue("batchEnabled", settings.batchEnabled)
    GM_setValue("batchSize", settings.batchSize)
    GM_setValue("startingBatch", settings.startingBatch)
    GM_setValue("timelineType", settings.timelineType)
    GM_setValue("mediaType", settings.mediaType)
    GM_setValue("concurrentDownloads", settings.concurrentDownloads)
    GM_setValue("cacheDuration", settings.cacheDuration)
    GM_setValue("apiServer", settings.apiServer)
  }

  function getServiceBaseUrl() {
    const settings = getSettings()
    return settings.apiServer === "default"
      ? API_URLS.DEFAULT
      : API_URLS.BACKUP
  }

  function formatNumber(num) {
    return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")
  }

  const cacheManager = {
    set: (key, data, success = true) => {
      if (!success) return;

      const settings = getSettings()
      const cacheItem = {
        data: data,
        timestamp: Date.now(),
        expiry: Date.now() + settings.cacheDuration * 60 * 1000,
      }
      localStorage.setItem(`twitter_dl_${key}`, JSON.stringify(cacheItem))
    },

    get: (key) => {
      const cacheItem = localStorage.getItem(`twitter_dl_${key}`)
      if (!cacheItem) return null

      try {
        const parsed = JSON.parse(cacheItem)
        if (Date.now() > parsed.expiry) {
          localStorage.removeItem(`twitter_dl_${key}`)
          return null
        }
        return parsed.data
      } catch (e) {
        localStorage.removeItem(`twitter_dl_${key}`)
        return null
      }
    },

    clear: () => {
      const keysToRemove = []
      for (let i = 0; i < localStorage.length; i++) {
        const key = localStorage.key(i)
        if (key.startsWith("twitter_dl_")) {
          keysToRemove.push(key)
        }
      }

      keysToRemove.forEach((key) => localStorage.removeItem(key))
    },
  }

  function createDownloadIcon() {
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    svg.setAttribute("xmlns", "http://www.w3.org/2000/svg")
    svg.setAttribute("viewBox", ICONS.DOWNLOAD.VIEWBOX)
    svg.setAttribute("width", "18")
    svg.setAttribute("height", "18")
    svg.style.verticalAlign = "middle"
    svg.style.cursor = "pointer"

    const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs")
    const style = document.createElementNS("http://www.w3.org/2000/svg", "style")
    style.textContent = ".fa-secondary{opacity:.4}"
    defs.appendChild(style)
    svg.appendChild(defs)

    const secondaryPath = document.createElementNS("http://www.w3.org/2000/svg", "path")
    secondaryPath.setAttribute("class", "fa-secondary")
    secondaryPath.setAttribute("fill", "currentColor")
    secondaryPath.setAttribute("d", ICONS.DOWNLOAD.SECONDARY_PATH)
    svg.appendChild(secondaryPath)

    const primaryPath = document.createElementNS("http://www.w3.org/2000/svg", "path")
    primaryPath.setAttribute("class", "fa-primary")
    primaryPath.setAttribute("fill", "currentColor")
    primaryPath.setAttribute("d", ICONS.DOWNLOAD.PRIMARY_PATH)
    svg.appendChild(primaryPath)

    return svg
  }
  
  function createPatreonIcon() {
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    svg.setAttribute("xmlns", "http://www.w3.org/2000/svg")
    svg.setAttribute("viewBox", ICONS.PATREON.VIEWBOX)
    svg.setAttribute("width", "18")
    svg.setAttribute("height", "18")
    svg.style.verticalAlign = "middle"
    svg.style.marginRight = "8px"

    const path = document.createElementNS("http://www.w3.org/2000/svg", "path")
    path.setAttribute("fill", "currentColor")
    path.setAttribute("d", ICONS.PATREON.PATH)
    svg.appendChild(path)

    return svg
  }

  function createInfoIcon() {
    const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    svg.setAttribute("xmlns", "http://www.w3.org/2000/svg")
    svg.setAttribute("viewBox", ICONS.INFO.VIEWBOX)
    svg.setAttribute("width", "16")
    svg.setAttribute("height", "16")
    svg.style.marginLeft = "8px"
    svg.style.cursor = "pointer"
    svg.style.color = "#64748b"
    svg.style.transition = "color 0.2s ease"

    const path = document.createElementNS("http://www.w3.org/2000/svg", "path")
    path.setAttribute("fill", "currentColor")
    path.setAttribute("d", ICONS.INFO.PATH)
    svg.appendChild(path)

    svg.addEventListener("mouseenter", () => {
      svg.style.color = "#0ea5e9"
    })

    svg.addEventListener("mouseleave", () => {
      svg.style.color = "#64748b"
    })

    return svg
  }

  function createInfoTooltip(message) {
    let activeTooltip = null

    function showTooltip(e) {
      if (activeTooltip) {
        document.body.removeChild(activeTooltip)
      }

      const tooltip = document.createElement("div")
      tooltip.textContent = message
      tooltip.style.cssText = `
        position: fixed;
        background-color: #1f2937;
        color: white;
        padding: 8px 12px;
        border-radius: 6px;
        font-size: 12px;
        line-height: 1.4;
        max-width: 300px;
        z-index: 10003;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
        pointer-events: none;
        white-space: normal;
        word-wrap: break-word;
      `

      document.body.appendChild(tooltip)
      activeTooltip = tooltip

      const rect = e.target.getBoundingClientRect()
      const tooltipRect = tooltip.getBoundingClientRect()

      let top = rect.bottom + 8
      let left = rect.left + (rect.width / 2) - (tooltipRect.width / 2)

      if (left < 8) left = 8
      if (left + tooltipRect.width > window.innerWidth - 8) {
        left = window.innerWidth - tooltipRect.width - 8
      }
      if (top + tooltipRect.height > window.innerHeight - 8) {
        top = rect.top - tooltipRect.height - 8
      }

      tooltip.style.top = top + "px"
      tooltip.style.left = left + "px"
    }

    function hideTooltip() {
      if (activeTooltip) {
        document.body.removeChild(activeTooltip)
        activeTooltip = null
      }
    }

    return { showTooltip, hideTooltip }
  }

  function createAuthTokenPopup() {
    const overlay = document.createElement("div")
    overlay.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.35);
        backdrop-filter: blur(2.5px);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 10001;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    `

    const popup = document.createElement("div")
    popup.style.cssText = `
        background-color: #ffffff;
        color: #0f172a;
        border-radius: 16px;
        width: 300px;
        max-width: 90%;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
        overflow: hidden;
    `

    const header = document.createElement("div")
    header.style.cssText = `
        padding: 16px;
        border-bottom: 1px solid #e2e8f0;
        font-weight: bold;
        font-size: 16px;
        text-align: center;
    `
    header.textContent = "Authentication Required"

    const content = document.createElement("div")
    content.style.cssText = `
        padding: 16px;
        text-align: center;
    `

    const authLink = document.createElement("a")
    authLink.href = "https://www.patreon.com/posts/127206894"
    authLink.target = "_blank"
    authLink.textContent = "How to Obtain Auth Token"
    authLink.style.cssText = `
        color: #0ea5e9;
        text-decoration: none;
        cursor: pointer;
    `
    content.appendChild(authLink)

    const buttonContainer = document.createElement("div")
    buttonContainer.style.cssText = `
        padding: 16px;
        display: flex;
        justify-content: center;
        border-top: 1px solid #e2e8f0;
    `

    const okButton = document.createElement("button")
    okButton.style.cssText = `
        background-color: #0ea5e9;
        color: white;
        border: none;
        border-radius: 9999px;
        padding: 8px 24px;
        font-weight: bold;
        cursor: pointer;
        transition: background-color 0.2s;
    `
    okButton.textContent = "OK"
    okButton.addEventListener("mouseenter", () => {
      okButton.style.backgroundColor = "#0284c7"
    })
    okButton.addEventListener("mouseleave", () => {
      okButton.style.backgroundColor = "#0ea5e9"
    })
    okButton.onclick = () => {
      document.body.removeChild(overlay)
      settingsTab.click()
    }

    buttonContainer.appendChild(okButton)
    popup.appendChild(header)
    popup.appendChild(content)
    popup.appendChild(buttonContainer)
    overlay.appendChild(popup)

    document.body.appendChild(overlay)
    return overlay
  }

  function createPatreonAuthPopup() {
    const overlay = document.createElement("div")
    overlay.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.35);
        backdrop-filter: blur(2.5px);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 10001;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    `

    const popup = document.createElement("div")
    popup.style.cssText = `
        background-color: #ffffff;
        color: #0f172a;
        border-radius: 16px;
        width: 320px;
        max-width: 90%;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
        overflow: hidden;
    `

    const header = document.createElement("div")
    header.style.cssText = `
        padding: 16px;
        border-bottom: 1px solid #e2e8f0;
        font-weight: bold;
        font-size: 16px;
        text-align: center;
    `
    header.textContent = "Patreon Authentication Required"

    const content = document.createElement("div")
    content.style.cssText = `
        padding: 16px;
        text-align: center;
    `

    const message = document.createElement("p")
    message.style.cssText = `
        margin-bottom: 16px;
        line-height: 1.5;
    `
    message.textContent = "Please enter your Patreon authentication code. This feature requires a paid membership to access."
    content.appendChild(message)

    const patreonButton = document.createElement("a")
    patreonButton.href = "https://www.patreon.com/exyezed"
    patreonButton.target = "_blank"
    patreonButton.style.cssText = `
        display: flex;
        align-items: center;
        justify-content: center;
        background-color: #f1f5f9;
        color: #0f172a;
        text-decoration: none;
        padding: 10px 16px;
        border-radius: 8px;
        margin-top: 8px;
        transition: background-color 0.2s;
    `
    patreonButton.innerHTML = createPatreonIcon().outerHTML + "Join Patreon Membership"
    patreonButton.addEventListener("mouseenter", () => {
      patreonButton.style.backgroundColor = "#e2e8f0"
    })
    patreonButton.addEventListener("mouseleave", () => {
      patreonButton.style.backgroundColor = "#f1f5f9"
    })
    content.appendChild(patreonButton)

    const buttonContainer = document.createElement("div")
    buttonContainer.style.cssText = `
        padding: 16px;
        display: flex;
        justify-content: center;
        border-top: 1px solid #e2e8f0;
    `

    const okButton = document.createElement("button")
    okButton.style.cssText = `
        background-color: #0ea5e9;
        color: white;
        border: none;
        border-radius: 9999px;
        padding: 8px 24px;
        font-weight: bold;
        cursor: pointer;
        transition: background-color 0.2s;
    `
    okButton.textContent = "OK"
    okButton.addEventListener("mouseenter", () => {
      okButton.style.backgroundColor = "#0284c7"
    })
    okButton.addEventListener("mouseleave", () => {
      okButton.style.backgroundColor = "#0ea5e9"
    })
    okButton.onclick = () => {
      document.body.removeChild(overlay)
      settingsTab.click()
    }

    buttonContainer.appendChild(okButton)
    popup.appendChild(header)
    popup.appendChild(content)
    popup.appendChild(buttonContainer)
    overlay.appendChild(popup)

    document.body.appendChild(overlay)
    return overlay
  }

  function createConfirmDialog(message, onConfirm, onCancel) {
    const overlay = document.createElement("div")
    overlay.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.35);
        backdrop-filter: blur(2.5px);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 10001;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    `

    const dialog = document.createElement("div")
    dialog.style.cssText = `
        background-color: #ffffff;
        color: #334155;
        border-radius: 16px;
        width: 300px;
        max-width: 90%;
        box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
        overflow: hidden;
    `

    const header = document.createElement("div")
    header.style.cssText = `
        padding: 16px;
        border-bottom: 1px solid #e2e8f0;
        font-weight: bold;
        font-size: 16px;
        text-align: center;
    `
    header.textContent = "Confirmation"

    const content = document.createElement("div")
    content.style.cssText = `
        padding: 16px;
        text-align: center;
    `
    content.textContent = message

    const buttons = document.createElement("div")
    buttons.style.cssText = `
        display: flex;
        padding: 16px;
        border-top: 1px solid #e2e8f0;
    `

    const cancelButton = document.createElement("button")
    cancelButton.style.cssText = `
        flex: 1;
        background-color: #94a3b8;
        color: white;
        border: none;
        border-radius: 9999px;
        padding: 8px 16px;
        margin-right: 8px;
        font-weight: bold;
        cursor: pointer;
        text-align: center;
        transition: background-color 0.2s;
    `
    cancelButton.textContent = "No"
    cancelButton.addEventListener("mouseenter", () => {
      cancelButton.style.backgroundColor = "#64748b"
    })
    cancelButton.addEventListener("mouseleave", () => {
      cancelButton.style.backgroundColor = "#94a3b8"
    })
    cancelButton.onclick = () => {
      document.body.removeChild(overlay)
      if (onCancel) onCancel()
    }

    const confirmButton = document.createElement("button")
    confirmButton.style.cssText = `
        flex: 1;
        background-color: #ef4444;
        color: white;
        border: none;
        border-radius: 9999px;
        padding: 8px 16px;
        font-weight: bold;
        cursor: pointer;
        text-align: center;
        transition: background-color 0.2s;
    `
    confirmButton.textContent = "Yes"
    confirmButton.addEventListener("mouseenter", () => {
      confirmButton.style.backgroundColor = "#dc2626"
    })
    confirmButton.addEventListener("mouseleave", () => {
      confirmButton.style.backgroundColor = "#ef4444"
    })
    confirmButton.onclick = () => {
      document.body.removeChild(overlay)
      if (onConfirm) onConfirm()
    }

    buttons.appendChild(cancelButton)
    buttons.appendChild(confirmButton)

    dialog.appendChild(header)
    dialog.appendChild(content)
    dialog.appendChild(buttons)
    overlay.appendChild(dialog)

    document.body.appendChild(overlay)
  }

  function formatDate(dateString) {
    const date = new Date(dateString)
    const year = date.getFullYear()
    const month = String(date.getMonth() + 1).padStart(2, "0")
    const day = String(date.getDate()).padStart(2, "0")
    const hours = String(date.getHours()).padStart(2, "0")
    const minutes = String(date.getMinutes()).padStart(2, "0")
    const seconds = String(date.getSeconds()).padStart(2, "0")

    return `${year}${month}${day}_${hours}${minutes}${seconds}`
  }

  function getCurrentTimestamp() {
    const now = new Date()
    const year = now.getFullYear()
    const month = String(now.getMonth() + 1).padStart(2, "0")
    const day = String(now.getDate()).padStart(2, "0")
    const hours = String(now.getHours()).padStart(2, "0")
    const minutes = String(now.getMinutes()).padStart(2, "0")
    const seconds = String(now.getSeconds()).padStart(2, "0")

    return `${year}${month}${day}_${hours}${minutes}${seconds}`
  }

  function fetchData(url) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url: url,
        responseType: "json",
        onload: (response) => {
          if (response.status >= 200 && response.status < 300) {
            resolve(response.response)
          } else {
            reject(new Error(`Request failed with status ${response.status}`))
          }
        },
        onerror: (error) => {
          reject(new Error(`Network error: ${error?.message || "Unknown error"}`))
        },
        ontimeout: () => {
          reject(new Error("Request timed out"))
        }
      })
    })
  }

  function fetchBinary(url) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url: url,
        responseType: "blob",
        onload: (response) => {
          if (response.status >= 200 && response.status < 300) {
            resolve(response.response)
          } else {
            reject(new Error(`Request failed with status ${response.status}`))
          }
        },
        onerror: () => {
          reject(new Error("Network error"))
        },
      })
    })
  }

  function getMediaTypeLabel(mediaType) {
    switch (mediaType) {
      case "image":
        return "Image"
      case "video":
        return "Video"
      case "gif":
        return "GIF"
      default:
        return "Media"
    }
  }

  function createToggleSwitch(options, selectedValue, onChange) {
    const toggleWrapper = document.createElement("div")
    toggleWrapper.style.cssText = `
      position: relative;
      height: 40px;
      background-color: #f1f5f9;
      border-radius: 8px;
      padding: 0;
      cursor: pointer;
      width: 100%;
      margin-bottom: 16px;
      overflow: hidden;
    `

    const toggleSlider = document.createElement("div")
    toggleSlider.style.cssText = `
      position: absolute;
      height: 100%;
      background-color: #0ea5e9;
      border-radius: 8px;
      transition: transform 0.3s ease, width 0.3s ease;
      z-index: 1;
    `

    const optionsContainer = document.createElement("div")
    optionsContainer.style.cssText = `
      position: relative;
      display: flex;
      height: 100%;
      z-index: 2;
      width: 100%;
    `

    const selectedIndex = options.findIndex((option) => option.value === selectedValue)
    const optionWidth = 100 / options.length

    toggleSlider.style.width = `${optionWidth}%`
    toggleSlider.style.transform = `translateX(${selectedIndex * 100}%)`

    options.forEach((option, index) => {
      const optionElement = document.createElement("div")
      optionElement.style.cssText = `
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 14px;
        transition: color 0.3s ease;
        color: ${option.value === selectedValue ? "white" : "#64748b"};
        cursor: pointer;
        user-select: none;
        text-align: center;
        height: 100%;
        padding: 0 4px;
      `

      if (option.icon) {
        const iconContainer = document.createElement("span")
        iconContainer.style.cssText = `
          display: flex;
          align-items: center;
          justify-content: center;
          margin-right: 6px;
        `

        const iconClone = option.icon.cloneNode(true)

        const paths = iconClone.querySelectorAll("path")
        paths.forEach((path) => {
          path.setAttribute("fill", option.value === selectedValue ? "white" : "#64748b")
        })

        iconContainer.appendChild(iconClone)
        optionElement.appendChild(iconContainer)
      }

      const text = document.createElement("span")
      text.textContent = option.label
      text.style.cssText = `
        display: inline-block;
        text-align: center;
      `
      optionElement.appendChild(text)

      optionElement.addEventListener("click", (e) => {
        e.stopPropagation()
        onChange(option.value)

        toggleSlider.style.transform = `translateX(${index * 100}%)`

        optionsContainer.querySelectorAll("div").forEach((opt, i) => {
          opt.style.color = i === index ? "white" : "#64748b"

          const optIcon = opt.querySelector("svg")
          if (optIcon) {
            const optPaths = optIcon.querySelectorAll("path")
            optPaths.forEach((path) => {
              path.setAttribute("fill", i === index ? "white" : "#64748b")
            })
          }
        })
      })

      optionsContainer.appendChild(optionElement)
    })

    toggleWrapper.appendChild(toggleSlider)
    toggleWrapper.appendChild(optionsContainer)

    return toggleWrapper
  }

  function createMediaTypeIcons() {
    const allIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    allIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg")
    allIcon.setAttribute("viewBox", ICONS.MEDIA_TYPE.ALL.VIEWBOX)
    allIcon.setAttribute("width", "16")
    allIcon.setAttribute("height", "16")
    allIcon.style.verticalAlign = "middle"

    const allPath = document.createElementNS("http://www.w3.org/2000/svg", "path")
    allPath.setAttribute("fill", "#64748b")
    allPath.setAttribute("d", ICONS.MEDIA_TYPE.ALL.PATH)
    allIcon.appendChild(allPath)

    const imageIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    imageIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg")
    imageIcon.setAttribute("viewBox", ICONS.MEDIA_TYPE.IMAGE.VIEWBOX)
    imageIcon.setAttribute("width", "16")
    imageIcon.setAttribute("height", "16")
    imageIcon.style.verticalAlign = "middle"

    const imagePath = document.createElementNS("http://www.w3.org/2000/svg", "path")
    imagePath.setAttribute("fill", "#64748b")
    imagePath.setAttribute("d", ICONS.MEDIA_TYPE.IMAGE.PATH)
    imageIcon.appendChild(imagePath)

    const videoIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    videoIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg")
    videoIcon.setAttribute("viewBox", ICONS.MEDIA_TYPE.VIDEO.VIEWBOX)
    videoIcon.setAttribute("width", "16")
    videoIcon.setAttribute("height", "16")
    videoIcon.style.verticalAlign = "middle"

    const videoPath = document.createElementNS("http://www.w3.org/2000/svg", "path")
    videoPath.setAttribute("fill", "#64748b")
    videoPath.setAttribute("d", ICONS.MEDIA_TYPE.VIDEO.PATH)
    videoIcon.appendChild(videoPath)

    const gifIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    gifIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg")
    gifIcon.setAttribute("viewBox", ICONS.MEDIA_TYPE.GIF.VIEWBOX)
    gifIcon.setAttribute("width", "16")
    gifIcon.setAttribute("height", "16")
    gifIcon.style.verticalAlign = "middle"

    const gifPath = document.createElementNS("http://www.w3.org/2000/svg", "path")
    gifPath.setAttribute("fill", "#64748b")
    gifPath.setAttribute("d", ICONS.MEDIA_TYPE.GIF.PATH)
    gifIcon.appendChild(gifPath)

    return {
      all: allIcon,
      image: imageIcon,
      video: videoIcon,
      gif: gifIcon
    }
  }

  function createSlider(options, selectedValue, onChange) {
    const toggleOptions = options.map((option) => {
      let label = option.toString()
      if (typeof option === "number" && option >= 60 && option % 60 === 0) {
        label = `${option / 60}h`
      }
      return { value: option, label: label }
    })

    return createToggleSwitch(toggleOptions, selectedValue, onChange)
  }

  function createModal(username) {
    const existingModal = document.getElementById("media-downloader-modal")
    if (existingModal) {
      existingModal.remove()
    }

    const settings = getSettings()

    const modal = document.createElement("div")
    modal.id = "media-downloader-modal"
    modal.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 0.35);
        backdrop-filter: blur(2.5px);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 10000;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    `

    const modalContent = document.createElement("div")
    modalContent.style.cssText = `
    background-color: #ffffff;
    color: #334155;
    border-radius: 16px;
    width: 500px;
    max-width: 90%;
    max-height: 90vh;
    overflow-y: auto;
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
    `

    const header = document.createElement("div")
    header.style.cssText = `
        display: flex;
        justify-content: space-between;
        align-items: center;
        padding: 16px;
        border-bottom: 1px solid #e2e8f0;
    `

    const title = document.createElement("h2")
    title.innerHTML = `Download ${getMediaTypeLabel(settings.mediaType)}: <span style="color: #0ea5e9">${username}</span>`
    title.style.cssText = `
    margin: 0;
    font-size: 18px;
    font-weight: bold;
    color: #334155;
    `

    const closeButton = document.createElement("button")
    closeButton.innerHTML = "&times;"
    closeButton.style.cssText = `
        background: none;
        border: none;
        color: #0f172a;
        font-size: 24px;
        cursor: pointer;
        padding: 0;
        line-height: 1;
        transition: color 0.2s;
    `
    closeButton.addEventListener("mouseenter", () => {
      closeButton.style.color = "#0ea5e9"
    })
    closeButton.addEventListener("mouseleave", () => {
      closeButton.style.color = "#0f172a"
    })
    closeButton.onclick = () => modal.remove()

    header.appendChild(title)
    header.appendChild(closeButton)

    const tabs = document.createElement("div")
    tabs.style.cssText = `
        display: flex;
        border-bottom: 1px solid #e2e8f0;
    `

    const mainTab = document.createElement("div")
    mainTab.textContent = "Main"
    mainTab.className = "active-tab"
    mainTab.style.cssText = `
        padding: 12px 16px;
        cursor: pointer;
        flex: 1;
        text-align: center;
        border-bottom: 2px solid #0ea5e9;
    `

    const settingsTab = document.createElement("div")
    settingsTab.textContent = "Settings"
    settingsTab.style.cssText = `
        padding: 12px 16px;
        cursor: pointer;
        flex: 1;
        text-align: center;
        color: #64748b;
    `

    tabs.appendChild(mainTab)
    tabs.appendChild(settingsTab)

    const mainContent = document.createElement("div")
    mainContent.style.cssText = `
    padding: 16px;
    `

    const settingsContent = document.createElement("div")
    settingsContent.style.cssText = `
    padding: 16px;
    display: none;
    `

    const fetchButton = document.createElement("button")
    const mediaTypeLabelText = getMediaTypeLabel(settings.mediaType).toLowerCase()
    const fetchIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    fetchIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg")
    fetchIcon.setAttribute("viewBox", ICONS.FETCH.VIEWBOX)
    fetchIcon.setAttribute("width", "16")
    fetchIcon.setAttribute("height", "16")
    fetchIcon.style.marginRight = "8px"

    const fetchPath = document.createElementNS("http://www.w3.org/2000/svg", "path")
    fetchPath.setAttribute("fill", "currentColor")
    fetchPath.setAttribute("d", ICONS.FETCH.PATH)
    fetchIcon.appendChild(fetchPath)

    const fetchButtonText = document.createElement("span")
    fetchButtonText.textContent =
      settings.mediaType === "all"
        ? "Fetch Media"
        : `Fetch ${mediaTypeLabelText === "gif" ? "GIF" : mediaTypeLabelText.charAt(0).toUpperCase() + mediaTypeLabelText.slice(1)}`

    fetchButton.innerHTML = ""
    fetchButton.appendChild(fetchIcon)
    fetchButton.appendChild(fetchButtonText)

    fetchButton.style.cssText = `
        background-color: #22c55e;
        color: white;
        border: none;
        border-radius: 6px;
        padding: 8px 16px;
        font-weight: bold;
        cursor: pointer;
        margin: 16px auto;
        width: 50%;
        display: flex;
        justify-content: center;
        align-items: center;
        text-align: center;
        transition: background-color 0.2s;
    `

    fetchButton.addEventListener("mouseenter", () => {
      fetchButton.style.backgroundColor = "#16a34a"
    })

    fetchButton.addEventListener("mouseleave", () => {
      fetchButton.style.backgroundColor = "#22c55e"
    })

    const infoContainer = document.createElement("div")
    infoContainer.style.cssText = `
        background-color: #f1f5f9;
        border-radius: 8px;
        padding: 12px;
        margin-bottom: 16px;
        display: none;
    `

    const buttonContainer = document.createElement("div")
    buttonContainer.style.cssText = `
        display: none;
        gap: 8px;
        margin-bottom: 16px;
    `

    const batchNavContainer = document.createElement("div")
    batchNavContainer.style.cssText = `
        display: none;
        gap: 8px;
        margin-bottom: 16px;
        flex-wrap: wrap;
    `
    const prevBatchButton = document.createElement("button")
    prevBatchButton.innerHTML = `
        ${ICONS.ARROW_LEFT}
        <span class="desktop-text">Prev Batch</span>
        <span class="mobile-text" style="display: none;">Prev</span>
    `

    prevBatchButton.style.cssText = `
        background-color: #6366f1;
        color: white;
        border: none;
        border-radius: 6px;
        padding: 8px 16px;
        font-weight: bold;
        cursor: pointer;
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
        text-align: center;
        transition: background-color 0.2s;
    `

    prevBatchButton.addEventListener("mouseenter", () => {
      prevBatchButton.style.backgroundColor = "#4f46e5"
    })
    prevBatchButton.addEventListener("mouseleave", () => {
      prevBatchButton.style.backgroundColor = "#6366f1"
    })
    const nextBatchButton = document.createElement("button")
    nextBatchButton.innerHTML = `
        <span class="desktop-text">Next Batch</span>
        <span class="mobile-text" style="display: none;">Next</span>
        ${ICONS.ARROW_RIGHT}
    `
    nextBatchButton.style.cssText = `
        background-color: #6366f1;
        color: white;
        border: none;
        border-radius: 6px;
        padding: 8px 16px;
        font-weight: bold;
        cursor: pointer;
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
        text-align: center;
        transition: background-color 0.2s;
    `
      nextBatchButton.addEventListener("mouseenter", () => {
      nextBatchButton.style.backgroundColor = "#4f46e5"
    })
    nextBatchButton.addEventListener("mouseleave", () => {
      nextBatchButton.style.backgroundColor = "#6366f1"
    })

    const autoBatchButton = document.createElement("button")
    autoBatchButton.innerHTML = `
        ${ICONS.SPARKLES}
        <span class="desktop-text">Auto Batch</span>
        <span class="mobile-text" style="display: none;">Auto</span>
    `
    autoBatchButton.style.cssText = `
        background-color: #6366f1;
        color: white;
        border: none;
        border-radius: 6px;
        padding: 8px 16px;
        font-weight: bold;
        cursor: pointer;
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
        text-align: center;
        transition: background-color 0.2s;
    `
      autoBatchButton.addEventListener("mouseenter", () => {
      autoBatchButton.style.backgroundColor = "#4f46e5"
    })

    autoBatchButton.addEventListener("mouseleave", () => {
      autoBatchButton.style.backgroundColor = "#6366f1"
    })

    batchNavContainer.appendChild(prevBatchButton)
    batchNavContainer.appendChild(autoBatchButton)
    batchNavContainer.appendChild(nextBatchButton)

    if (!document.getElementById("nav-responsive-styles")) {
        const responsiveStyle = document.createElement("style")
        responsiveStyle.id = "nav-responsive-styles"
        responsiveStyle.textContent = `
            @media (max-width: 640px) {
                .desktop-text {
                    display: none !important;
                }
                .mobile-text {
                    display: inline !important;
                }
            }
            @media (min-width: 641px) {
                .desktop-text {
                    display: inline !important;
                }
                .mobile-text {
                    display: none !important;
                }
            }
        `
        document.head.appendChild(responsiveStyle)
    }

    const batchInfoContainer = document.createElement("div")
    batchInfoContainer.style.cssText = `
        display: none;
        flex-direction: column;
        gap: 8px;
        margin-bottom: 16px;
        padding: 12px;
        background-color: #f8fafc;
        border-radius: 8px;
        border: 1px solid #e2e8f0;
    `

    const batchStatsRow = document.createElement("div")
    batchStatsRow.style.cssText = `
        display: flex;
        justify-content: space-between;
        align-items: center;
    `

    const currentBatchLabel = document.createElement("div")
    currentBatchLabel.style.cssText = `
        font-size: 14px;
        color: #475569;
    `

    const totalBatchLabel = document.createElement("div")
    totalBatchLabel.style.cssText = `
        font-size: 14px;
        color: #475569;
    `

    batchStatsRow.appendChild(currentBatchLabel)
    batchStatsRow.appendChild(totalBatchLabel)

    const downloadButtonsRow = document.createElement("div")
    downloadButtonsRow.style.cssText = `
        display: flex;
        gap: 8px;
        justify-content: center;
    `

    const downloadCurrentButton = document.createElement("button")
    downloadCurrentButton.innerHTML = ""
    const currentIcon = createDownloadIcon()
    currentIcon.style.width = "12px"
    currentIcon.style.height = "12px"
    currentIcon.style.marginRight = "4px"
    const currentText = document.createElement("span")
    currentText.textContent = "Download Current"
    downloadCurrentButton.appendChild(currentIcon)
    downloadCurrentButton.appendChild(currentText)
    downloadCurrentButton.style.cssText = `
        background-color: #0ea5e9;
        color: white;
        border: none;
        border-radius: 6px;
        padding: 6px 12px;
        font-weight: bold;
        cursor: pointer;
        font-size: 12px;
        transition: background-color 0.2s;
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
    `
    downloadCurrentButton.addEventListener("mouseenter", () => {
      downloadCurrentButton.style.backgroundColor = "#0284c7"
    })
    downloadCurrentButton.addEventListener("mouseleave", () => {
      downloadCurrentButton.style.backgroundColor = "#0ea5e9"
    })

    const downloadAllButton = document.createElement("button")
    downloadAllButton.innerHTML = ""
    const allIcon = createDownloadIcon()
    allIcon.style.width = "12px"
    allIcon.style.height = "12px"
    allIcon.style.marginRight = "4px"
    const allText = document.createElement("span")
    allText.textContent = "Download All"
    downloadAllButton.appendChild(allIcon)
    downloadAllButton.appendChild(allText)
    downloadAllButton.style.cssText = `
        background-color: #0ea5e9;
        color: white;
        border: none;
        border-radius: 6px;
        padding: 6px 12px;
        font-weight: bold;
        cursor: pointer;
        font-size: 12px;
        transition: background-color 0.2s;        
        flex: 1;
        display: flex;
        align-items: center;
        justify-content: center;
    `;
    downloadAllButton.addEventListener("mouseenter", () => {
      downloadAllButton.style.backgroundColor = "#0284c7";
    });
    downloadAllButton.addEventListener("mouseleave", () => {
      downloadAllButton.style.backgroundColor = "#0ea5e9";
    });

    downloadButtonsRow.appendChild(downloadCurrentButton);
    downloadButtonsRow.appendChild(downloadAllButton);

    batchInfoContainer.appendChild(batchStatsRow);
    batchInfoContainer.appendChild(downloadButtonsRow);

    const downloadButton = document.createElement("button");
    const downloadIcon = createDownloadIcon();
    downloadIcon.style.width = "16px";
    downloadIcon.style.height = "16px";
    downloadIcon.style.marginRight = "8px";

    downloadButton.innerHTML = "";
    downloadButton.appendChild(downloadIcon);
    downloadButton.appendChild(document.createTextNode("Download"));
    downloadButton.style.cssText = `
        background-color: #0ea5e9;
        color: white;
        border: none;
        border-radius: 6px;
        padding: 8px 16px;
        font-weight: bold;
        cursor: pointer;
        width: 50%;
        margin-left: auto;
        margin-right: auto;
        display: flex;
        align-items: center;
        justify-content: center;
        text-align: center;
        transition: background-color 0.2s;
    `
    downloadButton.addEventListener("mouseenter", () => {
      downloadButton.style.backgroundColor = "#0284c7"
    })
    downloadButton.addEventListener("mouseleave", () => {
      downloadButton.style.backgroundColor = "#0ea5e9"
    })
    downloadButton.onclick = () => downloadMedia(false)

    if (settings.batchEnabled) {
      buttonContainer.style.display = "none"
    } else {
      buttonContainer.appendChild(downloadButton)
    }

    const batchButtonsContainer = document.createElement("div")
    batchButtonsContainer.style.cssText = `
        display: none;
        gap: 8px;
        margin-bottom: 16px;
    `

    const stopBatchButton = document.createElement("button")
    stopBatchButton.textContent = "Stop Batch"
    stopBatchButton.style.cssText = `
        background-color: #ef4444;
        color: white;
        border: none;
        border-radius: 9999px;
        padding: 8px 16px;
        font-weight: bold;
        cursor: pointer;
        margin-bottom: 16px;
        width: 100%;
        display: none;
        text-align: center;
        transition: background-color 0.2s;
    `
    stopBatchButton.addEventListener("mouseenter", () => {
      stopBatchButton.style.backgroundColor = "#dc2626"
    })
    stopBatchButton.addEventListener("mouseleave", () => {
      stopBatchButton.style.backgroundColor = "#ef4444"
    })

    const progressContainer = document.createElement("div")
    progressContainer.style.cssText = `
        margin-top: 16px;
        display: none;
    `

    const progressText = document.createElement("div")
    progressText.style.cssText = `
        margin-bottom: 8px;
        font-size: 14px;
        text-align: center;
    `
    progressText.textContent = "Downloading..."

    const progressBar = document.createElement("div")
    progressBar.style.cssText = `
        width: 100%;
        height: 8px;
        background-color: #f1f5f9;
        border-radius: 4px;
        overflow: hidden;
    `

    const progressFill = document.createElement("div")
    progressFill.style.cssText = `
        height: 100%;
        width: 0%;
        background-color: #0ea5e9;
        transition: width 0.3s ease-in-out;
        will-change: width;
    `

    progressBar.appendChild(progressFill)
    progressContainer.appendChild(progressText)
    progressContainer.appendChild(progressBar)

    mainContent.appendChild(fetchButton)
    mainContent.appendChild(infoContainer)
    mainContent.appendChild(batchInfoContainer)
    mainContent.appendChild(batchNavContainer)
    mainContent.appendChild(buttonContainer)
    mainContent.appendChild(batchButtonsContainer)
    mainContent.appendChild(stopBatchButton)
    mainContent.appendChild(progressContainer);
    
    const settingsTabs = document.createElement("div")
    settingsTabs.style.cssText = `
        display: flex;
        border-bottom: 1px solid #e2e8f0;
        margin-bottom: 16px;
        width: 100%;
    `

    const fetchTab = document.createElement("button")
    fetchTab.textContent = "Fetch"
    fetchTab.style.cssText = `
        background: none;
        border: none;
        padding: 12px 16px;
        cursor: pointer;
        color: #0f172a;
        border-bottom: 2px solid #0ea5e9;
        font-weight: bold;
        font-size: 14px;
        flex: 1;
        text-align: center;
    `

    const downloadTab = document.createElement("button")
    downloadTab.textContent = "Download"
    downloadTab.style.cssText = `
        background: none;
        border: none;
        padding: 12px 16px;
        cursor: pointer;
        color: #64748b;
        border-bottom: none;
        font-weight: bold;
        font-size: 14px;
        flex: 1;
        text-align: center;
    `

    const authTab = document.createElement("button")
    authTab.textContent = "Auth"
    authTab.style.cssText = `
        background: none;
        border: none;
        padding: 12px 16px;
        cursor: pointer;
        color: #64748b;
        border-bottom: none;
        font-weight: bold;
        font-size: 14px;
        flex: 1;
        text-align: center;
    `

    const advancedTab = document.createElement("button")
    advancedTab.textContent = "Advanced"
    advancedTab.style.cssText = `
        background: none;
        border: none;
        padding: 12px 16px;
        cursor: pointer;
        color: #64748b;
        border-bottom: none;
        font-weight: bold;
        font-size: 14px;
        flex: 1;
        text-align: center;
    `

    settingsTabs.appendChild(fetchTab)
    settingsTabs.appendChild(downloadTab)
    settingsTabs.appendChild(authTab)
    settingsTabs.appendChild(advancedTab)

    const fetchTabContent = document.createElement("div")
    fetchTabContent.style.cssText = `
        display: flex;
        flex-direction: column;
        gap: 16px;
    `

    const downloadTabContent = document.createElement("div")
    downloadTabContent.style.cssText = `
        display: none;
        flex-direction: column;
        gap: 16px;
    `

    const authTabContent = document.createElement("div")
    authTabContent.style.cssText = `
        display: none;
        flex-direction: column;
        gap: 16px;
    `

    const advancedTabContent = document.createElement("div")
    advancedTabContent.style.cssText = `
        display: none;
        flex-direction: column;
        gap: 16px;
    `

    const patreonAuthGroup = document.createElement("div")
    patreonAuthGroup.style.cssText = `
        display: flex;
        flex-direction: column;
        gap: 8px;
    `

    const patreonAuthLabel = document.createElement("label")
    patreonAuthLabel.textContent = "Patreon Auth:"
    patreonAuthLabel.style.cssText = `
        font-size: 14px;
        font-weight: bold;
        color: #334155;
    `

    const patreonAuthInputContainer = document.createElement("div")
    patreonAuthInputContainer.style.cssText = `
        position: relative;
        display: flex;
        align-items: center;
    `

    const patreonAuthInput = document.createElement("input")
    patreonAuthInput.type = "text"
    patreonAuthInput.value = settings.patreonAuth
    patreonAuthInput.style.cssText = `
    background-color: #f1f5f9;
    border: 1px solid transparent;
    border-radius: 4px;
    padding: 8px 12px;
    color: #64748b;
    width: 100%;
    box-sizing: border-box;
    transition: all 0.2s ease;
    `
    patreonAuthInput.addEventListener("focus", () => {
      patreonAuthInput.style.border = "1px solid #0ea5e9"
      patreonAuthInput.style.outline = "none"
    })
    patreonAuthInput.addEventListener("blur", () => {
      patreonAuthInput.style.border = "1px solid transparent"
    })

    patreonAuthInput.addEventListener("input", () => {
      const newSettings = getSettings()
      newSettings.patreonAuth = patreonAuthInput.value
      saveSettings(newSettings)
      patreonAuthClearButton.style.display = patreonAuthInput.value ? "block" : "none"
    })

    const patreonAuthClearButton = document.createElement("button")
    patreonAuthClearButton.innerHTML = "&times;"
    patreonAuthClearButton.style.cssText = `
        position: absolute;
        right: 8px;
        background: none;
        border: none;
        color: #64748b;
        font-size: 18px;
        cursor: pointer;
        padding: 0;
        display: ${settings.patreonAuth ? "block" : "none"};
    `
    patreonAuthClearButton.addEventListener("click", () => {
      patreonAuthInput.value = ""
      const newSettings = getSettings()
      newSettings.patreonAuth = ""
      saveSettings(newSettings)
      patreonAuthClearButton.style.display = "none"
    })

    patreonAuthInputContainer.appendChild(patreonAuthInput)
    patreonAuthInputContainer.appendChild(patreonAuthClearButton)
    patreonAuthGroup.appendChild(patreonAuthLabel)
    patreonAuthGroup.appendChild(patreonAuthInputContainer)

    const tokenGroup = document.createElement("div")
    tokenGroup.style.cssText = `
        display: flex;
        flex-direction: column;
        gap: 8px;
    `

    const tokenLabel = document.createElement("label")
    tokenLabel.textContent = "Auth Token:"
    tokenLabel.style.cssText = `
        font-size: 14px;
        font-weight: bold;
        color: #334155;
    `

    const tokenInputContainer = document.createElement("div")
    tokenInputContainer.style.cssText = `
        position: relative;
        display: flex;
        align-items: center;
    `

    const tokenInput = document.createElement("input")
    tokenInput.type = "text"
    tokenInput.value = settings.authToken
    tokenInput.style.cssText = `
    background-color: #f1f5f9;
    border: 1px solid transparent;
    border-radius: 4px;
    padding: 8px 12px;
    color: #64748b;
    width: 100%;
    box-sizing: border-box;
    transition: all 0.2s ease;
    `
    tokenInput.addEventListener("focus", () => {
      tokenInput.style.border = "1px solid #0ea5e9"
      tokenInput.style.outline = "none"
    })
    tokenInput.addEventListener("blur", () => {
      tokenInput.style.border = "1px solid transparent"
    })

    tokenInput.addEventListener("input", () => {
      const newSettings = getSettings()
      newSettings.authToken = tokenInput.value
      saveSettings(newSettings)
      tokenClearButton.style.display = tokenInput.value ? "block" : "none"
    })

    const tokenClearButton = document.createElement("button")
    tokenClearButton.innerHTML = "&times;"
    tokenClearButton.style.cssText = `
        position: absolute;
        right: 8px;
        background: none;
        border: none;
        color: #64748b;
        font-size: 18px;
        cursor: pointer;
        padding: 0;
        display: ${settings.authToken ? "block" : "none"};
    `
    tokenClearButton.addEventListener("click", () => {
      tokenInput.value = ""
      const newSettings = getSettings()
      newSettings.authToken = ""
      saveSettings(newSettings)
      tokenClearButton.style.display = "none"
    })

    tokenInputContainer.appendChild(tokenInput)
    tokenInputContainer.appendChild(tokenClearButton)
    tokenGroup.appendChild(tokenLabel)
    tokenGroup.appendChild(tokenInputContainer)

    const apiServerGroup = document.createElement("div")
    apiServerGroup.style.cssText = `
        display: flex;
        flex-direction: column;
        gap: 8px;
    `

    const apiServerLabel = document.createElement("label")
    apiServerLabel.textContent = "Service:"
    apiServerLabel.style.cssText = `
        font-size: 14px;
        font-weight: bold;
        color: #334155;
    `

    const apiServerOptions = [
      { value: "default", label: "Default" },
      { value: "backup", label: "Backup" }
    ]

    const apiServerToggle = createToggleSwitch(apiServerOptions, settings.apiServer, (value) => {
      const newSettings = getSettings()
      newSettings.apiServer = value
      saveSettings(newSettings)
      settings.apiServer = value
    })

    apiServerGroup.appendChild(apiServerLabel)
    apiServerGroup.appendChild(apiServerToggle)

    const batchGroup = document.createElement("div")
    batchGroup.style.cssText = `
        display: flex;
        align-items: center;
        gap: 8px;
    `
    const batchLabel = document.createElement("label")
    batchLabel.style.cssText = `
        font-size: 14px;
        font-weight: bold;
        color: #334155;
        flex: 1;
        display: flex;
        align-items: center;
    `
    const batchLabelText = document.createElement("span")
    batchLabelText.textContent = "Batch:"
    batchLabel.appendChild(batchLabelText)

    const batchInfoIcon = createInfoIcon()
    const batchTooltip = createInfoTooltip("When enabled, the media fetching process is performed in batches")
    batchInfoIcon.addEventListener("mouseenter", batchTooltip.showTooltip)
    batchInfoIcon.addEventListener("mouseleave", batchTooltip.hideTooltip)
    batchLabel.appendChild(batchInfoIcon)

    const batchToggle = document.createElement("div")
    batchToggle.style.cssText = `
        position: relative;
        width: 50px;
        height: 24px;
        background-color: ${settings.batchEnabled ? "#22c55e" : "#cbd5e1"};
        border-radius: 12px;
        cursor: pointer;
        transition: background-color 0.3s;
    `

    const batchToggleHandle = document.createElement("div")
    batchToggleHandle.style.cssText = `
        position: absolute;
        top: 2px;
        left: ${settings.batchEnabled ? "28px" : "2px"};
        width: 20px;
        height: 20px;
        background-color: white;
        border-radius: 50%;
        transition: left 0.3s;
    `

    batchToggle.appendChild(batchToggleHandle)
    batchToggle.addEventListener("click", () => {
        const newSettings = getSettings()
        newSettings.batchEnabled = !newSettings.batchEnabled
        saveSettings(newSettings)
        batchToggle.style.backgroundColor = newSettings.batchEnabled ? "#22c55e" : "#cbd5e1"
        batchToggleHandle.style.left = newSettings.batchEnabled ? "28px" : "2px"

        batchSizeGroup.style.display = newSettings.batchEnabled ? "flex" : "none"
        startingBatchGroup.style.display = newSettings.batchEnabled ? "flex" : "none"

        if (!newSettings.batchEnabled) {
            buttonContainer.innerHTML = ""
            buttonContainer.appendChild(downloadButton)
            buttonContainer.style.display = infoContainer.style.display === "block" ? "block" : "none"
        } else {
            buttonContainer.innerHTML = ""
            buttonContainer.style.display = "none"
        }
    })

    batchGroup.appendChild(batchLabel)
    batchGroup.appendChild(batchToggle)

    const batchSizeGroup = document.createElement("div")
    batchSizeGroup.style.cssText = `
    display: ${settings.batchEnabled ? "flex" : "none"};
    flex-direction: column;
    gap: 8px;
    `

    const batchSizeLabel = document.createElement("label")
    batchSizeLabel.style.cssText = `
        font-size: 14px;
        font-weight: bold;
        color: #334155;
        display: flex;
        align-items: center;
    `

    const batchSizeLabelText = document.createElement("span")
    batchSizeLabelText.textContent = "Batch Size:"
    batchSizeLabel.appendChild(batchSizeLabelText)

    const batchSizeInfoIcon = createInfoIcon()
    const batchSizeTooltip = createInfoTooltip("Number of media items fetched in a single request")
    batchSizeInfoIcon.addEventListener("mouseenter", batchSizeTooltip.showTooltip)
    batchSizeInfoIcon.addEventListener("mouseleave", batchSizeTooltip.hideTooltip)
    batchSizeLabel.appendChild(batchSizeInfoIcon)

    const batchSizeToggle = createSlider(batchSizes, settings.batchSize, (value) => {
      const newSettings = getSettings()
      newSettings.batchSize = value
      saveSettings(newSettings)
      settings.batchSize = value
    })

    batchSizeGroup.appendChild(batchSizeLabel)
    batchSizeGroup.appendChild(batchSizeToggle)

    const startingBatchGroup = document.createElement("div")
    startingBatchGroup.style.cssText = `
    display: ${settings.batchEnabled ? "flex" : "none"};
    flex-direction: column;
    gap: 8px;
    `

    const startingBatchLabel = document.createElement("label")
    startingBatchLabel.style.cssText = `
        font-size: 14px;
        font-weight: bold;
        color: #334155;
        display: flex;
        align-items: center;
    `

    const startingBatchLabelText = document.createElement("span")
    startingBatchLabelText.textContent = "Starting Batch:"
    startingBatchLabel.appendChild(startingBatchLabelText)

    const startingBatchInfoIcon = createInfoIcon()
    const startingBatchTooltip = createInfoTooltip("Determines which batch number to start fetching from")
    startingBatchInfoIcon.addEventListener("mouseenter", startingBatchTooltip.showTooltip)
    startingBatchInfoIcon.addEventListener("mouseleave", startingBatchTooltip.hideTooltip)
    startingBatchLabel.appendChild(startingBatchInfoIcon)

    const startingBatchInput = document.createElement("input")
    startingBatchInput.type = "number"
    startingBatchInput.value = settings.startingBatch
    startingBatchInput.min = "0"
    startingBatchInput.style.cssText = `
        background-color: #f1f5f9;
        border: 1px solid transparent;
        border-radius: 4px;
        padding: 8px 12px;
        color: #64748b;
        width: 100%;
        box-sizing: border-box;
        transition: all 0.2s ease;
    `
    startingBatchInput.addEventListener("focus", () => {
      startingBatchInput.style.border = "1px solid #0ea5e9"
      startingBatchInput.style.outline = "none"
    })
    startingBatchInput.addEventListener("blur", () => {
      startingBatchInput.style.border = "1px solid transparent"
    })

    startingBatchInput.addEventListener("input", () => {
      const newSettings = getSettings()
      newSettings.startingBatch = parseInt(startingBatchInput.value) || 0
      saveSettings(newSettings)
      settings.startingBatch = newSettings.startingBatch
    })

    startingBatchGroup.appendChild(startingBatchLabel)
    startingBatchGroup.appendChild(startingBatchInput)

    const timelineTypeGroup = document.createElement("div")
    timelineTypeGroup.style.cssText = `
    display: flex;
    flex-direction: column;
    gap: 8px;
    `

    const timelineTypeLabel = document.createElement("label")
    timelineTypeLabel.textContent = "Timeline Type:"
    timelineTypeLabel.style.cssText = `
        font-size: 14px;
        font-weight: bold;
        color: #334155;
    `

    const timelineTypeOptions = [
      { value: "media", label: "Media" },
      { value: "timeline", label: "Post" },
      { value: "tweets", label: "Tweets" },
      { value: "with_replies", label: "Replies" },
    ]

    const timelineTypeToggle = createToggleSwitch(timelineTypeOptions, settings.timelineType, (value) => {
      const newSettings = getSettings()
      newSettings.timelineType = value
      saveSettings(newSettings)
      settings.timelineType = value
    })

    timelineTypeGroup.appendChild(timelineTypeLabel)
    timelineTypeGroup.appendChild(timelineTypeToggle)

    const mediaTypeGroup = document.createElement("div")
    mediaTypeGroup.style.cssText = `
    display: flex;
    flex-direction: column;
    gap: 8px;
    `

    const mediaTypeLabel = document.createElement("label")
    mediaTypeLabel.textContent = "Media Type:"
    mediaTypeLabel.style.cssText = `
        font-size: 14px;
        font-weight: bold;
        color: #334155;
    `

    const mediaTypeIcons = createMediaTypeIcons()
    const mediaTypeOptions = [
      { value: "all", label: "All", icon: mediaTypeIcons.all },
      { value: "image", label: "Image", icon: mediaTypeIcons.image },
      { value: "video", label: "Video", icon: mediaTypeIcons.video },
      { value: "gif", label: "GIF", icon: mediaTypeIcons.gif },
    ]

    const mediaTypeToggle = createToggleSwitch(mediaTypeOptions, settings.mediaType, (value) => {
      const newSettings = getSettings()
      newSettings.mediaType = value
      saveSettings(newSettings)
      settings.mediaType = value

      const newMediaTypeLabel = getMediaTypeLabel(value).toLowerCase()
      const newFetchButtonText =
        value === "all"
          ? "Fetch Media"
          : `Fetch ${newMediaTypeLabel === "gif" ? "GIF" : newMediaTypeLabel.charAt(0).toUpperCase() + newMediaTypeLabel.slice(1)}`

      fetchButton.innerHTML = ""
      const newFetchIcon = fetchIcon.cloneNode(true)
      fetchButton.appendChild(newFetchIcon)
      fetchButton.appendChild(document.createTextNode(newFetchButtonText))

      title.innerHTML = `Download ${getMediaTypeLabel(value)}: <span style="color: #0ea5e9">${username}</span>`
    })

    mediaTypeGroup.appendChild(mediaTypeLabel)
    mediaTypeGroup.appendChild(mediaTypeToggle)

    const concurrentGroup = document.createElement("div")
    concurrentGroup.style.cssText = `
    display: flex;
    flex-direction: column;
    gap: 8px;
    `

    const concurrentLabel = document.createElement("label")
    concurrentLabel.style.cssText = `
        font-size: 14px;
        font-weight: bold;
        color: #334155;
        display: flex;
        align-items: center;
    `

    const concurrentLabelText = document.createElement("span")
    concurrentLabelText.textContent = "Batch Download Items:"
    concurrentLabel.appendChild(concurrentLabelText)

    const concurrentInfoIcon = createInfoIcon()
    const concurrentTooltip = createInfoTooltip("Total item that are downloaded in a single request")
    concurrentInfoIcon.addEventListener("mouseenter", concurrentTooltip.showTooltip)
    concurrentInfoIcon.addEventListener("mouseleave", concurrentTooltip.hideTooltip)
    concurrentLabel.appendChild(concurrentInfoIcon)

    const concurrentToggle = createSlider(concurrentSizes, settings.concurrentDownloads, (value) => {
      const newSettings = getSettings()
      newSettings.concurrentDownloads = value
      saveSettings(newSettings)
      settings.concurrentDownloads = value
    })

    concurrentGroup.appendChild(concurrentLabel)
    concurrentGroup.appendChild(concurrentToggle)

    const cacheDurationGroup = document.createElement("div")
    cacheDurationGroup.style.cssText = `
    display: flex;
    flex-direction: column;
    gap: 8px;
    `

    const cacheDurationLabel = document.createElement("label")
    cacheDurationLabel.textContent = "Cache Duration:"
    cacheDurationLabel.style.cssText = `
        font-size: 14px;
        font-weight: bold;
        color: #334155;
    `

    const cacheDurationOptions = cacheDurations.map((duration) => {
      let label = duration.toString() + "m"
      if (duration >= 60 && duration % 60 === 0) {
        label = `${duration / 60}h`
      }
      return { value: duration, label: label }
    })

      const cacheDurationToggle = createToggleSwitch(cacheDurationOptions, settings.cacheDuration, (value) => {
      const newSettings = getSettings()
      newSettings.cacheDuration = value
      saveSettings(newSettings)
      settings.cacheDuration = value
    });
    cacheDurationGroup.appendChild(cacheDurationLabel)
    cacheDurationGroup.appendChild(cacheDurationToggle)

    const buttonsContainer = document.createElement("div")
    buttonsContainer.style.cssText = `
    display: flex;
    gap: 16px;
    margin-top: 16px;
    width: 100%;
    justify-content: center;
    align-items: center;
    `

    const clearCacheButton = document.createElement("button")
    const trashIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    trashIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg")
    trashIcon.setAttribute("viewBox", ICONS.TRASH.VIEWBOX)
    trashIcon.setAttribute("width", "16")
    trashIcon.setAttribute("height", "16")
    trashIcon.style.marginRight = "8px"

    const trashPath = document.createElementNS("http://www.w3.org/2000/svg", "path")
    trashPath.setAttribute("fill", "currentColor")
    trashPath.setAttribute("d", ICONS.TRASH.PATH)
    trashIcon.appendChild(trashPath)
    clearCacheButton.appendChild(trashIcon)
    clearCacheButton.appendChild(document.createTextNode("Clear Cache"))
    clearCacheButton.style.cssText = `
    background-color: #ef4444;
    color: white;
    border: none;
    border-radius: 6px;
    padding: 8px 16px;
    font-weight: bold;
    cursor: pointer;
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    transition: background-color 0.2s;
    `

    const resetDefaultButton = document.createElement("button")
    const resetIcon = document.createElementNS("http://www.w3.org/2000/svg", "svg")
    resetIcon.setAttribute("xmlns", "http://www.w3.org/2000/svg")
    resetIcon.setAttribute("viewBox", ICONS.RESET.VIEWBOX)
    resetIcon.setAttribute("width", "16")
    resetIcon.setAttribute("height", "16")
    resetIcon.style.marginRight = "8px"

    const resetPath = document.createElementNS("http://www.w3.org/2000/svg", "path")
    resetPath.setAttribute("fill", "currentColor")
    resetPath.setAttribute("d", ICONS.RESET.PATH)
    resetIcon.appendChild(resetPath)

    resetDefaultButton.appendChild(resetIcon)
    resetDefaultButton.appendChild(document.createTextNode("Reset Default"))
    resetDefaultButton.style.cssText = `
    background-color: #6366f1;
    color: white;
    border: none;
    border-radius: 6px;
    padding: 8px 16px;
    font-weight: bold;
    cursor: pointer;
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    text-align: center;
    transition: background-color 0.2s;
    `
    buttonsContainer.appendChild(clearCacheButton)
    buttonsContainer.appendChild(resetDefaultButton)

    clearCacheButton.addEventListener("mouseenter", () => {
      clearCacheButton.style.backgroundColor = "#dc2626"
    })
    clearCacheButton.addEventListener("mouseleave", () => {
      clearCacheButton.style.backgroundColor = "#ef4444"
    })

    resetDefaultButton.addEventListener("mouseenter", () => {
      resetDefaultButton.style.backgroundColor = "#5b56f4"
    })
    resetDefaultButton.addEventListener("mouseleave", () => {
      resetDefaultButton.style.backgroundColor = "#6366f1"
    })

    clearCacheButton.addEventListener("click", () => {
      createConfirmDialog("Are you sure about clearing the cache?", () => {
        cacheManager.clear()

        const notification = document.createElement("div")
        notification.style.cssText = `
                position: fixed;
                bottom: 20px;
                left: 50%;
                transform: translateX(-50%);
                background-color: #0ea5e9;
                color: white;
                padding: 12px 24px;
                border-radius: 9999px;
                font-weight: bold;
                z-index: 10002;
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
                text-align: center;
            `;
        notification.textContent = "Cache cleared successfully";
        document.body.appendChild(notification);

        setTimeout(() => {
          document.body.removeChild(notification);
        }, 3000);
      });
    });

    resetDefaultButton.addEventListener("click", () => {
      createConfirmDialog("Are you sure you want to reset all settings to default values?", () => {
        const currentSettings = getSettings()
        const preservedPatreonAuth = currentSettings.patreonAuth
        const preservedAuthToken = currentSettings.authToken

        const resetSettings = { ...defaultSettings }
        resetSettings.patreonAuth = preservedPatreonAuth
        resetSettings.authToken = preservedAuthToken
          saveSettings(resetSettings);

        startingBatchInput.value = defaultSettings.startingBatch;

        batchToggle.style.backgroundColor = defaultSettings.batchEnabled ? "#22c55e" : "#cbd5e1";
        batchToggleHandle.style.left = defaultSettings.batchEnabled ? "28px" : "2px";

        batchSizeGroup.style.display = defaultSettings.batchEnabled ? "flex" : "none";
        startingBatchGroup.style.display = defaultSettings.batchEnabled ? "flex" : "none";

        patreonAuthClearButton.style.display = preservedPatreonAuth ? "block" : "none";
        tokenClearButton.style.display = preservedAuthToken ? "block" : "none";

        const timelineTypeOptions = ["media", "timeline", "tweets", "with_replies"];
        const timelineTypeIndex = timelineTypeOptions.indexOf(defaultSettings.timelineType);
        const timelineTypeSlider = timelineTypeToggle.querySelector("div:first-child");
        const timelineTypeContainer = timelineTypeToggle.querySelector("div:last-child");

        if (timelineTypeSlider && timelineTypeContainer && timelineTypeIndex !== -1) {
          timelineTypeSlider.style.transform = `translateX(${timelineTypeIndex * 100}%)`;
          timelineTypeContainer.querySelectorAll("div").forEach((opt, i) => {
            opt.style.color = i === timelineTypeIndex ? "white" : "#64748b";
            const optIcon = opt.querySelector("svg");
            if (optIcon) {
              const optPaths = optIcon.querySelectorAll("path");
              optPaths.forEach((path) => {
                path.setAttribute("fill", i === timelineTypeIndex ? "white" : "#64748b");
              });
            }
          });
        }

        const mediaTypeOptions = ["all", "image", "video", "gif"];
        const mediaTypeIndex = mediaTypeOptions.indexOf(defaultSettings.mediaType);
        const mediaTypeSlider = mediaTypeToggle.querySelector("div:first-child");
        const mediaTypeContainer = mediaTypeToggle.querySelector("div:last-child");

        if (mediaTypeSlider && mediaTypeContainer && mediaTypeIndex !== -1) {
          mediaTypeSlider.style.transform = `translateX(${mediaTypeIndex * 100}%)`;
          mediaTypeContainer.querySelectorAll("div").forEach((opt, i) => {
            opt.style.color = i === mediaTypeIndex ? "white" : "#64748b";
            const optIcon = opt.querySelector("svg");
            if (optIcon) {
              const optPaths = optIcon.querySelectorAll("path");
              optPaths.forEach((path) => {
                path.setAttribute("fill", i === mediaTypeIndex ? "white" : "#64748b");
              });
            }
          });
        }

        const batchSizeIndex = batchSizes.indexOf(defaultSettings.batchSize);
        const batchSizeSlider = batchSizeToggle.querySelector("div:first-child");
        const batchSizeContainer = batchSizeToggle.querySelector("div:last-child");

        if (batchSizeSlider && batchSizeContainer && batchSizeIndex !== -1) {
          batchSizeSlider.style.transform = `translateX(${batchSizeIndex * 100}%)`;
          batchSizeContainer.querySelectorAll("div").forEach((opt, i) => {
            opt.style.color = i === batchSizeIndex ? "white" : "#64748b";
            const optIcon = opt.querySelector("svg");
            if (optIcon) {
              const optPaths = optIcon.querySelectorAll("path");
              optPaths.forEach((path) => {
                path.setAttribute("fill", i === batchSizeIndex ? "white" : "#64748b");
              });
            }
          });
        }

        const concurrentIndex = concurrentSizes.indexOf(defaultSettings.concurrentDownloads);
        const concurrentSlider = concurrentToggle.querySelector("div:first-child");
        const concurrentContainer = concurrentToggle.querySelector("div:last-child");

        if (concurrentSlider && concurrentContainer && concurrentIndex !== -1) {
          concurrentSlider.style.transform = `translateX(${concurrentIndex * 100}%)`;
          concurrentContainer.querySelectorAll("div").forEach((opt, i) => {
            opt.style.color = i === concurrentIndex ? "white" : "#64748b";
            const optIcon = opt.querySelector("svg");
            if (optIcon) {
              const optPaths = optIcon.querySelectorAll("path");
              optPaths.forEach((path) => {
                path.setAttribute("fill", i === concurrentIndex ? "white" : "#64748b");
              });
            }
          });
        }
        const cacheDurationIndex = cacheDurations.indexOf(defaultSettings.cacheDuration);
        const cacheDurationSlider = cacheDurationToggle.querySelector("div:first-child");
        const cacheDurationContainer = cacheDurationToggle.querySelector("div:last-child");

        if (cacheDurationSlider && cacheDurationContainer && cacheDurationIndex !== -1) {
          cacheDurationSlider.style.transform = `translateX(${cacheDurationIndex * 100}%)`;
          cacheDurationContainer.querySelectorAll("div").forEach((opt, i) => {
            opt.style.color = i === cacheDurationIndex ? "white" : "#64748b";
            const optIcon = opt.querySelector("svg");
            if (optIcon) {
              const optPaths = optIcon.querySelectorAll("path");
              optPaths.forEach((path) => {
                path.setAttribute("fill", i === cacheDurationIndex ? "white" : "#64748b");
              });
            }
          });
        }

        const apiServerOptions = ["default", "backup"];
        const apiServerIndex = apiServerOptions.indexOf(defaultSettings.apiServer);
        const apiServerSlider = apiServerToggle.querySelector("div:first-child");
        const apiServerContainer = apiServerToggle.querySelector("div:last-child");

        if (apiServerSlider && apiServerContainer && apiServerIndex !== -1) {
          apiServerSlider.style.transform = `translateX(${apiServerIndex * 100}%)`;
          apiServerContainer.querySelectorAll("div").forEach((opt, i) => {
            opt.style.color = i === apiServerIndex ? "white" : "#64748b";
            const optIcon = opt.querySelector("svg");
            if (optIcon) {
              const optPaths = optIcon.querySelectorAll("path");
              optPaths.forEach((path) => {
                path.setAttribute("fill", i === apiServerIndex ? "white" : "#64748b");
              });
            }
          });
        }

        const newMediaTypeLabel = getMediaTypeLabel(defaultSettings.mediaType).toLowerCase();
        const newFetchButtonText = defaultSettings.mediaType === "all"
          ? "Fetch Media"
          : `Fetch ${newMediaTypeLabel === "gif" ? "GIF" : newMediaTypeLabel.charAt(0).toUpperCase() + newMediaTypeLabel.slice(1)}`;

        fetchButton.innerHTML = "";
        const newFetchIcon = fetchIcon.cloneNode(true);
        fetchButton.appendChild(newFetchIcon);
        fetchButton.appendChild(document.createTextNode(newFetchButtonText));
        title.innerHTML = `Download ${getMediaTypeLabel(defaultSettings.mediaType)}: <span style="color: #0ea5e9">${username}</span>`;
      })
    })

    const patreonLink = document.createElement("a")
    patreonLink.href = "https://www.patreon.com/exyezed"
    patreonLink.target = "_blank"
    patreonLink.style.cssText = `
        display: flex;
        align-items: center;
        justify-content: center;
        color: #64748b;
        text-decoration: none;
        margin-top: 16px;
        padding: 8px;
        border-radius: 8px;
        transition: background-color 0.2s, color 0.2s;
    `
    patreonLink.innerHTML = createPatreonIcon().outerHTML + "Patreon Authentication";
    patreonLink.addEventListener("mouseenter", () => {
      patreonLink.style.backgroundColor = "#f1f5f9"
      patreonLink.style.color = "#0ea5e9"
    })
      patreonLink.addEventListener("mouseleave", () => {
      patreonLink.style.backgroundColor = "transparent"
      patreonLink.style.color = "#64748b"
    })

    fetchTabContent.appendChild(batchGroup)
    fetchTabContent.appendChild(batchSizeGroup)
    fetchTabContent.appendChild(startingBatchGroup)
    fetchTabContent.appendChild(timelineTypeGroup)

    downloadTabContent.appendChild(mediaTypeGroup)
    downloadTabContent.appendChild(concurrentGroup)

    authTabContent.appendChild(patreonAuthGroup)
    authTabContent.appendChild(tokenGroup)
    authTabContent.appendChild(patreonLink)

    advancedTabContent.appendChild(apiServerGroup);
    advancedTabContent.appendChild(cacheDurationGroup);
    advancedTabContent.appendChild(buttonsContainer);    
    function switchTab(activeTab, activeContent) {
      const allTabs = [fetchTab, downloadTab, authTab, advancedTab];
      const allContents = [fetchTabContent, downloadTabContent, authTabContent, advancedTabContent];

      allTabs.forEach(tab => {
        if (tab && tab.style) {
          tab.style.color = "#64748b";
          tab.style.borderBottom = "none";
        }
      });

      allContents.forEach(content => {
        if (content && content.style) {
          content.style.display = "none";
        }
      });

      if (activeTab && activeTab.style) {
        activeTab.style.color = "#0f172a";
        activeTab.style.borderBottom = "2px solid #0ea5e9";
      }
      if (activeContent && activeContent.style) {
        activeContent.style.display = "flex";
      }
    }

    fetchTab.addEventListener("click", function() {
      switchTab(fetchTab, fetchTabContent);
    });
    downloadTab.addEventListener("click", function() {
      switchTab(downloadTab, downloadTabContent);
    });
    authTab.addEventListener("click", function() {
      switchTab(authTab, authTabContent);
    });
    advancedTab.addEventListener("click", function() {
      switchTab(advancedTab, advancedTabContent);
    });

    const settingsForm = document.createElement("div");    
    settingsForm.style.cssText = `
        display: flex;
        flex-direction: column;
    `;    
    settingsForm.appendChild(settingsTabs);
    settingsForm.appendChild(fetchTabContent);
    settingsForm.appendChild(downloadTabContent);
    settingsForm.appendChild(authTabContent);
    settingsForm.appendChild(advancedTabContent);

    settingsContent.appendChild(settingsForm);

    mainTab.addEventListener("click", function() {
      mainTab.style.borderBottom = "2px solid #0ea5e9"
      mainTab.style.color = "#0f172a"
      settingsTab.style.borderBottom = "none"
      settingsTab.style.color = "#64748b"
      mainContent.style.display = "block"
      settingsContent.style.display = "none"
    });

    settingsTab.addEventListener("click", function() {
      settingsTab.style.borderBottom = "2px solid #0ea5e9"
      settingsTab.style.color = "#0f172a"
      mainTab.style.borderBottom = "none"
      mainTab.style.color = "#64748b"
      settingsContent.style.display = "block"
      mainContent.style.display = "none"

      switchTab(fetchTab, fetchTabContent);
    });

    modalContent.appendChild(header)
    modalContent.appendChild(tabs)
    modalContent.appendChild(mainContent)
    modalContent.appendChild(settingsContent)
    modal.appendChild(modalContent)

    const mediaData = {
      username: username,
      currentPage: getSettings().startingBatch,
      mediaItems: [],
      allMediaItems: [],
      hasMore: false,
      downloading: false,
      totalDownloaded: 0,
      totalToDownload: 0,
      totalItems: 0,
      autoBatchRunning: false,
    }

    fetchButton.addEventListener("click", async () => {
      const settings = getSettings()

      if (!settings.authToken) {
        createAuthTokenPopup()
        return
      }

      if (!settings.patreonAuth) {
        createPatreonAuthPopup()
        return
      }

      infoContainer.style.display = "none"
      buttonContainer.style.display = "none"
      nextBatchButton.style.display = "none"
      autoBatchButton.style.display = "none"
      stopBatchButton.style.display = "none"
      progressContainer.style.display = "none"
      fetchButton.disabled = true
      fetchButton.innerHTML = ""
      fetchButton.appendChild(document.createTextNode("Fetching..."))

      try {
        const cacheKey = `${settings.timelineType}_${settings.mediaType}_${username}_${mediaData.currentPage}_${settings.batchSize}_batch_${settings.batchEnabled}`
        let data = cacheManager.get(cacheKey)

        if (!data) {
          let url
          if (settings.batchEnabled) {
            url = `${getServiceBaseUrl()}/metadata/${settings.timelineType}/${settings.batchSize}/${mediaData.currentPage}/${settings.mediaType}/${username}/${settings.authToken}/${settings.patreonAuth || ""}`
          } else {
            url = `${getServiceBaseUrl()}/metadata/${settings.timelineType}/${settings.mediaType}/${username}/${settings.authToken}/${settings.patreonAuth || ""}`
          }

          data = await fetchData(url)
          if (data && data.timeline && data.timeline.length > 0) {
            cacheManager.set(cacheKey, data, true)
          }
        }
        if (data.timeline && data.timeline.length > 0) {
          mediaData.mediaItems = data.timeline
          mediaData.hasMore = data.metadata.has_more
          mediaData.totalItems = data.total_urls

          if (mediaData.currentPage === 0) {
            mediaData.allMediaItems = [...data.timeline]
          } else {
            mediaData.allMediaItems = [...mediaData.allMediaItems, ...data.timeline]
          }

          const mediaTypeLabel = getMediaTypeLabel(settings.mediaType)

          if (settings.batchEnabled) {
            infoContainer.innerHTML = `
              <div style="margin-bottom: 8px;"><strong>Account:</strong> ${data.account_info.name}</div>
              <div style="margin-bottom: 8px;"><strong>${mediaTypeLabel} Found:</strong> ${formatNumber(data.total_urls)}</div>
              <div style="margin-bottom: 8px;"><strong>Total Items:</strong> ${formatNumber(mediaData.allMediaItems.length)}</div>
            `
            currentBatchLabel.innerHTML = `Current Batch: <strong>${mediaData.currentPage + 1}</strong>`
            const totalBatches = Math.ceil(data.total_urls / settings.batchSize)
            totalBatchLabel.innerHTML = `Total ZIP ${totalBatches === 1 ? 'File' : 'Files'}: <strong>${totalBatches}</strong>`

            batchInfoContainer.style.display = "flex"
            batchNavContainer.style.display = "flex"
          } else {
            const currentPart = Math.floor(mediaData.allMediaItems.length / 500) + 1
            infoContainer.innerHTML = `
              <div style="margin-bottom: 8px;"><strong>Account:</strong> ${data.account_info.name}</div>
              <div style="margin-bottom: 8px;"><strong>${mediaTypeLabel} Found:</strong> ${formatNumber(data.total_urls)}</div>
              <div style="margin-top: 8px;"><strong>Total ZIP ${currentPart === 1 ? 'File' : 'Files'}:</strong> ${currentPart}</div>
            `

            batchInfoContainer.style.display = "none"
            batchNavContainer.style.display = "none"
          }

          infoContainer.style.display = "block"

          if (settings.batchEnabled) {
            buttonContainer.style.display = "none"
          } else {
            buttonContainer.innerHTML = ""
            buttonContainer.appendChild(downloadButton)
            buttonContainer.style.display = "block"
          }

          if (settings.batchEnabled && mediaData.hasMore) {
            nextBatchButton.style.display = "block"
            autoBatchButton.style.display = "block"
          }

          downloadCurrentButton.onclick = () => downloadMedia(false)
          downloadAllButton.onclick = () => downloadMedia(true)

          fetchButton.disabled = false
          const currentMediaTypeLabel = getMediaTypeLabel(settings.mediaType).toLowerCase()
          const updatedFetchButtonText =
            settings.mediaType === "all"
              ? "Fetch Media"
              : `Fetch ${currentMediaTypeLabel === "gif" ? "GIF" : currentMediaTypeLabel.charAt(0).toUpperCase() + currentMediaTypeLabel.slice(1)}`

          fetchButton.innerHTML = ""
          let updatedFetchIcon1 = fetchIcon.cloneNode(true)
          fetchButton.appendChild(updatedFetchIcon1)
          fetchButton.appendChild(document.createTextNode(updatedFetchButtonText))
        } else {
          infoContainer.innerHTML = '<div style="color: #ef4444;">No media found, invalid token, or invalid Patreon authentication.</div>'
          infoContainer.style.display = "block"
          fetchButton.disabled = false
          const currentMediaTypeLabel = getMediaTypeLabel(settings.mediaType).toLowerCase()
          const updatedFetchButtonText =
            settings.mediaType === "all"
              ? "Fetch Media"
              : `Fetch ${currentMediaTypeLabel === "gif" ? "GIF" : currentMediaTypeLabel.charAt(0).toUpperCase() + currentMediaTypeLabel.slice(1)}`

          fetchButton.innerHTML = ""
          let updatedFetchIcon2 = fetchIcon.cloneNode(true)
          fetchButton.appendChild(updatedFetchIcon2)
          fetchButton.appendChild(document.createTextNode(updatedFetchButtonText))
        }
      } catch (error) {
        infoContainer.innerHTML = `<div style="color: #ef4444;">Error: ${error.message}</div>`
        infoContainer.style.display = "block"
        fetchButton.disabled = false
        const currentMediaTypeLabel = getMediaTypeLabel(settings.mediaType).toLowerCase()

        const updatedFetchButtonText =
          settings.mediaType === "all"
            ? "Fetch Media"
            : `Fetch ${currentMediaTypeLabel === "gif" ? "GIF" : currentMediaTypeLabel.charAt(0).toUpperCase() + currentMediaTypeLabel.slice(1)}`

        fetchButton.innerHTML = ""
        let updatedFetchIcon3 = fetchIcon.cloneNode(true)
        fetchButton.appendChild(updatedFetchIcon3)
        fetchButton.appendChild(document.createTextNode(updatedFetchButtonText))
      }
    })
      nextBatchButton.addEventListener("click", () => {
      mediaData.currentPage++
      fetchButton.click()
    })

    prevBatchButton.addEventListener("click", () => {
      if (mediaData.currentPage > getSettings().startingBatch) {
        mediaData.currentPage--
        fetchButton.click()
      }
    })

    autoBatchButton.addEventListener("click", () => {
      if (mediaData.autoBatchRunning) {
        return
      }

      mediaData.autoBatchRunning = true
      autoBatchButton.style.display = "none"
      stopBatchButton.style.display = "block"
      nextBatchButton.style.display = "none"

      startAutoBatch()
    })

    stopBatchButton.addEventListener("click", () => {
      createConfirmDialog("Stop auto batch download?", () => {
        mediaData.autoBatchRunning = false
        stopBatchButton.style.display = "none"
        autoBatchButton.style.display = "block"
        if (mediaData.hasMore) {
          nextBatchButton.style.display = "block"
        }
      })
    })

    async function startAutoBatch() {
      while (mediaData.hasMore && mediaData.autoBatchRunning) {
        mediaData.currentPage++

        downloadCurrentButton.disabled = true
        downloadAllButton.disabled = true

        await new Promise((resolve) => {
          const settings = getSettings()
          const cacheKey = `${settings.timelineType}_${settings.mediaType}_${username}_${mediaData.currentPage}_${settings.batchSize}_batch_${settings.batchEnabled}`
          const data = cacheManager.get(cacheKey)

          if (data) {
            processNextBatch(data)
            resolve()
          } else {
            let url
            if (settings.batchEnabled) {
              url = `${getServiceBaseUrl()}/metadata/${settings.timelineType}/${settings.batchSize}/${mediaData.currentPage}/${settings.mediaType}/${username}/${settings.authToken}/${settings.patreonAuth || ""}`
            } else {
              url = `${getServiceBaseUrl()}/metadata/${settings.timelineType}/${settings.mediaType}/${username}/${settings.authToken}/${settings.patreonAuth || ""}`
            }

            fetchData(url)
              .then((data) => {
                if (data && data.timeline && data.timeline.length > 0) {
                  cacheManager.set(cacheKey, data, true)
                }
                processNextBatch(data)
                resolve()
              })
              .catch(() => {
                mediaData.autoBatchRunning = false
                stopBatchButton.style.display = "none"
                autoBatchButton.style.display = "block"

                downloadCurrentButton.disabled = false
                downloadAllButton.disabled = false

                if (mediaData.hasMore) {
                  nextBatchButton.style.display = "block"
                }

                resolve()
              })
          }
        })

        await new Promise((resolve) => setTimeout(resolve, 1000))
      }

      if (mediaData.autoBatchRunning) {
        mediaData.autoBatchRunning = false
        stopBatchButton.style.display = "none"
        autoBatchButton.style.display = "none"
      }

      downloadCurrentButton.disabled = false
      downloadAllButton.disabled = false
    }

    function processNextBatch(data) {
      if (data.timeline && data.timeline.length > 0) {
        mediaData.mediaItems = data.timeline
        mediaData.hasMore = data.metadata.has_more

        mediaData.allMediaItems = [...mediaData.allMediaItems, ...data.timeline]

        const settings = getSettings()
        const mediaTypeLabel = getMediaTypeLabel(settings.mediaType)

        const batchInfo = settings.startingBatch === 0
          ? `Batch ${mediaData.currentPage + 1}`
          : `Batch ${mediaData.currentPage + 1} (from batch ${settings.startingBatch + 1})`

        infoContainer.innerHTML = `
            <div style="margin-bottom: 8px;"><strong>Account:</strong> ${data.account_info.name}</div>
            <div style="margin-bottom: 8px;"><strong>${mediaTypeLabel} Found:</strong> ${formatNumber(data.total_urls)}</div>
            <div style="margin-top: 8px;"><strong>Current:</strong> ${batchInfo}</div>
            <div style="margin-top: 8px;"><strong>Total Items:</strong> ${formatNumber(mediaData.allMediaItems.length)}</div>
        `

        if (!mediaData.hasMore) {
          nextBatchButton.style.display = "none"
          autoBatchButton.style.display = "none"
          stopBatchButton.style.display = "none"
        }
      } else {
        mediaData.hasMore = false
        nextBatchButton.style.display = "none"
        autoBatchButton.style.display = "none"
        stopBatchButton.style.display = "none"
      }
    }

    function chunkMediaItems(items) {
      const chunks = []
      for (let i = 0; i < items.length; i += 500) {
        chunks.push(items.slice(i, i + 500))
      }
      return chunks
    }

    async function downloadMedia(downloadAll) {
      if (mediaData.downloading) return

      mediaData.downloading = true

      const settings = getSettings()
      const timestamp = getCurrentTimestamp()

      let itemsToDownload
      if (downloadAll) {
        itemsToDownload = mediaData.allMediaItems
      } else {
        itemsToDownload = mediaData.mediaItems
      }

      mediaData.totalToDownload = itemsToDownload.length
      mediaData.totalDownloaded = 0

      progressText.textContent = `Downloading 0/${formatNumber(mediaData.totalToDownload)}`
      progressFill.style.width = "0%"
      progressContainer.style.display = "block"

      fetchButton.disabled = true
      if (settings.batchEnabled) {
        downloadCurrentButton.disabled = true
        downloadAllButton.disabled = true
      } else {
        downloadButton.disabled = true
      }
      nextBatchButton.disabled = true
      autoBatchButton.disabled = true
      stopBatchButton.disabled = true

      const chunks = chunkMediaItems(itemsToDownload)

      for (let chunkIndex = 0; chunkIndex < chunks.length; chunkIndex++) {
        const chunk = chunks[chunkIndex]

        if (chunk.length === 1 && chunks.length === 1) {
          try {
            const item = chunk[0]
            const formattedDate = formatDate(item.date)
            const baseFilename = `${username}_${formattedDate}_${item.tweet_id}`
            const fileExtension = item.type === "photo" ? "jpg" : "mp4"
            const filename = `${baseFilename}.${fileExtension}`

            progressText.textContent = `Downloading 0/1`

            const blob = await fetchBinary(item.url)

            const downloadLink = document.createElement("a")
            downloadLink.href = URL.createObjectURL(blob)
            downloadLink.download = filename
            document.body.appendChild(downloadLink)
            downloadLink.click()
            document.body.removeChild(downloadLink)

            mediaData.totalDownloaded = 1
            progressText.textContent = `Downloading 1/1`
            progressFill.style.width = "100%"

            continue
          } catch (error) {}
        }

        const zip = new JSZip()

        const hasImages = chunk.some((item) => item.type === "photo")
        const hasVideos = chunk.some((item) => item.type === "video")
        const hasGifs = chunk.some((item) => item.type === "gif")

        let imageFolder, videoFolder, gifFolder
        if (settings.mediaType === "all") {
          if (hasImages) imageFolder = zip.folder("image")
          if (hasVideos) videoFolder = zip.folder("video")
          if (hasGifs) gifFolder = zip.folder("gif")
        }

        const filenameMap = {}

        let completedCount = 0

        for (let i = 0; i < chunk.length; i++) {
          const item = chunk[i]
          try {
            const formattedDate = formatDate(item.date)
            let baseFilename = `${username}_${formattedDate}_${item.tweet_id}`

            if (filenameMap[baseFilename] !== undefined) {
              filenameMap[baseFilename]++
              baseFilename = `${baseFilename}_${String(filenameMap[baseFilename]).padStart(2, "0")}`
            } else {
              filenameMap[baseFilename] = 0
            }

            const fileExtension = item.type === "photo" ? "jpg" : "mp4"
            const filename = `${baseFilename}.${fileExtension}`

            completedCount = mediaData.totalDownloaded + i
            progressText.textContent = `Downloading ${formatNumber(completedCount)}/${formatNumber(mediaData.totalToDownload)}`
            progressFill.style.width = `${(completedCount / mediaData.totalToDownload) * 100}%`

            await new Promise((resolve) => setTimeout(resolve, 0))

            const blob = await fetchBinary(item.url)

            if (settings.mediaType === "all") {
              if (item.type === "photo") {
                imageFolder.file(filename, blob)
              } else if (item.type === "video") {
                videoFolder.file(filename, blob)
              } else if (item.type === "gif") {
                gifFolder.file(filename, blob)
              }
            } else {
              zip.file(filename, blob)
            }

            completedCount = mediaData.totalDownloaded + i + 1
            progressText.textContent = `Downloading ${formatNumber(completedCount)}/${formatNumber(mediaData.totalToDownload)}`
            progressFill.style.width = `${(completedCount / mediaData.totalToDownload) * 100}%`

            await new Promise((resolve) => setTimeout(resolve, 0))
          } catch (error) {
            console.error("Error downloading item:", error)
          }
        }

        mediaData.totalDownloaded += chunk.length

        progressText.textContent = `Creating ZIP file ${chunkIndex + 1}/${chunks.length}...`

        try {
          const zipBlob = await zip.generateAsync({ type: "blob" })

          let zipFilename
          if (chunks.length === 1 && chunk.length < 500) {
            zipFilename = `${username}_${timestamp}.zip`
          } else if (settings.batchEnabled && !downloadAll) {
            zipFilename = `${username}_${timestamp}_part_${String(mediaData.currentPage + 1).padStart(2, "0")}.zip`
          } else {
            zipFilename = `${username}_${timestamp}_part_${String(chunkIndex + 1).padStart(2, "0")}.zip`
          }

          const downloadLink = document.createElement("a")
          downloadLink.href = URL.createObjectURL(zipBlob)
          downloadLink.download = zipFilename
          document.body.appendChild(downloadLink)
          downloadLink.click()
          document.body.removeChild(downloadLink)
        } catch (error) {
          progressText.textContent = `Error creating ZIP ${chunkIndex + 1}: ${error.message}`
        }
      }

      progressText.textContent = "Download complete!"
      progressFill.style.width = "100%"

      setTimeout(() => {
        fetchButton.disabled = false
        if (settings.batchEnabled) {
          downloadCurrentButton.disabled = false
          downloadAllButton.disabled = false
        } else {
          downloadButton.disabled = false
        }
        nextBatchButton.disabled = false
        autoBatchButton.disabled = false
        stopBatchButton.disabled = false

        mediaData.downloading = false
      }, 2000)
    }

    document.body.appendChild(modal)
  }

  function extractUsername() {
    const pathParts = window.location.pathname.split("/").filter((part) => part)
    if (pathParts.length > 0) {
      return pathParts[0]
    }
    return null
  }

  function insertDownloadIcon() {
    const usernameDivs = document.querySelectorAll('[data-testid="UserName"]')

    usernameDivs.forEach((usernameDiv) => {
      if (!usernameDiv.querySelector(".download-icon")) {
        const username = extractUsername()
        if (!username) return

        const verifiedButton = usernameDiv
          .querySelector('[aria-label*="verified"], [aria-label*="Verified"]')
          ?.closest("button")

        const targetElement = verifiedButton
          ? verifiedButton.parentElement
          : usernameDiv.querySelector(".css-1jxf684")?.closest("span")

        if (targetElement) {
          const downloadIcon = createDownloadIcon()

          const iconDiv = document.createElement("div")
          iconDiv.className = "download-icon css-175oi2r r-1awozwy r-xoduu5"
          iconDiv.style.cssText = `
                    display: inline-flex;
                    align-items: center;
                    margin-left: 6px;
                    margin-right: 6px;
                    gap: 6px;
                    padding: 0 3px;
                    transition: transform 0.2s, color 0.2s;
                `
          iconDiv.appendChild(downloadIcon)

          iconDiv.addEventListener("mouseenter", () => {
            iconDiv.style.transform = "scale(1.1)"
            iconDiv.style.color = "#0ea5e9"
          })

          iconDiv.addEventListener("mouseleave", () => {
            iconDiv.style.transform = "scale(1)"
            iconDiv.style.color = ""
          })

          iconDiv.addEventListener("click", (e) => {
            e.stopPropagation()
            createModal(username)
          })

          const wrapperDiv = document.createElement("div")
          wrapperDiv.style.cssText = `
                    display: inline-flex;
                    align-items: center;
                    gap: 4px;
                `
          wrapperDiv.appendChild(iconDiv)
          targetElement.parentNode.insertBefore(wrapperDiv, targetElement.nextSibling)
        }
      }
    })
  }

  insertDownloadIcon()

  function checkForUserNameElement() {
    const usernameDivs = document.querySelectorAll('[data-testid="UserName"]')
    if (usernameDivs.length > 0) {
      insertDownloadIcon()
    }
  }

  setInterval(checkForUserNameElement, 100)

  let lastUrl = location.href
  let lastUsername = extractUsername()

  function checkForChanges() {
    const currentUrl = location.href
    const currentUsername = extractUsername()

    if (currentUrl !== lastUrl || currentUsername !== lastUsername) {
      lastUrl = currentUrl
      lastUsername = currentUsername

      document.querySelectorAll(".download-icon").forEach((icon) => {
        const wrapper = icon.closest("div[style*='display: inline-flex']")
        if (wrapper) {
          wrapper.remove()
        }
      })

      setTimeout(insertDownloadIcon, 50)
    }
  }

  const observer = new MutationObserver(() => {
    checkForChanges()
    checkForUserNameElement()
  })

  observer.observe(document.body, {
    childList: true,
    subtree: true,
    attributes: true,
    characterData: true,
  })

  setInterval(checkForChanges, 300)

  const originalPushState = history.pushState
  const originalReplaceState = history.replaceState

  history.pushState = function () {
    originalPushState.apply(this, arguments)
    checkForChanges()
    insertDownloadIcon()
  }

  history.replaceState = function () {
    originalReplaceState.apply(this, arguments)
    checkForChanges()
    insertDownloadIcon()
  }

  window.addEventListener("popstate", () => {
    checkForChanges()
    insertDownloadIcon()
  })
})()