YouTube Downloader - Local Server Interface - PRO

The Best YouTube Downloader! Download Video (Full HD/4K/8K) & Audio (MP3) via Local Server. No limits, no waiting, max speed. Features: Type icons (Video/Audio), auto-clean, fixed UI.

目前為 2025-12-01 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube Downloader - Local Server Interface - PRO
// @name:pt-BR   YouTube Downloader - Local Server Interface - PRO
// @name:es      YouTube Downloader - Local Server Interface - PRO
// @name:fr      YouTube Downloader - Local Server Interface - PRO
// @name:de      YouTube Downloader - Local Server Interface - PRO
// @name:it      YouTube Downloader - Local Server Interface - PRO
// @name:ru      YouTube Downloader - Local Server Interface - PRO
// @name:zh-CN   YouTube Downloader - Local Server Interface - PRO
// @name:ja      YouTube Downloader - Local Server Interface - PRO
// @name:ko      YouTube Downloader - Local Server Interface - PRO
// @name:hi      YouTube Downloader - Local Server Interface - PRO
// @name:id      YouTube Downloader - Local Server Interface - PRO
// @namespace    http://tampermonkey.net/
// @version      2.3.0
// @description  The Best YouTube Downloader! Download Video (Full HD/4K/8K) & Audio (MP3) via Local Server. No limits, no waiting, max speed. Features: Type icons (Video/Audio), auto-clean, fixed UI.
// @description:pt-BR A melhor ferramenta para baixar YouTube! Baixe Vídeos (Full HD/4K/8K) e Áudio (MP3) via Servidor Local. Sem limites, sem espera, velocidade máxima. Recursos: Ícones de tipo, limpeza automática, UI fixa.
// @description:es   ¡El mejor descargador de YouTube! Descarga video (Full HD/4K/8K) y audio (MP3) a través del servidor local. Sin límites, sin esperas, máxima velocidad. Características: Iconos de tipo, limpieza automática.
// @description:zh-CN 最好的YouTube下载器!通过本地服务器下载视频(全高清/4K/8K)和音频(MP3)。无限制,无需等待,最高速度。功能:类型图标,自动清理。
// @description:ru   Лучший загрузчик YouTube! Скачивайте видео (Full HD/4K/8K) и аудио (MP3) через локальный сервер. Без ограничений, без ожиданий, максимальная скорость.
// @description:fr   Le meilleur téléchargeur YouTube ! Téléchargez Vidéo (Full HD/4K/8K) et Audio (MP3) via serveur local. Sans limites, sans attente, vitesse maximale.
// @description:de   Der beste YouTube-Downloader! Video (Full HD/4K/8K) & Audio (MP3) über lokalen Server herunterladen. Keine Limits, keine Wartezeit, maximale Geschwindigkeit.
// @description:ja   最高のYouTubeダウンローダー!ローカルサーバー経由でビデオ(フルHD / 4K / 8K)とオーディオ(MP3)をダウンロードします。制限なし、待機なし、最高速度。
// @description:it   Il miglior downloader di YouTube! Scarica video (Full HD/4K/8K) e audio (MP3) tramite server locale. Nessun limite, nessuna attesa, massima velocità.
// @description:hi   सर्वश्रेष्ठ यूट्यूब डाउनलोडर! स्थानीय सर्वर के माध्यम से वीडियो (पूर्ण एचडी/4K/8K) और ऑडियो (MP3) डाउनलोड करें। कोई सीमा नहीं, कोई प्रतीक्षा नहीं, अधिकतम गति।
// @description:id   Pengunduh YouTube Terbaik! Unduh Video (Full HD/4K/8K) & Audio (MP3) melalui Server Lokal. Tanpa batas, tanpa menunggu, kecepatan maksimal.
// @description:ko   최고의 YouTube 다운로더! 로컬 서버를 통해 비디오(Full HD/4K/8K) 및 오디오(MP3)를 다운로드하십시오. 제한 없음, 대기 없음, 최대 속도.
// @description:ar   أفضل تنزيل يوتيوب! قم بتنزيل الفيديو (Full HD/4K/8K) والصوت (MP3) عبر الخادم المحلي. لا حدود ، لا انتظار ، أقصى سرعة.
// @copyright    2025, Tauã B. Kloch Leite - All Rights Reserved.
// @author       Tauã B. Kloch Leite
// @icon         https://img.icons8.com/?size=100&id=9F8aDI7mYs6V&format=png&color=000000
// @match        https://www.youtube.com/*
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_openInTab
// @grant        GM_setClipboard
// ==/UserScript==

(function () {
  'use strict';

  let policy = null;
  if (window.trustedTypes && window.trustedTypes.createPolicy) {
      try { policy = window.trustedTypes.createPolicy('yt-dl-policy', { createHTML: (s) => s }); } catch (e) {}
  }
  const safeHTML = (html) => policy ? policy.createHTML(html) : html;
  const SERVER_URL = "http://127.0.0.1:5000";
  const POLLING_INTERVAL = 3000;

  const ICONS = {
        pix: "https://upload.wikimedia.org/wikipedia/commons/a/a2/Logo%E2%80%94pix_powered_by_Banco_Central_%28Brazil%2C_2020%29.svg",
        paypal: "https://www.paypalobjects.com/webstatic/icon/pp258.png",
        btc: "https://cryptologos.cc/logos/bitcoin-btc-logo.svg?v=025",
        eth: "https://cryptologos.cc/logos/ethereum-eth-logo.svg?v=025",
        sol: "https://cryptologos.cc/logos/solana-sol-logo.svg?v=025",
        bnb: "https://cryptologos.cc/logos/bnb-bnb-logo.svg?v=025",
        matic: "https://cryptologos.cc/logos/polygon-matic-logo.svg?v=025",
        usdt: "https://cryptologos.cc/logos/tether-usdt-logo.svg?v=025",
        ada: "https://cryptologos.cc/logos/cardano-ada-logo.svg?v=025",
        doge: "https://cryptologos.cc/logos/dogecoin-doge-logo.svg?v=025"
  };

  const T = {
    title: "Downloader Local v2.2", tab_dl: "Downloads", tab_sup: "Doação", vid: "🎬 Vídeo", aud: "🎵 Áudio", queue: "Fila", done: "Prontos", err: "Erros", refresh: "🔄 Atualizar", clear: "🗑️ Limpar Lista", conn_err: "Servidor Offline?", open: "Abrir", folder: "Pasta", sup_title: "APOIE O PROJETO", sup_desc: "Mantenha as atualizações vivas!", lbl_pix: "CHAVE PIX", btn_copy: "COPIAR", auto_dl: "⬇️ Salvo: ", open_panel: "🚀 Abrir Painel Server", toggle: "👁️ Mostrar/Ocultar"
  };

  const state = { enabledUI: GM_getValue("yt_dl_enabledUI", true), stats: {}, items: [], activeTab: 'dl' };
  const setEnabledUI = (v) => { state.enabledUI = v; GM_setValue("yt_dl_enabledUI", v); renderPanel(); };
  const getHistory = () => GM_getValue('yt_dl_history_local', []);
  const addToHistory = (f) => { let h=getHistory(); if(!h.includes(f)){ h.push(f); if(h.length>50)h.shift(); GM_setValue('yt_dl_history_local', h); }};

  const clearList = async () => {
      try { await fetch(`${SERVER_URL}/clear`, { method: 'POST' }); } catch(e){}
      GM_setValue('yt_dl_history_local', []);
      state.items = [];
      state.stats = { total:0, in_progress:0, finished:0, errors:0 };
      renderPanel();
  };

  const css = `
    .yt-dl-panel { position: fixed; bottom: 30px; left: 30px; width: 340px; background: #0f0f0f; color: #fff; border-radius: 12px; z-index: 2147483647; font-family: 'Roboto', sans-serif; border: 1px solid #333; font-size: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.9); display: flex; flex-direction: column; }
    .yt-dl-head { background: #1a1a1a; display: flex; flex-direction: column; border-bottom: 1px solid #333; flex-shrink: 0; }
    .yt-dl-head-top { padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; }
    .yt-dl-tabs { display: flex; width: 100%; background: #111; }
    .yt-dl-tab { flex: 1; text-align: center; padding: 10px 0; cursor: pointer; color: #666; border-bottom: 2px solid transparent; font-weight: 700; text-transform: uppercase; font-size: 11px; transition: 0.2s; }
    .yt-dl-tab.active { color: #fff; border-bottom: 2px solid #d63384; background: #222; }

    .yt-dl-body { padding: 0; flex: 1; overflow-y: auto; max-height: 50vh; }
    .yt-dl-content { padding: 15px; }

    .yt-dl-btn-group { display: flex; gap: 8px; margin-bottom: 15px; }
    .yt-dl-btn { flex: 1; border: none; padding: 10px; border-radius: 6px; cursor: pointer; color: #fff; font-weight: 700; font-size: 13px; display: flex; align-items: center; justify-content: center; gap: 5px; transition: 0.2s; box-shadow: 0 2px 5px rgba(0,0,0,0.2); }
    .yt-dl-btn:hover { transform: translateY(-1px); filter: brightness(1.1); }
    .btn-blue { background: #3ea6ff; color: #000; }
    .btn-purple { background: #d63384; color: #fff; }
    .btn-gray { background: #333; border: 1px solid #444; }
    .btn-red { background: #d32f2f; }

    .yt-dl-item { display: flex; align-items: center; gap: 10px; padding: 8px 0; border-bottom: 1px solid #222; }
    .yt-dl-thumb { width: 44px; height: 44px; background: #000; border-radius: 6px; object-fit: cover; }
    .yt-dl-info { flex: 1; overflow: hidden; display: flex; flex-direction: column; justify-content: center; }
    .yt-dl-name { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; font-weight: 500; font-size: 12px; margin-bottom: 2px; }
    .yt-dl-meta { font-size: 10px; display: flex; align-items: center; gap: 6px; }

    .tag-type { padding: 2px 6px; border-radius: 4px; font-weight: bold; font-size: 9px; text-transform: uppercase; }
    .tag-vid { background: #0f3d5c; color: #3ea6ff; border: 1px solid #1e5985; }
    .tag-aud { background: #3c1f30; color: #ff66b2; border: 1px solid #7d2a58; }

    .action-group { display: flex; gap: 5px; }
    .btn-open { background: none; border: 1px solid #4caf50; color: #4caf50; cursor: pointer; font-size: 10px; border-radius: 4px; padding: 4px 8px; font-weight: bold; }
    .btn-open:hover { background: #4caf50; color: #000; }
    .btn-folder { background: none; border: 1px solid #aaa; color: #aaa; cursor: pointer; font-size: 10px; border-radius: 4px; padding: 4px 8px; }
    .btn-folder:hover { background: #eee; color: #000; }

    .sup-box { padding: 20px; text-align: center; }
    .sup-row { display: flex; align-items: center; gap: 8px; background: #1a1a1a; padding: 8px; border-radius: 6px; border: 1px solid #333; margin-bottom: 8px; }
    .sup-icon { width: 20px; height: 20px; object-fit: contain; }
    .sup-val { flex: 1; background: none; border: none; color: #eee; font-size: 11px; font-family: monospace; outline: none; }
    .sup-copy { background: #d63384; border: none; color: #fff; border-radius: 4px; cursor: pointer; font-size: 10px; padding: 4px 8px; }

    /* CORREÇÃO PAYPAL ICONE */
    .paypal-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; background: #003087; color: white; padding: 8px 20px; border-radius: 20px; text-decoration: none; font-weight: bold; margin: 20px auto 0 auto; width: fit-content; font-size: 12px; }
    .paypal-btn img { height: 20px !important; margin: 0; }
  `;
  const injectCSS = () => { if(!document.getElementById("yt-dl-style")) { const s=document.createElement("style"); s.id="yt-dl-style"; s.textContent=css; document.head.appendChild(s); }};
  const toast = (msg, success=true) => {
      const el=document.createElement("div");
      el.textContent=msg;
      el.style.cssText=`position:fixed;top:20px;right:20px;background:${success?'#28a745':'#f44336'};color:#fff;padding:10px 20px;border-radius:4px;z-index:999999;font-weight:bold;box-shadow:0 5px 15px rgba(0,0,0,0.5)`;
      document.body.appendChild(el);
      setTimeout(()=>el.remove(), 3000);
  };

  const openLocalFile = async (filename) => {
      try { await fetch(`${SERVER_URL}/open_file`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({filename: filename}) }); } catch(e) { toast(T.conn_err, false); }
  };
  // AGORA ENVIA O TIPO (VIDEO/AUDIO) PARA ABRIR A PASTA CERTA
  const openFolder = async (type) => {
      try { await fetch(`${SERVER_URL}/open_folder`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({type: type}) }); } catch(e) { toast(T.conn_err, false); }
  };
  const copyToClipboard = (text) => { GM_setClipboard(text); toast("Copiado!"); };

  const refreshData = async () => {
      try {
          const [sRes, fRes] = await Promise.all([ fetch(`${SERVER_URL}/stats`), fetch(`${SERVER_URL}/files`) ]);
          state.stats = await sRes.json();
          const files = await fRes.json();
          state.items = files.items || [];
          state.items.forEach(i => {
              if(i.status === 'finished' && i.filename && !getHistory().includes(i.filename)) {
                  addToHistory(i.filename);
                  toast(T.auto_dl + i.title.substring(0,20)+"...");
              }
          });
          renderPanel();
      } catch (e) {}
  };

  const send = async (type) => {
      try {
          await fetch(`${SERVER_URL}/${type === 'video' ? 'download' : 'download_audio'}`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({videoUrl: location.href}) });
          refreshData(); toast("OK ✅");
      } catch(e) { toast(T.conn_err, false); }
  };

  let panel;
  const renderPanel = () => {
      injectCSS();
      if(!state.enabledUI) { if(panel) panel.style.display='none'; return; }
      if(!panel) { panel=document.createElement('div'); panel.className='yt-dl-panel'; document.body.appendChild(panel); }
      panel.style.display='flex';

      const bodyDiv = panel.querySelector('.yt-dl-body');
      const scrollTop = bodyDiv ? bodyDiv.scrollTop : 0;

      const dlContent = `
        <div class="yt-dl-content">
            <div class="yt-dl-btn-group">
                <button class="yt-dl-btn btn-blue" id="btn-vid">${T.vid}</button>
                <button class="yt-dl-btn btn-purple" id="btn-aud">${T.aud}</button>
            </div>
            <div style="font-size:10px; color:#aaa; display:flex; justify-content:space-between; margin-bottom:10px; background:#1a1a1a; padding:8px; border-radius:6px;">
                <span>${T.queue}: <b style="color:#ffeb3b">${state.stats.in_progress||0}</b></span>
                <span>${T.done}: <b style="color:#4caf50">${state.stats.finished||0}</b></span>
                <span>${T.err}: <b style="color:#f44336">${state.stats.errors||0}</b></span>
            </div>

            <div id="yt-dl-list">${state.items.slice().reverse().slice(0,5).map(i => {
                const isAud = i.type === 'audio';
                const tagClass = isAud ? 'tag-aud' : 'tag-vid';
                const tagText = isAud ? 'MP3' : 'MP4';
                const icon = isAud ? '🎵' : '🎬';
                return `
                <div class="yt-dl-item">
                    <img class="yt-dl-thumb" src="${i.thumb||''}">
                    <div class="yt-dl-info">
                        <div class="yt-dl-name" title="${i.title}">${icon} ${i.title||'...'}</div>
                        <div class="yt-dl-meta">
                            <span class="tag-type ${tagClass}">${tagText}</span>
                            <span style="color:${i.status==='finished'?'#4caf50':'#aaa'}">${i.status}</span>
                        </div>
                    </div>
                    ${i.status==='finished' ? `
                    <div class="action-group">
                        <button class="btn-open" data-file="${encodeURIComponent(i.filename)}">▶️</button>
                        <button class="btn-folder" data-type="${i.type}" title="${T.folder}">📂</button>
                    </div>` : ''}
                </div>`;
            }).join('')}</div>

            <div style="margin-top:15px; display:flex; gap:10px;">
                <button class="yt-dl-btn btn-gray" id="btn-refresh" style="font-size:11px; padding:6px; flex:2;">${T.refresh}</button>
                <button class="yt-dl-btn btn-red" id="btn-clear" style="font-size:11px; padding:6px; flex:1;">${T.clear}</button>
            </div>
        </div>`;

      // LISTA CRIPTO
      const cryptoList = [
          {img: ICONS.btc, name: "BTC", val: "bc1q6gz3dtj9qvlxyyh3grz35x8xc7hkuj07knlemn"},
          {img: ICONS.eth, name: "ETH", val: "0xd8724d0b19d355e9817d2a468f49e8ce067e70a6"},
          {img: ICONS.sol, name: "SOL", val: "7ztAogE7SsyBw7mwVHhUr5ZcjUXQr99JoJ6oAgP99aCn"},
          {img: ICONS.usdt, name: "USDT (BEP20)", val: "0xd8724d0b19d355e9817d2a468f49e8ce067e70a6"},
          {img: ICONS.bnb, name: "BNB", val: "0xd8724d0b19d355e9817d2a468f49e8ce067e70a6"},
          {img: ICONS.matic, name: "MATIC", val: "0xd8724d0b19d355e9817d2a468f49e8ce067e70a6"},
          {img: ICONS.ada, name: "ADA", val: "addr1q8..."} // Adicione sua carteira se quiser
      ].map(c => `<div class="sup-row"><img src="${c.img}" class="sup-icon"><span style="font-size:9px;color:#888;width:30px">${c.name}</span><input type="text" class="sup-val" readonly value="${c.val}"><button class="sup-copy" data-val="${c.val}">${T.btn_copy}</button></div>`).join('');

      const supContent = `<div class="sup-box"><div style="color:#d63384;font-weight:bold;margin-bottom:5px">${T.sup_title}</div><div style="color:#aaa;font-size:11px;margin-bottom:15px">${T.sup_desc}</div><div style="text-align:left;color:#d63384;font-weight:bold;font-size:10px;margin-bottom:5px">${T.lbl_pix}</div><div class="sup-row"><img src="${ICONS.pix}" class="sup-icon"><input type="text" class="sup-val" readonly value="69993230419"><button class="sup-copy" data-val="69993230419">${T.btn_copy}</button></div><div style="text-align:left;color:#d63384;font-weight:bold;font-size:10px;margin:15px 0 5px">CRYPTO WALLETS</div>${cryptoList}<a href="https://www.paypal.com/donate/?business=4J4UK7ACU3DS6" target="_blank" class="paypal-btn"><img src="${ICONS.paypal}"> PayPal</a></div>`;

      const html = `
        <div class="yt-dl-head">
            <div class="yt-dl-head-top"><span style="font-weight:700;color:#fff;font-size:13px;">${T.title}</span><span id="yt-dl-close" style="cursor:pointer;color:#aaa;font-size:16px;">×</span></div>
            <div class="yt-dl-tabs"><div class="yt-dl-tab ${state.activeTab==='dl'?'active':''}" id="tab-btn-dl">${T.tab_dl}</div><div class="yt-dl-tab ${state.activeTab==='sup'?'active':''}" id="tab-btn-sup">${T.tab_sup}</div></div>
        </div>
        <div class="yt-dl-body">${state.activeTab === 'dl' ? dlContent : supContent}</div>
        <div style="background:#000;padding:8px;text-align:center;font-size:9px;color:#444;border-top:1px solid #222">Developed for <b>Tauã B. Kloch Leite</b> © 2025</div>
      `;
      panel.innerHTML = safeHTML(html);

      if(panel.querySelector('.yt-dl-body')) panel.querySelector('.yt-dl-body').scrollTop = scrollTop;

      document.getElementById('yt-dl-close').onclick = () => setEnabledUI(false);
      document.getElementById('tab-btn-dl').onclick = () => { state.activeTab='dl'; renderPanel(); };
      document.getElementById('tab-btn-sup').onclick = () => { state.activeTab='sup'; renderPanel(); };

      if(state.activeTab === 'dl') {
          document.getElementById('btn-vid').onclick = () => send('video');
          document.getElementById('btn-aud').onclick = () => send('audio');
          document.getElementById('btn-refresh').onclick = refreshData;
          document.getElementById('btn-clear').onclick = clearList;
          panel.querySelectorAll('.btn-open').forEach(b => b.onclick = (e) => openLocalFile(decodeURIComponent(e.target.dataset.file)));
          // AQUI: Passa o dataset.type para a função abrir a pasta certa
          panel.querySelectorAll('.btn-folder').forEach(b => b.onclick = (e) => openFolder(e.target.dataset.type));
      } else {
          panel.querySelectorAll('.sup-copy').forEach(btn => { btn.onclick = (e) => copyToClipboard(e.target.dataset.val); });
      }
  };

  const addInlineButtons = () => {
    const container = document.querySelector('[id^="top-level-buttons"]');
    if (!container || container.querySelector("#yt-dl-inline-vid")) return;
    const btnV = document.createElement("button"); btnV.id = "yt-dl-inline-vid"; btnV.textContent = T.vid; btnV.style.cssText = "background:#3ea6ff;color:#000;border:none;padding:6px 12px;border-radius:18px;margin-left:8px;cursor:pointer;font-weight:bold;"; btnV.onclick = (e) => { e.preventDefault(); send('video'); }; container.appendChild(btnV);
  };
  const observer = new MutationObserver(addInlineButtons);
  observer.observe(document.body, { childList: true, subtree: true });

  setInterval(refreshData, POLLING_INTERVAL);
  window.addEventListener("keydown", (e) => { if (e.altKey && e.shiftKey && (e.key === "Y" || e.key === "y")) { setEnabledUI(!state.enabledUI); e.preventDefault(); } });

  // ITENS DO MENU
  GM_registerMenuCommand(`${T.toggle} (Alt+Shift+Y)`, () => setEnabledUI(!state.enabledUI));
  GM_registerMenuCommand(`${T.open_panel}`, () => GM_openInTab(SERVER_URL + '/panel', {active:true}));

  renderPanel(); refreshData();
})();