抖音视频下载极简版

依旧是ai编的

目前為 2025-04-06 提交的版本,檢視 最新版本

// ==UserScript==
// @name         抖音视频下载极简版
// @namespace    http://tampermonkey.net/
// @version      0.7.4
// @description  依旧是ai编的
// @match        *://www.douyin.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @license MIT
// @require      https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.9/dayjs.min.js
// @connect      douyinvod.com
// @connect      *.douyinvod.com
// @run-at       document-idle
// ==/UserScript==

(function() {
    'use strict';

    let detectedVideoUrls = {};
    let lastDetectedUrl = null;
    let observer = null;
    let isDownloading = false;

    const downloadButton = document.createElement('button');
    downloadButton.id = 'douyin-smart-matcher-downloader-btn-v74';
    downloadButton.textContent = '等待视频加载...';
    downloadButton.disabled = true;

    GM_addStyle(`
        #douyin-smart-matcher-downloader-btn-v74 {
            position: fixed;
            bottom: 55px;
            right: 15px;
            z-index: 20009;
            padding: 12px 25px;
            background-color: #FF6600;
            color: white;
            border: none;
            border-radius: 30px;
            cursor: pointer;
            font-size: 15px;
            font-weight: bold;
            box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
            transition: background-color 0.2s ease, transform 0.1s ease, color 0.2s ease;
        }
        #douyin-smart-matcher-downloader-btn-v74:disabled {
            background-color: #cccccc;
            color: #666666;
            cursor: not-allowed;
            opacity: 0.7;
        }
        #douyin-smart-matcher-downloader-btn-v74:hover:not(:disabled) {
            background-color: #FF8800;
        }
        #douyin-smart-matcher-downloader-btn-v74:active:not(:disabled) {
            transform: scale(0.95);
        }
    `);

    function addButtonToPage() {
        if (document.body) {
            if (!document.getElementById(downloadButton.id)) {
                document.body.appendChild(downloadButton);
                startPerformanceObserver();
            }
        } else {
            setTimeout(addButtonToPage, 150);
        }
    }

    addButtonToPage();

    function getQueryParam(url, paramName) {
        try {
            const urlObj = new URL(url);
            return urlObj.searchParams.get(paramName);
        } catch (e) {
            const safeParamName = paramName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
            const match = url.match(new RegExp(`[?&]${safeParamName}=([^&]*)`));
            return match ? match[1] : null;
        }
    }

    function getVideoIdFromPath(pathname) {
        const match = pathname.match(/\/(?:video|note)\/(\d+)/);
        return match ? match[1] : null;
    }

    downloadButton.addEventListener('click', () => {
        let targetVid = null;
        let targetUrl = null;
        const currentUrl = window.location.href;
        const currentPathname = window.location.pathname;
        const noteIdFromPath = getVideoIdFromPath(currentPathname);

        const modalId = getQueryParam(currentUrl, 'modal_id');
        if (modalId) {
            targetVid = modalId;
            targetUrl = detectedVideoUrls[targetVid];
        }

        if (!targetUrl && noteIdFromPath) {
            targetVid = noteIdFromPath;
            targetUrl = detectedVideoUrls[targetVid];
        }

        if (!targetUrl && currentPathname.includes('/note/') && lastDetectedUrl) {
            targetUrl = lastDetectedUrl;
            let vidFromFallbackUrl = getQueryParam(targetUrl, '__vid');
            if (vidFromFallbackUrl) {
                targetVid = vidFromFallbackUrl;
            } else {
                targetVid = noteIdFromPath || 'note_unknown';
            }
        }

        if (!targetUrl) {
             let reason = '当前页面标识';
             if (modalId) reason = `modal_id (${modalId})`;
             else if (noteIdFromPath) reason = `路径ID (${noteIdFromPath})`;
            alert(`未能根据 ${reason} 找到匹配的视频链接。\n请确保视频已加载。`);
            return;
        }

        if (isDownloading) {
            return;
        }

        isDownloading = true;
        downloadButton.disabled = true;
        downloadButton.textContent = '请求中...';

        GM_xmlhttpRequest({
            method: "GET",
            url: targetUrl,
            responseType: 'blob',
            headers: {
                // --- 添加的请求头 ---
                "Referer": window.location.href, // 保留 Referer
                "User-Agent": navigator.userAgent, // 显式设置 User-Agent
                "Cookie": document.cookie, // 显式发送当前页面的 Cookie
                // --- 可以考虑添加更多通用头 ---
                 "Accept": "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5",
                 "Accept-Language": navigator.language || "en-US,en;q=0.9",
                 "Sec-Fetch-Site": "same-site", // 或 "cross-site" 取决于 douyinvod.com 和 douyin.com 的关系
                 "Sec-Fetch-Mode": "no-cors", // 视频请求通常是 no-cors
                 "Sec-Fetch-Dest": "video", // 或 "empty"
            },
            // 注意: Tampermonkey/Violentmonkey对Cookie的处理可能与手动设置冲突。
            // 如果添加Cookie后出现问题,可以尝试注释掉 "Cookie": document.cookie 这一行。
            onload: function(response) {
                 if (response.status === 200 || response.status === 206) {
                     downloadButton.textContent = '处理数据...';
                     try {
                         const blob = response.response;
                         const blobUrl = URL.createObjectURL(blob);
                         const a = document.createElement('a');
                         a.href = blobUrl;
                         const timestamp = dayjs().format('YYYYMMDD_HHmmss');
                         const filenameVid = targetVid || 'unknown';
                         a.download = `douyin_${filenameVid}_${timestamp}.mp4`;
                         a.style.display = 'none';
                         document.body.appendChild(a);
                         a.click();
                         document.body.removeChild(a);
                         setTimeout(() => URL.revokeObjectURL(blobUrl), 100);
                         downloadButton.textContent = '下载视频';
                     } catch (e) {
                         alert('处理下载数据时发生错误!');
                         downloadButton.textContent = '处理失败';
                     }
                 } else {
                     alert(`下载失败:服务器返回错误 ${response.status} ${response.statusText} (URL可能已过期或请求头无效)`);
                     downloadButton.textContent = `错误 ${response.status}`;
                 }
                  setTimeout(() => {
                      isDownloading = false;
                      downloadButton.disabled = Object.keys(detectedVideoUrls).length === 0 && !lastDetectedUrl;
                      if(!downloadButton.disabled && !downloadButton.textContent.includes('下载视频')) {
                           downloadButton.textContent = '下载失败,重试?';
                      } else if (!downloadButton.disabled) {
                           downloadButton.textContent = '下载视频';
                      } else {
                           downloadButton.textContent = '等待视频加载...';
                      }
                  }, 1500);
            },
            onerror: function(error) {
                alert(`下载失败:网络错误 ${error.error || ''}`);
                downloadButton.textContent = '网络错误';
                 setTimeout(() => {
                     isDownloading = false;
                     downloadButton.disabled = Object.keys(detectedVideoUrls).length === 0 && !lastDetectedUrl;
                     downloadButton.textContent = '下载失败,重试?';
                 }, 1500);
            },
            ontimeout: function() {
                alert('下载失败:请求超时');
                downloadButton.textContent = '请求超时';
                setTimeout(() => {
                     isDownloading = false;
                     downloadButton.disabled = Object.keys(detectedVideoUrls).length === 0 && !lastDetectedUrl;
                     downloadButton.textContent = '下载失败,重试?';
                 }, 1500);
            }
        });
    });

    function performanceCallback(list, observer) {
        // ... (与 V7.1 逻辑相同) ...
        const entries = list.getEntries();
        let newUrlsAdded = false;
        let latestUrlInBatch = null;

        for (const entry of entries) {
            if (entry.entryType === 'resource') {
                const url = entry.name;
                if (typeof url === 'string' && url.includes('douyinvod.com') && (url.includes('/video/') || url.endsWith('.mp4'))) {
                    const vid = getQueryParam(url, '__vid');
                    if (vid) {
                        detectedVideoUrls[vid] = url;
                        newUrlsAdded = true;
                    }
                    latestUrlInBatch = url;
                }
            }
        }

        if (latestUrlInBatch) {
            lastDetectedUrl = latestUrlInBatch;
        }

        if ((Object.keys(detectedVideoUrls).length > 0 || lastDetectedUrl) && downloadButton.disabled) {
            downloadButton.disabled = false;
            downloadButton.textContent = '下载视频';
        }
    }

    function startPerformanceObserver() {
        // ... (与 V7.1 启动逻辑相同) ...
        if (typeof PerformanceObserver === 'undefined') {
             alert("你的浏览器不支持 PerformanceObserver,脚本无法运行。");
             return;
        }
        if (observer) {
            observer.disconnect();
        }
        observer = new PerformanceObserver(performanceCallback);
        try {
             observer.observe({ type: 'resource', buffered: true });
        } catch (e) {
             try {
                 observer.observe({ entryTypes: ['resource'] });
             } catch (e2) {
                 alert("启动性能监视器失败,脚本可能无法工作。");
             }
        }
    }

})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址