抖音视频下载极简版

依旧是ai编的

// ==UserScript==
// @name         抖音视频下载极简版
// @namespace    http://tampermonkey.net/
// @version      0.8.0
// @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 = {}; // 存储 { __vid: url }
    let observer = null;
    let isDownloading = false;

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

    GM_addStyle(`
        #douyin-precise-nickname-downloader-btn {
            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-precise-nickname-downloader-btn:disabled {
            background-color: #cccccc;
            color: #666666;
            cursor: not-allowed;
            opacity: 0.7;
        }
        #douyin-precise-nickname-downloader-btn:hover:not(:disabled) {
            background-color: #FF8800;
        }
        #douyin-precise-nickname-downloader-btn: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}=([^&]*)`));
             try {
                 return match ? decodeURIComponent(match[1].replace(/\+/g, ' ')) : null;
             } catch (decodeError) {
                  return match ? match[1] : null;
             }
         }
    }

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

    function sanitizeFilename(name, defaultName = 'unknown') {
        if (!name) return defaultName;
        let cleaned = name.replace(/^@/, '').trim();
        cleaned = cleaned.replace(/[\\/:*?"<>|.\n\r\t]/g, '_');
        cleaned = cleaned.substring(0, 50);
        return cleaned || defaultName;
    }

    // --- 修改:获取特定视频 ID 的作者昵称 ---
    function getAuthorNickname(videoId) {
        if (!videoId) return '未知作者'; // 如果没有 videoId,无法精确查找
        try {
 
            const videoInfoContainer = document.querySelector(`[data-e2e="video-info"][data-e2e-aweme-id="${videoId}"]`);
            if (videoInfoContainer) {
 
                const nicknameElement = videoInfoContainer.querySelector('[data-e2e="feed-video-nickname"]');
                if (nicknameElement && nicknameElement.textContent) {
                    return sanitizeFilename(nicknameElement.textContent, '未知作者');
                }
            }
    
             const fallbackElement = document.querySelector('[data-e2e="feed-video-nickname"]');
             if(fallbackElement && fallbackElement.textContent) {
                 console.warn("[抖音下载] 未找到与视频ID匹配的昵称容器,使用了全局查找。")
                 return sanitizeFilename(fallbackElement.textContent, '未知作者');
             }

            return '未知作者';
        } catch (e) {
            return '未知作者';
        }
    }
   
    downloadButton.addEventListener('click', () => {
        let targetUrl = null;
        const pageId = getVideoIdFromPath(window.location.pathname) || getQueryParam(window.location.href, 'modal_id');
      
        if (pageId) {
            targetUrl = detectedVideoUrls[pageId]; // 如果 pageId == __vid,就能找到
        }

        if (!targetUrl) {
             const reason = pageId ? `页面ID (${pageId})` : '当前页面标识';
            alert(`未能根据 ${reason} 找到匹配的视频链接。\n请确保视频URL包含ID信息且已加载。`);
            return;
        }

        if (isDownloading) {
            return;
        }
   
        const authorNickname = getAuthorNickname(pageId);

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

        GM_xmlhttpRequest({
            method: "GET",
            url: targetUrl,
            responseType: 'blob',
            headers: { // 恢复 V7.4 的所有头
                "Referer": window.location.href,
                "User-Agent": navigator.userAgent,
                "Cookie": document.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",
                 "Sec-Fetch-Mode": "no-cors",
                 "Sec-Fetch-Dest": "video",
            },
            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 = pageId || 'unknown'; // 文件名使用 pageId
                         // !! 构建包含精确昵称的新文件名 !!
                         a.download = `douyin_${filenameVid}_${authorNickname}_${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;
                      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;
                     downloadButton.textContent = '下载失败,重试?';
                 }, 1500);
            },
            ontimeout: function() {
                alert('下载失败:请求超时');
                downloadButton.textContent = '请求超时';
                setTimeout(() => {
                     isDownloading = false;
                     downloadButton.disabled = Object.keys(detectedVideoUrls).length === 0;
                     downloadButton.textContent = '下载失败,重试?';
                 }, 1500);
            }
        });
    });
   
    function performanceCallback(list, observer) {
        const entries = list.getEntries();
        let newUrlsAddedToDict = false;

        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;
                        newUrlsAddedToDict = true;
                    }
                  
                }
            }
        }

        if (newUrlsAddedToDict && downloadButton.disabled) {
            downloadButton.disabled = false;
            downloadButton.textContent = '下载视频';
        }
    }

    // --- 启动 PerformanceObserver ---
    function startPerformanceObserver() {
        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或关注我们的公众号极客氢云获取最新地址