Greasy Fork

Greasy Fork is available in English.

小红书视频下载极简版

这是ai编的......

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         小红书视频下载极简版
// @namespace    http://tampermonkey.net/
// @version      1.0.12
// @description  这是ai编的......
// @match        *://www.xiaohongshu.com/*
// @grant        GM_download
// @grant        GM_addStyle
// @license MIT
// @require      https://cdnjs.cloudflare.com/ajax/libs/dayjs/1.11.9/dayjs.min.js
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    // 原版按钮样式(未改动)
    const btn = document.createElement('button');
    btn.id = 'xhs-video-downloader';
    GM_addStyle(`
        #xhs-video-downloader {
            position: fixed;
            bottom: 30px;
            right: 80px;
            z-index: 9999;
            padding: 10px 20px;
            background: #ff2442;
            color: white;
            border: none;
            border-radius: 20px;
            cursor: pointer;
            box-shadow: 0 4px 12px rgba(0,0,0,0.2);
            font-family: -apple-system, sans-serif;
        }

         #xhs-video-downloader:disabled {
             background-color: #cccccc;
             cursor: not-allowed;
             opacity: 0.7;
         }
    `);
    btn.textContent = '↓ 下载视频';
    // 初始时不显式设置禁用状态,由检测逻辑控制
    document.body.appendChild(btn);

    // =============== 增强视频检测 (保持原样) ===============
    let currentVideoUrl = null;
    let lastVideoCheck = 0;

    // =============== 增强作者获取 (保持原样) ===============
    const getDynamicAuthor = () => {
        try {
            return (
                document.querySelector('.author-info .name')?.textContent.trim() ||
                document.querySelector('[class*="username"]')?.textContent.trim() ||
                document.title.split('的小红书笔记')[0]
            ).replace(/[\\/:*?"<>|.\n\r]/g, '').substring(0, 20) || '未知作者';
        } catch(e) {
            return '未知作者';
        }
    };

    // =============== 新增:只获取 #detail-title 的内容 ===============
    const getSpecificTitle = () => {
        try {
            const titleElement = document.querySelector('#detail-title'); // 直接使用 ID 选择器
            if (titleElement) {
                const titleText = titleElement.textContent;
                const firstLine = titleText.split('\n')[0].trim();
                // 清理并截取前 20 字符
                const cleanTitle = firstLine.replace(/[\\/:*?"<>|.\n\r]/g, '').substring(0, 20);
                return cleanTitle; // 返回清理后的标题
            }
            return ''; // 找不到元素则返回空字符串
        } catch (e) {
            console.error("获取 #detail-title 出错:", e); // 输出错误到控制台
            return ''; // 出错也返回空字符串
        }
    };

    // =============== 点击事件(只修改文件名) ===============
    btn.addEventListener('click', () => {
        if(!currentVideoUrl) return alert('请先播放视频');
        // 保持原始逻辑,不添加额外的禁用检查

        const timestamp = dayjs().format('YYYYMMDD_HH-mm-ss');
        const author = getDynamicAuthor();
        const title = getSpecificTitle(); // <-- 调用新的、只获取指定ID的函数

        // 构建文件名
        let filename = `XHS_${timestamp}_${author}`;
        if (title) { // 只有标题非空时才添加
            filename += `_${title}`;
        }
        filename += '.mp4';

        console.log("生成文件名:", filename); // 保留原始确认日志

        // 恢复原始 GM_download
        GM_download({
            url: currentVideoUrl,
            name: filename,
            onerror: (e) => alert(`下载失败: ${e.error}`)
            // 无 onload, ontimeout
        });
    });

    const origOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function(method, url) {
        if(typeof url === 'string' && /sns-video.*\.mp4/.test(url)) {
             let fullUrl = url;
             try { if (!url.startsWith('http')) { fullUrl = new URL(url, window.location.href).href; } } catch {}
             if (fullUrl && fullUrl.startsWith('http') && fullUrl !== currentVideoUrl) {
                 currentVideoUrl = fullUrl;
                 btn.disabled = false; // 启用按钮
                 console.log('XHR捕获视频:', fullUrl);
             }
        }
        try {
            origOpen.apply(this, arguments);
        } catch(e) { /* 忽略原始调用错误 */ }
    };

})();