Greasy Fork

来自缓存

Greasy Fork is available in English.

TypeMonkey视频&音频解析器

高级视频&音频解析器,支持抖音无水印和流媒体格式

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

// ==UserScript==
// @name         TypeMonkey视频&音频解析器
// @namespace    https://github.com/yourname
// @version      2.5
// @description  高级视频&音频解析器,支持抖音无水印和流媒体格式
// @author       YourName
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        GM_notification
// @grant        GM_setClipboard
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @connect      *
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';
    
    // 抖音无水印解析API
    const DOUYIN_API = "https://api.douyin.wtf/api?url=";
    
    // 全局变量
    let floatBall = null;
    let panel = null;
    let lastPosition = {
        x: GM_getValue('lastPositionX', 20),
        y: GM_getValue('lastPositionY', 20)
    };
    let isExpanded = false;
    let sourcesCount = 0;

    // 添加全局样式
    GM_addStyle(`
        #videoParserFloatBall {
            position: fixed;
            width: 50px;
            height: 50px;
            background: linear-gradient(145deg, #4a76c6, #3a66b6);
            border-radius: 50%;
            cursor: pointer;
            z-index: 99999;
            display: flex;
            justify-content: center;
            align-items: center;
            color: white;
            font-size: 24px;
            box-shadow: 0 6px 15px rgba(0,0,0,0.3);
            transition: all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
            user-select: none;
        }
        
        #videoParserFloatBall.badge::after {
            content: '';
            position: absolute;
            top: -5px;
            right: -5px;
            width: 15px;
            height: 15px;
            background: #ff4757;
            border-radius: 50%;
            border: 2px solid white;
        }
        
        #videoParserPanel {
            position: fixed;
            z-index: 99998;
            background: #2d3a4b;
            color: #ecf0f1;
            padding: 0;
            border-radius: 15px;
            box-shadow: 0 12px 30px rgba(0,0,0,0.4);
            width: 95%;
            max-width: 420px;
            max-height: 85vh;
            overflow: hidden;
            font-family: system-ui, -apple-system, sans-serif;
            display: flex;
            flex-direction: column;
            display: none;
            transform-origin: top center;
            font-size: 14px;
            border: 1px solid #3a4a60;
        }
        
        #videoParserPanel .header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            padding: 15px;
            background: #1e2a38;
            color: white;
            cursor: move;
            border-bottom: 1px solid #3a4a60;
            user-select: none;
        }
        
        #videoParserPanel .header h3 {
            margin: 0;
            font-size: 16px;
            font-weight: 600;
            color: #3498db;
        }
        
        #videoParserPanel .header .buttons {
            display: flex;
            gap: 10px;
        }
        
        #videoParserPanel .header button {
            width: 30px;
            height: 30px;
            background: #3a4a60;
            color: white;
            border: none;
            border-radius: 6px;
            cursor: pointer;
            display: flex;
            justify-content: center;
            align-items: center;
            font-size: 18px;
            transition: background 0.3s;
        }
        
        #videoParserPanel .header button:hover {
            background: #4a5a70;
        }
        
        #videoParserPanel .header .close:hover {
            background: #ff4757;
        }
        
        #panelContent {
            overflow-y: auto;
            padding: 15px;
            max-height: calc(85vh - 60px);
            background: #1e2a38;
            font-size: 14px;
        }
        
        .source-item {
            margin: 15px 0;
            padding: 15px;
            background: #2d3a4b;
            border-radius: 10px;
            box-shadow: 0 4px 10px rgba(0,0,0,0.2);
            border: 1px solid #3a4a60;
        }
        
        .source-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 12px;
        }
        
        .source-info {
            font-weight: bold;
            display: flex;
            align-items: center;
            gap: 10px;
        }
        
        .source-url {
            word-break: break-all;
            font-size: 13px;
            color: #bdc3c7;
            padding: 12px;
            background: #1a2432;
            border-radius: 8px;
            max-height: 80px;
            overflow-y: auto;
            margin-bottom: 15px;
            border: 1px solid #2d3a4b;
        }
        
        .btn-group {
            display: flex;
            gap: 10px;
            flex-wrap: wrap;
        }
        
        .btn-group button {
            padding: 8px 15px;
            color: white;
            border: none;
            border-radius: 8px;
            cursor: pointer;
            flex: 1;
            font-size: 13px;
            min-width: 100px;
            font-weight: 600;
            transition: all 0.3s;
        }
        
        .btn-group button:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0,0,0,0.2);
        }
        
        .copy-btn {
            background: linear-gradient(145deg, #2ecc71, #27ae60);
        }
        
        .douyin-btn {
            background: linear-gradient(145deg, #e74c3c, #c0392b);
        }
        
        .download-btn {
            background: linear-gradient(145deg, #9b59b6, #8e44ad);
        }
        
        .audio-btn {
            background: linear-gradient(145deg, #f39c12, #d35400);
        }
        
        .empty-msg {
            text-align: center;
            padding: 40px 20px;
            color: #7f8c8d;
        }
        
        .empty-msg p {
            margin: 10px 0;
        }
        
        @media (max-width: 500px) {
            #videoParserPanel {
                width: 98%;
                max-width: none;
                left: 1% !important;
                right: 1% !important;
                transform: none !important;
                max-width: unset;
            }
            
            .btn-group button {
                min-width: 45%;
            }
        }
    `);

    // 创建球形悬浮按钮
    function createFloatBall() {
        if (floatBall) return;
        
        floatBall = document.createElement('div');
        floatBall.id = 'videoParserFloatBall';
        floatBall.textContent = '🎥';
        floatBall.title = '视频&音频解析器';
        
        // 定位
        floatBall.style.left = `${lastPosition.x}px`;
        floatBall.style.top = `${lastPosition.y}px`;
        
        // 添加拖拽功能
        let isDragging = false;
        let offsetX, offsetY;
        
        floatBall.addEventListener('mousedown', startDrag);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', stopDrag);
        
        function startDrag(e) {
            isDragging = true;
            offsetX = e.clientX - floatBall.getBoundingClientRect().left;
            offsetY = e.clientY - floatBall.getBoundingClientRect().top;
            floatBall.style.cursor = 'grabbing';
            floatBall.style.boxShadow = '0 10px 25px rgba(0,0,0,0.4)';
        }
        
        function drag(e) {
            if (!isDragging) return;
            floatBall.style.left = `${e.clientX - offsetX}px`;
            floatBall.style.top = `${e.clientY - offsetY}px`;
            lastPosition = { 
                x: e.clientX - offsetX,
                y: e.clientY - offsetY
            };
            GM_setValue('lastPositionX', lastPosition.x);
            GM_setValue('lastPositionY', lastPosition.y);
        }
        
        function stopDrag() {
            isDragging = false;
            floatBall.style.cursor = 'pointer';
            floatBall.style.boxShadow = '0 6px 15px rgba(0,0,0,0.3)';
        }
        
        // 点击展开面板
        floatBall.addEventListener('click', async () => {
            if (isExpanded) {
                collapsePanel();
            } else {
                const sources = await findVideoSources();
                showResults(sources);
            }
        });
        
        document.body.appendChild(floatBall);
    }

    // 展开面板
    function expandPanel() {
        if (!panel) return;
        
        isExpanded = true;
        floatBall.style.display = 'none';
        
        // 设置面板初始位置
        const rect = floatBall.getBoundingClientRect();
        panel.style.left = `${rect.left}px`;
        panel.style.top = `${rect.top}px`;
        panel.style.display = 'flex';
        panel.style.transform = 'scale(0.5) translateY(-30px)';
        panel.style.opacity = '0';
        
        // 动画效果
        setTimeout(() => {
            panel.style.transition = 'all 0.5s cubic-bezier(0.68, -0.55, 0.265, 1.55)';
            panel.style.transform = 'scale(1) translateY(0)';
            panel.style.opacity = '1';
        }, 10);
    }

    // 收起面板
    function collapsePanel() {
        if (!panel || !floatBall) return;
        
        isExpanded = false;
        
        // 动画效果
        panel.style.transition = 'all 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55)';
        panel.style.transform = 'scale(0.5) translateY(-30px)';
        panel.style.opacity = '0';
        
        setTimeout(() => {
            panel.style.display = 'none';
            floatBall.style.display = 'flex';
            floatBall.style.left = `${lastPosition.x}px`;
            floatBall.style.top = `${lastPosition.y}px`;
        }, 400);
    }

    // 主解析函数
    async function findVideoSources() {
        const sources = [];
        const now = Date.now();
        const uniqueSources = new Set();

        // 1. 解析标准视频元素
        document.querySelectorAll('video').forEach(video => {
            if (video.src) addUniqueSource(sources, uniqueSources, 'video', video.src);
            video.querySelectorAll('source').forEach(s => {
                if (s.src) addUniqueSource(sources, uniqueSources, 'source', s.src);
            });
        });
        
        // 2. 解析音频元素
        document.querySelectorAll('audio').forEach(audio => {
            if (audio.src) addUniqueSource(sources, uniqueSources, 'audio', audio.src);
            audio.querySelectorAll('source').forEach(s => {
                if (s.src) addUniqueSource(sources, uniqueSources, 'audio-source', s.src);
            });
        });

        // 3. 解析iframe嵌入视频
        const iframePromises = [];
        document.querySelectorAll('iframe').forEach(iframe => {
            const src = iframe.src || iframe.dataset.src;
            if (!src) return;
            
            // 抖音特殊处理
            if (src.includes("douyin.com") || src.includes("iesdouyin.com")) {
                iframePromises.push(
                    parseDouyin(src).then(url => {
                        if (url) addUniqueSource(sources, uniqueSources, 'douyin', url);
                    })
                );
            } 
            else if (/youtube|vimeo|bilibili/.test(src)) {
                addUniqueSource(sources, uniqueSources, 'iframe', src);
            }
        });

        // 等待所有抖音解析完成
        await Promise.all(iframePromises);

        // 更新计数
        sourcesCount = sources.length;
        if (sourcesCount > 0 && !isExpanded) {
            floatBall.classList.add('badge');
        }

        return sources;
    }

    // 添加唯一来源
    function addUniqueSource(sources, uniqueSet, type, url) {
        if (!url || uniqueSet.has(url)) return;
        uniqueSet.add(url);
        sources.push(createSource(type, url));
    }

    // 创建来源对象
    function createSource(type, url) {
        return {
            type: type,
            url: url,
            timestamp: Date.now(),
            isStream: /\.(m3u8|mpd)(\?|$)/i.test(url),
            isAudio: /audio|audio-source/.test(type)
        };
    }

    // 抖音无水印解析
    function parseDouyin(url) {
        return new Promise(resolve => {
            GM_xmlhttpRequest({
                method: "GET",
                url: `${DOUYIN_API}${encodeURIComponent(url)}`,
                timeout: 8000,
                onload: function(response) {
                    try {
                        const data = JSON.parse(response.responseText);
                        resolve(data.nwm_video_url || null);
                    } catch (e) {
                        resolve(null);
                    }
                },
                onerror: function() {
                    resolve(null);
                },
                ontimeout: function() {
                    resolve(null);
                }
            });
        });
    }

    // 显示结果面板
    function showResults(sources) {
        if (panel) panel.remove();
        
        panel = document.createElement('div');
        panel.id = 'videoParserPanel';
        
        // 标题栏
        const header = document.createElement('div');
        header.className = 'header';
        
        const title = document.createElement('h3');
        title.textContent = sources.length > 0 ? 
            `🎥 发现 ${sources.length} 个媒体源` : 
            `⚠️ 未检测到媒体源`;

        const buttonContainer = document.createElement('div');
        buttonContainer.className = 'buttons';

        // 刷新按钮
        const refreshBtn = document.createElement('button');
        refreshBtn.textContent = '↻';
        refreshBtn.title = '重新扫描';

        // 收起按钮
        const minimizeBtn = document.createElement('button');
        minimizeBtn.textContent = '−';
        minimizeBtn.title = '收起/展开';
        
        // 关闭按钮
        const closeBtn = document.createElement('button');
        closeBtn.textContent = '×';
        closeBtn.title = '关闭';
        closeBtn.className = 'close';
        
        buttonContainer.appendChild(refreshBtn);
        buttonContainer.appendChild(minimizeBtn);
        buttonContainer.appendChild(closeBtn);
        
        header.appendChild(title);
        header.appendChild(buttonContainer);

        // 内容区域
        const content = document.createElement('div');
        content.id = 'panelContent';
        
        if (sources.length > 0) {
            const list = document.createElement('div');
            
            sources.forEach((src, i) => {
                const item = document.createElement('div');
                item.className = 'source-item';
                
                const headerRow = document.createElement('div');
                headerRow.className = 'source-header';
                
                const sourceInfo = document.createElement('div');
                sourceInfo.className = 'source-info';
                
                const sourceType = document.createElement('span');
                let typeText = src.type.toUpperCase();
                if (src.isAudio) {
                    sourceType.style.color = '#f39c12';
                    typeText = '音频资源';
                } else if (src.type === 'douyin') {
                    sourceType.style.color = '#e74c3c';
                } else if (src.type === 'stream') {
                    sourceType.style.color = '#9b59b6';
                } else {
                    sourceType.style.color = '#3498db';
                }
                sourceType.textContent = `[${typeText}]`;
                
                const sourceIndex = document.createElement('span');
                sourceIndex.textContent = `#${i+1}`;
                
                sourceInfo.appendChild(sourceIndex);
                sourceInfo.appendChild(sourceType);
                
                const urlText = document.createElement('div');
                urlText.className = 'source-url';
                urlText.textContent = src.url;
                
                const btnGroup = document.createElement('div');
                btnGroup.className = 'btn-group';
                
                // 复制按钮
                const copyBtn = document.createElement('button');
                copyBtn.textContent = '复制链接';
                copyBtn.className = 'copy-btn';
                copyBtn.dataset.url = src.url;
                
                // 下载按钮
                let downloadBtn = null;
                if (src.isAudio) {
                    downloadBtn = document.createElement('button');
                    downloadBtn.textContent = '下载音频';
                    downloadBtn.className = 'audio-btn';
                    downloadBtn.dataset.download = src.url;
                } else if (src.type === 'douyin') {
                    downloadBtn = document.createElement('button');
                    downloadBtn.textContent = '无水印下载';
                    downloadBtn.className = 'douyin-btn';
                    downloadBtn.dataset.download = src.url;
                } else if (!src.isStream) {
                    downloadBtn = document.createElement('button');
                    downloadBtn.textContent = '下载视频';
                    downloadBtn.className = 'download-btn';
                    downloadBtn.dataset.download = src.url;
                }
                
                btnGroup.appendChild(copyBtn);
                if (downloadBtn) btnGroup.appendChild(downloadBtn);
                
                item.appendChild(headerRow);
                item.appendChild(urlText);
                item.appendChild(btnGroup);
                headerRow.appendChild(sourceInfo);
                list.appendChild(item);
            });
            
            content.appendChild(list);
        } else {
            const emptyMsg = document.createElement('div');
            emptyMsg.className = 'empty-msg';
            emptyMsg.innerHTML = `
                
📹


                
未找到视频或音频资源


                
尝试播放媒体后重新扫描


            `;
            content.appendChild(emptyMsg);
        }

        // 组装面板
        panel.appendChild(header);
        panel.appendChild(content);
        document.body.appendChild(panel);
        
        // 添加事件监听器
        function setupEventListeners() {
            // 复制功能
            content.querySelectorAll('.copy-btn').forEach(btn => {
                btn.addEventListener('click', () => {
                    GM_setClipboard(btn.dataset.url);
                    GM_notification({
                        title: '复制成功',
                        text: '链接已复制到剪贴板',
                        timeout: 2000
                    });
                });
            });
            
            // 下载功能
            content.querySelectorAll('.download-btn, .audio-btn, .douyin-btn').forEach(btn => {
                btn.addEventListener('click', () => {
                    const url = btn.dataset.download;
                    const isAudio = btn.classList.contains('audio-btn');
                    let fileExtension = 'mp4';
                    
                    if (isAudio) {
                        fileExtension = url.includes('.m4a') ? 'm4a' : 
                                       url.includes('.aac') ? 'aac' : 
                                       url.includes('.wav') ? 'wav' : 'mp3';
                    } 
                    
                    const filename = `${isAudio ? 'audio' : 'video'}_${Date.now()}.${fileExtension}`;
                    
                    GM_download({
                        url: url,
                        name: filename
                    });
                });
            });
            
            // 刷新功能
            refreshBtn.addEventListener('click', async () => {
                refreshBtn.textContent = '...';
                refreshBtn.disabled = true;
                const newSources = await findVideoSources();
                showResults(newSources);
            });
            
            // 收起功能
            minimizeBtn.addEventListener('click', collapsePanel);
            
            // 关闭功能
            closeBtn.addEventListener('click', () => {
                if (panel) panel.remove();
                if (floatBall) floatBall.remove();
                panel = null;
                floatBall = null;
            });
            
            // 拖动功能
            let isDragging = false;
            let offsetX, offsetY;
            
            header.addEventListener('mousedown', startDrag);
            document.addEventListener('mousemove', drag);
            document.addEventListener('mouseup', stopDrag);
            
            function startDrag(e) {
                if (e.target.tagName === 'BUTTON') return;
                isDragging = true;
                offsetX = e.clientX - panel.getBoundingClientRect().left;
                offsetY = e.clientY - panel.getBoundingClientRect().top;
                panel.style.cursor = 'grabbing';
            }
            
            function drag(e) {
                if (!isDragging) return;
                panel.style.left = `${e.clientX - offsetX}px`;
                panel.style.top = `${e.clientY - offsetY}px`;
            }
            
            function stopDrag() {
                isDragging = false;
                panel.style.cursor = 'default';
            }
        }
        
        setupEventListeners();
        expandPanel();
    }

    // 主执行函数
    function init() {
        createFloatBall();
        
        // 初始扫描
        setTimeout(async () => {
            await findVideoSources();
        }, 5000);
    }
    
    // 等待页面加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();