Greasy Fork

来自缓存

Greasy Fork is available in English.

YouTube to DownSub (With Format Choice)

Automated subtitle download via DownSub with format selection popup

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         YouTube to DownSub (With Format Choice)
// @namespace    http://tampermonkey.net/
// @version      4.0
// @description  Automated subtitle download via DownSub with format selection popup
// @author       You
// @match        https://www.youtube.com/*
// @match        https://downsub.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=downsub.com
// @grant        window.close
// ==/UserScript==

(function() {
    'use strict';

    // ==========================================
    // PART 1: YOUTUBE
    // ==========================================
    if (window.location.hostname.includes('youtube.com')) {

        let currentUrl = location.href;

        // --- POPUP LOGIC ---
        function createFormatPopup() {
            // Remove existing if any
            if (document.getElementById('ds-popup-overlay')) return;

            // 1. Overlay
            const overlay = document.createElement('div');
            overlay.id = 'ds-popup-overlay';
            overlay.style.cssText = `
                position: fixed; top: 0; left: 0; width: 100%; height: 100%;
                background: rgba(0, 0, 0, 0.5); z-index: 9999;
                display: flex; justify-content: center; align-items: center;
            `;
            overlay.onclick = (e) => { if(e.target === overlay) closePopup(); };

            // 2. The Box
            const box = document.createElement('div');
            box.style.cssText = `
                background: #1f1f1f; padding: 20px; border-radius: 12px;
                box-shadow: 0 4px 15px rgba(0,0,0,0.5); text-align: center;
                font-family: Roboto, Arial, sans-serif; min-width: 250px;
                border: 1px solid #333; color: white;
            `;

            // 3. Title
            const title = document.createElement('h3');
            title.innerText = 'Select Format';
            title.style.margin = '0 0 20px 0';
            title.style.fontSize = '18px';

            // 4. Buttons Container
            const btnContainer = document.createElement('div');
            btnContainer.style.display = 'flex';
            btnContainer.style.justifyContent = 'space-around';
            btnContainer.style.gap = '10px';

            // 5. Button Creator Helper
            const createBtn = (text, color, formatCode) => {
                const b = document.createElement('button');
                b.innerText = text;
                b.style.cssText = `
                    padding: 10px 20px; border: none; border-radius: 6px;
                    background-color: ${color}; color: white; font-weight: bold;
                    cursor: pointer; font-size: 14px; transition: opacity 0.2s;
                `;
                b.onmouseover = () => b.style.opacity = '0.8';
                b.onmouseout = () => b.style.opacity = '1';
                b.onclick = () => {
                    openDownSub(formatCode);
                    closePopup();
                };
                return b;
            };

            const srtBtn = createBtn('SRT', '#3498db', 'SRT');
            const txtBtn = createBtn('TXT', '#e67e22', 'TXT');

            // Append everything
            btnContainer.appendChild(srtBtn);
            btnContainer.appendChild(txtBtn);
            box.appendChild(title);
            box.appendChild(btnContainer);
            overlay.appendChild(box);
            document.body.appendChild(overlay);
        }

        function closePopup() {
            const overlay = document.getElementById('ds-popup-overlay');
            if (overlay) overlay.remove();
        }

        function openDownSub(format) {
            const videoUrl = encodeURIComponent(window.location.href);
            // We append the selected format to the URL (target_fmt)
            window.open(`https://downsub.com/?url=${videoUrl}&auto_dl=1&target_fmt=${format}`, '_blank');
        }

        // --- BUTTON INJECTION ---
        function addButton() {
            if (document.getElementById('ds-download-btn')) return;

            const validSelectors = [
                '#top-level-buttons-computed',
                '#flexible-item-buttons',
                'ytd-menu-renderer #top-level-buttons-computed'
            ];

            let container = null;
            for (let sel of validSelectors) {
                container = document.querySelector(sel);
                if (container) break;
            }

            if (window.location.pathname === '/watch' && container) {
                const btn = document.createElement('button');
                btn.id = 'ds-download-btn';
                btn.innerText = 'Download Subtitle';
                btn.style.cssText = `
                    background-color: #2c3e50; color: white; border: 1px solid #e67e22;
                    border-radius: 18px; padding: 0 16px; margin-left: 8px;
                    height: 36px; font-size: 14px; font-weight: 700;
                    cursor: pointer; display: flex; align-items: center;
                `;

                btn.onmouseover = () => { btn.style.backgroundColor = '#e67e22'; };
                btn.onmouseout = () => { btn.style.backgroundColor = '#2c3e50'; };

                // Open the popup instead of direct link
                btn.onclick = createFormatPopup;

                container.appendChild(btn);
            }
        }

        const observer = new MutationObserver(() => {
            if (window.location.href !== currentUrl) {
                currentUrl = window.location.href;
                setTimeout(addButton, 500);
            }
            addButton();
        });
        observer.observe(document.body, { childList: true, subtree: true });
        setTimeout(addButton, 1000);
    }

    // ==========================================
    // PART 2: DOWNSUB
    // ==========================================
    if (window.location.hostname.includes('downsub.com')) {

        // 1. Check if we should run
        if (!window.location.href.includes('auto_dl=1')) return;

        // 2. Determine target format from URL (Default to TXT if missing)
        const urlParams = new URLSearchParams(window.location.search);
        const targetFormat = urlParams.get('target_fmt') || 'TXT';

        console.log(`⚡ Auto-Downloader Active. Target: ${targetFormat}`);

        let hasClicked = false;

        const checkLoop = setInterval(() => {
            if (hasClicked) return;

            const candidates = document.querySelectorAll('button, a');

            for (let el of candidates) {
                const text = el.textContent.trim();

                // DYNAMIC CHECK: Matches "SRT" or "TXT" depending on user choice
                // Logic: Text matches target exactly OR contains target but is NOT a translation button
                if (text === targetFormat || (text.includes(targetFormat) && !text.includes('Translate'))) {

                    console.log(`⚡ Target (${targetFormat}) found:`, el);

                    el.click();
                    hasClicked = true;
                    clearInterval(checkLoop);

                    setTimeout(() => {
                        window.close();
                    }, 1500);

                    break;
                }
            }
        }, 100);

        setTimeout(() => clearInterval(checkLoop), 15000);
    }

})();