Sleazy Fork is available in English.

MissAv批量备份收藏视频

从当前missav页面获取图片文件和视频信息,并合并结果后提供下载生成的网页文件

// ==UserScript==
// @name        MissAv批量备份收藏视频
// @namespace    https://github.com/10086100886
// @version      1.2.0.0
// @description  从当前missav页面获取图片文件和视频信息,并合并结果后提供下载生成的网页文件
// @license MIT
// @author      人民的勤务员 <toniaiwanowskiskr47@gmail.com>  &  ChatGPT
// @match        https://missav.com/*



// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        GM_openInTab
// @icon         https://pic.616pic.com/ys_bnew_img/00/35/79/Gv93yQh7v6.jpg
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
// @require https://update.greasyfork.org/scripts/498124/1396763/video.js
// @require https://update.greasyfork.org/scripts/498149/1395619/%E4%BF%A1%E6%81%AF%E6%9F%A5%E7%9C%8B%E5%99%A8.js


// ==/UserScript==

(function() {
    'use strict';

var controlButton = createButton('备份', '10px', '10px');
    var buttonA = createButton('备份当前', '70px', '10px');
    var buttonB = createButton('备份片单', '130px', '10px');
    var buttonC = createButton('设置选项', '190px', '10px');
     var webdavbutton = createButton('WebDav', '250px', '10px');
    // 设置按钮的背景颜色和样式
    controlButton.style.backgroundColor = 'blue'; // 控制按钮改为蓝色背景
    buttonA.style.backgroundColor = 'green';
    buttonB.style.backgroundColor = 'blue';
    buttonC.style.backgroundColor = 'red';
    webdavbutton.style.backgroundColor = 'blue';
    // 隐藏初始的三个按钮
    buttonA.style.display = 'none';
    buttonB.style.display = 'none';
    buttonC.style.display = 'none';
     webdavbutton.style.display = 'none';
    // 添加按钮到页面
    document.body.appendChild(controlButton);
    document.body.appendChild(buttonA);
    document.body.appendChild(buttonB);
    document.body.appendChild(buttonC);
        document.body.appendChild(webdavbutton);
    // 控制按钮的点击事件
    controlButton.addEventListener('click', function() {
        if (buttonA.style.display === 'none') {
            // 显示三个按钮
            buttonA.style.display = 'block';
            buttonB.style.display = 'block';
            buttonC.style.display = 'block';
           webdavbutton.style.display = 'block';
            controlButton.innerHTML = '隐藏';
        } else {
            // 隐藏三个按钮
            buttonA.style.display = 'none';
            buttonB.style.display = 'none';
            buttonC.style.display = 'none';
           webdavbutton.style.display = 'none';
            controlButton.innerHTML = '备份';
        }
    });
      webdavbutton.addEventListener('click', function() {
          // 点击按钮时执行的操作
WebDAVManager.listFilesAndFolders(webdavfold);


    });

    // 按钮A的点击事件
    buttonA.addEventListener('click', function() {
           resetGlobalVariables();
     singleFileDownload =true;
      window.showLogContainer();
      var currentDate = new Date();
    var currentTime = currentDate.getFullYear() + '-' + (currentDate.getMonth() + 1) + '-' + currentDate.getDate() + '_' +currentDate.getHours() + '-' + currentDate.getMinutes() + '-' + currentDate.getSeconds();
if (useDefaultTitle) {
            name = document.querySelector('meta[name="twitter:title"]').content;
        } else {
            const twitterTitleContent = document.querySelector('meta[name="twitter:title"]').content;
            name = prompt("请输入自定义名称:", twitterTitleContent);
            if (name === null) {
                name = twitterTitleContent;
            }
        }
     inurl = window.location.href;
        const defaultPages = getTotalPagesd();

        const totalPages = setTotalPage(defaultPages);
        allpages=totalPages
        //const delay = settime();
        if (totalPages ) {
            start(totalPages);
        }

    });

    // 按钮B的点击事件
    buttonB.addEventListener('click', function() {
          // 点击按钮时执行的操作
  resetGlobalVariables();
 fetchJsonData();


    });

    // 按钮C的点击事件
    buttonC.addEventListener('click', function() {
         createSettingsUI();

        // 这里可以添加按钮C点击后的具体操作,比如打开链接或执行其他动作
    });

    // 创建按钮的辅助函数
    function createButton(text, top, left) {
        var button = document.createElement('button');
        button.innerHTML = text;
        button.style.position = 'fixed';
        button.style.bottom = top;
        button.style.right = left;
        button.style.zIndex = '1000';
        button.style.padding = '10px';
        button.style.border = 'none';
        button.style.cursor = 'pointer';
        button.style.color = '#fff';
        button.style.fontSize = '14px';
        button.style.fontWeight = 'bold';
        button.style.textAlign = 'center';
        button.style.width = '100px'; // 调整按钮宽度
        return button;
    }
// 全局变量
var allResults = []; // 存储所有的结果数据
var zip = new JSZip(); // 创建一个压缩文件实例
var allzip = new JSZip(); // 另一个可能的压缩文件实例
var imgFolder = zip.folder("img"); // 在 zip 中创建一个名为 "img" 的文件夹,用于存储图片文件
var allimgFolder = allzip.folder("img"); // 在 allzip 中创建一个名为 "img" 的文件夹,可能用于另一个压缩文件的图片存储
var ALLfiledown = false; // 标识是否所有文件已下载完毕的布尔变量
var videos = []; // 存储视频文件或相关信息的数组
var finalData = []; // 存储最终处理数据的数组
var inurl = ""; // 当前下载地址的变量
var pendingRequests = 0; // 当前待处理的请求数量
var delayTime; // 延迟时间,以毫秒为单位,用于控制异步操作的时间间隔
var currentPage = 1; // 当前处理的页数,可能用于分页处理或其他进度跟踪
var currentUrlIndex = 0; // 当前处理的 urls 数组中的索引位置
var name = ""; // 当前下载的名称
var urls = []; // 存储需要处理的网址数组
var a = -1; // 循环中的计数或索引,初始值为 -1
var allZipContents = []; // 存储所有压缩文件内容的数组
var singleFileDownload = false; // 标识是否为单个文件下载模式的布尔变量
var names = []; // 存储下载名称列表的数组
var allpages = 0; // 存储总页数或其他页面处理相关信息的变量
var modalContainer = null; // 存储模态窗口容器的全局变量,用于显示下载进度或其他信息
var shouldReplace = false; // 控制是否在下载大图时进行替换操作的布尔变量
var temporaryData = [];
var saveJson= false ;
var useDefaultTitle= true;
var pageCount= true;
var saveVideoInfo= false;
var   saveImage= false;
  var downloadLog = {};
  var   errorLogs = {};
var  downloadLogFileA = false; // 这里设置为 true 时载日志
var   webdavfold="missavsave";
//var   webdavfold="1111";
  var   savetowebdav= false;
   var webdavUrl =''
    var webdavUsername =''
    var webdavPassword =''
     var deleteSelected=false;
 ini();//读取配置
function resetGlobalVariables() {
    zip = new JSZip(); // 重置为一个新的 JSZip 实例,用于创建新的压缩文件
    allzip = new JSZip(); // 可能是另一个新的 JSZip 实例,用于其他用途的压缩文件
   if (saveImage) {
      imgFolder = zip.folder("img"); // 在 zip 中创建一个名为 "img" 的文件夹,用于存储图片文件
    allimgFolder = allzip.folder("img"); // 在 allzip 中创建一个名为 "img" 的文件夹,可能用于另一个压缩文件的图片存储
   }
    downloadLog = {};
   errorLogs = {};
    ALLfiledown = false; // 重置为 false,表示所有文件未下载完毕
    videos = []; // 清空存储视频文件或相关信息的数组
    finalData = []; // 清空存储最终处理数据的数组
    inurl = ""; // 重置当前下载地址为空字符串
    pendingRequests = 0; // 重置待处理的请求数量为 0

    currentPage = 1; // 重置当前处理的页数为 1
    currentUrlIndex = 0; // 重置当前处理的 urls 数组索引为 0
    name = ""; // 重置当前下载的名称为空字符串
    urls = []; // 清空存储需要处理的网址数组
    a = -1; // 重置循环中的计数或索引为 -1
    allZipContents = []; // 清空存储所有压缩文件内容的数组
    singleFileDownload = false; // 重置为 false,表示不是单个文件下载模式
    names = []; // 清空存储下载名称列表的数组
    allpages = 0; // 重置存储总页数或其他页面处理相关信息的变量为 0
    temporaryData = [];
}


async function processUrls() {
    //delayTime = 20;
    let completedTasks = 0; // 计数已完成的任务数量

    for (const url of urls) {
        a = a + 1; // 每次循环递增 a
        inurl = url;
        console.log("正在处理网址:", url, names[a]);
       window.addToLog("处理:"+url+names[a], 'info');
        name = names[a];

        try {
            const totalPages = await getTotalPages(url); // 等待 getTotalPages 返回结果
            console.log("Total pages for", url, ":", totalPages); // 显示总页数
            window.addToLog(name+" 总页数:"+url+ totalPages, 'info');
            allpages = totalPages;
            start(totalPages); // 启动处理流程

            // 等待当前页面的请求完成
            while (pendingRequests > 0) {
                await new Promise(resolve => setTimeout(resolve, 100)); // 每隔 100 毫秒检查一次是否所有请求都已完成
            }

            completedTasks++; // 标记当前任务已完成
        } catch (error) {
            console.error("Error processing URL:", url, error); // 处理错误信息
            allpages = 1;
            start(1); // 启动处理流程
          while (pendingRequests > 0) {
                await new Promise(resolve => setTimeout(resolve, 100)); // 每隔 100 毫秒检查一次是否所有请求都已完成
            }
            completedTasks++; // 标记当前任务已完成
        }
    }

    // 如果所有任务都已完成且 urls 数组不为空,则调用下载函数
    if (completedTasks === urls.length && urls.length !== 0) {
        downloadAllZips();
    }
}




function getAllCookies() {
        return document.cookie;
    }

    // 获取指定 JSON 数据的函数




    function fetchJsonData() {
        const cookies = getAllCookies();
          //alert(cookies);
        console.log('Current page cookies:', cookies);

        // 构建 API URL
        const apiUrl = 'https://missav.com/api/playlists/dfe-057';

        // 发送带有 cookies 的请求
        GM_xmlhttpRequest({
            method: 'GET',
            url: apiUrl,
            headers: {
                'Cookie': cookies
            },
            onload: function(response) {
                if (response.status === 200) {
                    try {
                        const jsonResponse = JSON.parse(response.responseText);
                        if (jsonResponse && Array.isArray(jsonResponse.data)) {
                           createReportUI(jsonResponse.data, 500); // 假设每页显示 10 个项目

                         // 调用 processUrls 函数处理 URLs
                        } else {
                            console.error('JSON 格式无效');
                          showModal("JSON 格式无效",2000);
                        }
                    } catch (error) {
                        console.error('Error parsing JSON:', error);
                       showModal("解析错误"+error,2000);
                    }
                } else {
                    console.error('Request failed with status:', response.status);
                   showModal("解析错误",2000);
                }
            },
            onerror: function(error) {
                showModal("解析错误"+error,2000);
            }
        });
    }

  function processUrl(url) {
    // 检查是否包含 `page=` 参数
    var pageIndex = url.indexOf('page=');

    if (pageIndex !== -1) {
        // 找到 `page=` 参数并删除它及其后的所有内容
        var baseUrl = url.substring(0, pageIndex + 5); // +5 to include `page=`
        return baseUrl;
    } else {
        // 检查是否已有其他参数
        if (url.includes('?')) {
            // 有其他参数,添加 `&page=`
            return url + '&page=';
        } else {
            // 没有其他参数,添加 `?page=`
            return url + '?page=';
        }
    }
}
  function settime() {
    // 让用户输入延时时间
   delayTime = prompt("请输入每页请求的延时时间(毫秒):", "1000");

    // 检查用户是否取消输入
    if (delayTime === null) {
        alert("输入取消");
        return;
    }

    delayTime = parseInt(delayTime);

    // 检查输入的延时时间是否有效
    if (isNaN(delayTime) || delayTime <= 0) {
        alert("请输入有效的延时时间(正整数)!");
        return;
    }

    // 返回有效的延时时间
    return delayTime;
}


   function getTotalPagesd() { // 获取总页数
    var totalPagesElement = document.querySelector('#price-currency');
    var totalPagesText = totalPagesElement ? totalPagesElement.innerText : '';
    var totalPages = parseInt(totalPagesText.replace('/', '').trim(), 10);

    // 如果获取总页数失败,则返回 1
    if (isNaN(totalPages) || totalPages <= 0) {
        totalPages = 1;
    }

    return totalPages;
}
  function getTotalPages(url) {
    return new Promise((resolve, reject) => {
        // 如果没有提供 URL,则使用当前页面的 URL
        if (!url) {
            url = window.location.href;
        }

        // 发起 GM_xmlhttpRequest 请求获取页面内容
        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            headers: { "Cookie": document.cookie },
            onload: function(response) {
                // 处理响应
                if (response.status === 200) {
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(response.responseText, 'text/html');
                    const totalPagesElement = doc.querySelector('#price-currency'); // 替换为实际选择器
                    if (totalPagesElement) {
                        const totalPagesText = totalPagesElement.innerText;
                        const totalPages = parseInt(totalPagesText.replace('/', '').trim(), 10);
                        resolve(totalPages); // 成功时返回总页数
                    } else {
                        window.addToLog('页面中没有找到总页数,默认为1页', 'warning');
                      reject('Total pages element not found'); // 页面中没有找到总页数元素
                    }
                } else {
                    window.addToLog('请求失败', 'warning');
                  reject(`Request failed with status ${response.status}`); // 请求失败
                }
            },
            onerror: function() {
                window.addToLog('请求出错', 'warning');
              reject('Request failed'); // 请求出错

            }
        });
    });
}


 // 设置总页数
function setTotalPage(defaultPages) {
    if (!pageCount) {
        return defaultPages;
    }

    const inputPages = parseInt(prompt(`当前 ${name} 总页数为 ${defaultPages}。请输入你想要抓取的页数(不输入抓取全部):`, defaultPages), 10);
    if (isNaN(inputPages) || inputPages <= 0) {
        return defaultPages;
    }
    return inputPages;
}
   // 开始处理页面抓取
    function start(totalPages, callback) {
    const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
    pendingRequests = pages.length;
    fetchPage(pages.shift(), pages, callback);
}

   // 点击按钮时执行操作



 // 异步获取页面内容
   function fetchPage(pageNum, pages, callback) {
    const pageUrl = `${processUrl(inurl)}${pageNum}`;
    console.log(`正在获取第 ${pageNum} 页的内容...`);
    //showModal(`正在获取${name} 第 ${pageNum} / ${allpages}页 `);
if (a !== -1) {
    showModal(`${a + 1}/${names.length} 正在获取 ${name} 第 ${pageNum} / ${allpages} 页`);
} else {
    showModal(`正在获取 ${name} 第 ${pageNum} / ${allpages} 页`);
}
    GM_xmlhttpRequest({
        method: "GET",
        url: pageUrl,
        headers: { "Cookie": document.cookie },
        onload: function(response) {
            if (response.status === 200) {
                processPageContent(response.responseText, pageNum, pages, callback);
            } else {
                pendingRequests--;
                checkIfComplete(callback);
                if (pages.length > 0) {
                    setTimeout(() => fetchPage(pages.shift(), pages, callback), delayTime);
                }
            }
        }
    });
}


  //获取视频信息
function extractInformation(htmlContent) {
    let data = {}; // 创建一个对象来存储提取的数据
    let xhr = new XMLHttpRequest();
    xhr.open('GET', htmlContent, false); // 同步方式打开请求
    xhr.send();

    // 创建一个虚拟的 <div> 元素来加载 HTML 内容
    let tempDiv = document.createElement('div');
    tempDiv.innerHTML = xhr.responseText;

    // 获取所有包含信息的父元素列表
    let parentElements = tempDiv.querySelectorAll('div.space-y-2 > div');

    if (parentElements.length > 0) {
        let allInfo = {}; // 初始化一个空对象来存储所有信息

        // 遍历每个包含信息的 <div> 元素
        parentElements.forEach(div => {
            let span = div.querySelector('span'); // 获取第一个 <span> 元素

            if (span) {
                let category = span.textContent.trim(); // 获取主分类名称

                if (!allInfo[category]) {
                    allInfo[category] = []; // 初始化一个空数组来存储该分类下的所有信息
                }

                // 查找所有的 <a> 元素和 <time> 元素
                div.querySelectorAll('a, time').forEach(element => {
                    let info = {};

                    // 判断元素是否是 <a> 元素
                    if (element.tagName === 'A') {
                        info['name'] = element.textContent.trim(); // 获取名称
                        info['link'] = element.href.trim(); // 获取链接
                    } else if (element.tagName === 'TIME') {
                        info = element.textContent.trim(); // 如果是 <time> 元素,则直接保存其文本内容
                    }

                    // 添加信息对象到相应的主分类数组中
                    allInfo[category].push(info);
                });

                // 如果没有找到 <a> 元素,则尝试获取 <span> 标签内的文本内容
                if (div.querySelectorAll('a').length === 0) {
                    let spanText = div.querySelector('span.font-medium');
                    if (spanText) {
                        let info = spanText.textContent.trim();
                        allInfo[category].push(info);
                    }
                }
            }
        });

        // 提取 class="mb-1 text-secondary break-all line-clamp-2" 的内容
        let descriptionElement = tempDiv.querySelector('.mb-1.text-secondary.break-all.line-clamp-2');
        let descriptionContent = descriptionElement ? descriptionElement.textContent.trim() : '';
        allInfo['简介'] = descriptionContent;

        // 将所有信息存储到 data 对象中
        data['videosinfo'] = allInfo;

        // 查找包含 x-cloak 和 x-show="currentTab === 'magnets'" 的第二个元素
        let secondElement = tempDiv.querySelector('div[x-cloak][x-show="currentTab === \'magnets\'"]');

        if (secondElement) {
            let linksAndInfo = [];

            // 遍历第二个元素内的 <a> 元素
            secondElement.querySelectorAll('a[rel="nofollow"]').forEach(a => {
                let linkInfo = {
                    name: a.textContent.trim(),
                    link: a.href.trim()
                };

                // 查找相邻的 <td> 元素,获取大小和日期信息
                let sizeTd = a.closest('td').nextElementSibling;
                if (sizeTd && sizeTd.classList.contains('font-mono')) {
                    linkInfo['size'] = sizeTd.textContent.trim(); // 获取大小信息
                }

                let dateTd = sizeTd ? sizeTd.nextElementSibling : null;
                if (dateTd && dateTd.classList.contains('hidden')) {
                    linkInfo['date'] = dateTd.textContent.trim(); // 获取日期信息
                }
  let nextSibling = a.nextElementSibling;

    // 循环处理所有紧邻的<span>元素
    while (nextSibling && nextSibling.tagName === 'SPAN') {
        let spanText = nextSibling.textContent.trim();
       linkInfo['name'] += ' ' + spanText // 将<span>元素的文本内容追加到name中

        nextSibling = nextSibling.nextElementSibling; // 继续查找下一个兄弟元素
    }
                linksAndInfo.push(linkInfo);
            });

            // 将第二个元素的链接和信息添加到 data 中
            data['secondElementLinksInfo'] = linksAndInfo;
        } else {
            console.error('未找到包含 x-cloak 和 x-show="currentTab === \'magnets\'" 的第二个元素。');
        }

        return data; // 返回结构化的数据

    } else {
        console.error('未找到匹配的父元素 div.space-y-2');
        return null; // 如果未找到匹配的父元素,返回 null
    }
}  ///大




    // 使用XMLHttpRequest获取页面内容
    function fetchPageforinfo(url) {
        let xhr = new XMLHttpRequest();

        xhr.onreadystatechange = function() {
            if (xhr.readyState === XMLHttpRequest.DONE) {
                if (xhr.status === 200) {
                    extractInformation(xhr.responseText); // 将获取的页面内容传递给提取信息的函数
                } else {
                    console.error('请求失败:' + xhr.status);
                }
            }
        };

        xhr.open('GET', url, true);
        xhr.send();
    }


    // 处理获取到的页面内容
function processPageContent(htmlContent, pageNum, pages, callback) {
    const parser = new DOMParser();
    const doc = parser.parseFromString(htmlContent, 'text/html');
    const divElements = doc.querySelectorAll('div.relative.aspect-w-16.aspect-h-9.rounded.overflow-hidden.shadow-lg');
   const logEntry = {
        url: `${processUrl(inurl)}${pageNum}`,
        elementsFetched: divElements.length
    };

    // 如果当前名称的日志组不存在,则创建一个新数组
    if (!downloadLog[name]) {
        downloadLog[name] = [];
    }

    // 将日志条目添加到日志数组中
    downloadLog[name].push(logEntry);
    if (divElements.length === 0) {
        const logEntry = {
            url: `${processUrl(inurl)}${pageNum}`,
            elementsFetched: 0, // 这里可以根据实际需求设置其他信息
            errorMessage: `获取第 ${pageNum} 页失败。`
        };
       if (!errorLogs[name]) {
        errorLogs[name] = [];
    }
        errorLogs[name].push(logEntry);
      console.log(`获取第 ${pageNum} 页失败。`);
        window.addToLog(`${name}${processUrl(inurl)}${pageNum}+获取失败 数量:`+divElements.length, 'error');
    }




    divElements.forEach(div => {
        var imgUrl = div.querySelector('img').getAttribute('data-src');


if (shouldReplace) {
  imgUrl = imgUrl.replace('cover-t.jpg', 'cover-n.jpg');
}
        const video = {
            fileName: div.querySelector('a').getAttribute('alt'),
            imgUrl: imgUrl,
            videoUrl: div.querySelector('video').getAttribute('data-src'),
            markContent: Array.from(div.querySelectorAll('span')).map(mark => mark.textContent).join(' '),
            altText: div.querySelector('img').getAttribute('alt'),
            jumpUrl: div.querySelector('a').getAttribute('href'),

        };

 if(saveVideoInfo){
          video.info=extractInformation(video.jumpUrl);
   //showBanner(`正在获取 ${video.fileName} 信息`);
    window.addToLog(`正在获取 ${video.fileName} 信息`, 'info');
   console.log()
        };



        if (video.imgUrl && video.altText) {
            videos.push(video);
             if (saveImage) {
               window.addToLog(`保存`+video.imgUrl, 'info');
            pendingRequests++;
            GM_xmlhttpRequest({
                method: 'GET',
                url: video.imgUrl,
                responseType: 'blob',
                onload: function(response) {
                    if (response.status === 200) {
                      if (saveImage) {
    if (singleFileDownload) {
        console.log("这是单个文件下载");
        imgFolder.file(`${video.fileName}.jpg`, response.response, { binary: true });
    } else {
        console.log("这是批量文件下载");
        allimgFolder.file(`${video.fileName}.jpg`, response.response, { binary: true });
    }
}

                        pendingRequests--;
                        checkIfComplete(callback);
                    } else {
                        pendingRequests--;
                        checkIfComplete(callback);
                    }
                }
            });
        }
          }else {
               pendingRequests--;
                        checkIfComplete(callback);
          }
    });

 showModal(`获取第 ${pageNum} 页的内容完成,等待 ${delayTime} 毫秒加载第 ${pageNum + 1} 页。`);
    pendingRequests--;
    checkIfComplete(callback);
    if (pages.length > 0) {
        setTimeout(() => fetchPage(pages.shift(), pages, callback), delayTime);
    } else {

    }
}

  closeModal();

function downloadLogFile() {


    if (!downloadLogFileA) {
        console.log('日志下载已被跳过');
        return;
    }
  if (Object.keys(errorLogs).length === 0) {
        // 如果错误日志为空,直接下载正常日志文件
        const logBlob = new Blob([JSON.stringify(downloadLog, null, 4)], { type: 'application/json' });
        const logUrl = URL.createObjectURL(logBlob);
        const logLink = document.createElement('a');
        logLink.href = logUrl;
        logLink.download = 'download_log.json';
        logLink.click();
        URL.revokeObjectURL(logUrl);
    } else {
        // 创建一个JSZip实例
        const zip = new JSZip();

        // 添加正常日志文件到压缩包
        const logBlob = new Blob([JSON.stringify(downloadLog, null, 4)], { type: 'application/json' });
        zip.file('download_log.json', logBlob);

        // 添加错误日志文件到压缩包
        const errorLogBlob = new Blob([JSON.stringify(errorLogs, null, 4)], { type: 'application/json' });
        zip.file('error_log.json', errorLogBlob);

        // 生成压缩包并触发下载
        zip.generateAsync({ type: 'blob' }).then(function(content) {
            const zipUrl = URL.createObjectURL(content);
            const link = document.createElement('a');
            link.href = zipUrl;
            link.download = 'logs.zip';
            link.click();
            URL.revokeObjectURL(zipUrl);
        });
    }
}



function sanitizeFileName(name) {
    return name.replace(/[\\\/:*?"<>|]/g, '_');
}

function checkIfComplete(callback) {
    if (pendingRequests === 0) {

      const additionalInfo = {
    timestamp: new Date().toISOString(),
    inurl: inurl

};
        if (singleFileDownload) {
            showModal("获取完毕,正在生成单个文件...");

      finalData = {
    info: additionalInfo,
    video: videos
};
if (saveJson) {
          zip.file("data.json", JSON.stringify(finalData, null, 4));
}
 if (savetowebdav){

    WebDAVManager.uploadFile(webdavfold, `${sanitizeFileName(name)}.json`, JSON.stringify(finalData, null, 4));
   }



            const jsonIndexContent = generateJsonIndexContent(finalData);
            const numFiles = Object.keys(zip.files).length; // 获取压缩包中文件的数量

         if (numFiles === 0) {
                    const htmlContent = jsonIndexContent; // 替换为实际的HTML内容
                    const htmlBlob = new Blob([htmlContent], { type: 'text/html' });
                    const htmlUrl = URL.createObjectURL(htmlBlob);

                    const a = document.createElement('a');
                    a.href = htmlUrl;
                    a.download = `${sanitizeFileName(name)}.html`;
                    a.click();
           closeModal();
           downloadLogFile();

                if (callback) callback();
         }else{
                      zip.file(`${sanitizeFileName(name)}.html`, jsonIndexContent);

    // 生成并下载单个文件
            zip.generateAsync({ type: "blob" }, function updateCallback(metadata) {
                const progress = metadata.percent.toFixed(2);
                showModal(`压缩进度: ${progress}%`);
            }).then(content => {
                const zipUrl = URL.createObjectURL(content);
                const a = document.createElement('a');
                a.href = zipUrl;
                a.download = `${name}.zip`;
                a.click();
                URL.revokeObjectURL(zipUrl);
                closeModal();
               downloadLogFile();
                if (callback) callback();
            });
                    }


        } else {
                finalData = {
    info: additionalInfo,
    video: videos
};
            if (saveJson) {
          allzip.file(`${sanitizeFileName(name)}.json`, JSON.stringify(finalData, null, 4));
            }
          if (savetowebdav){
    WebDAVManager.uploadFile(webdavfold, `${sanitizeFileName(name)}.json`, JSON.stringify(finalData, null, 4));
   }

            const jsonIndexContent =  generateJsonIndexContent(finalData);;
            allzip.file(`${sanitizeFileName(name)}.html`, jsonIndexContent);
                  finalData = [];

                    videos= [];
            if (callback) callback();
        }

    }
}
function downloadAllZips() {
if (singleFileDownload === false) {
   showModal("获取完毕,正在生成压缩文件...");

    const numFiles = Object.keys(allzip.files).length; // 获取压缩包中文件的数量

         if (numFiles === 1) {
            // 如果压缩包中只有一个文件,直接处理该文件
            const fileName = Object.keys(allzip.files)[0]; // 获取唯一的文件名
            const file = allzip.files[fileName];

            // 根据文件类型获取文件内容
            file.async('blob').then(content => {
                // 创建一个Blob对象,并下载
                const blob = new Blob([content]);
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = fileName;
                document.body.appendChild(a); // 添加到文档中以确保点击有效
                a.click();
                document.body.removeChild(a); // 下载完成后移除元素
                URL.revokeObjectURL(url);
                closeModal();
            }).catch(error => {
                console.error('Error fetching file content:', error);
                closeModal();
            });
  downloadLogFile();
            return; // 结束函数执行,不生成压缩包
        }



    allzip.generateAsync({ type: "blob" }, function updateCallback(metadata) {
                const progress = metadata.percent.toFixed(2);
                showModal(`压缩进度: ${progress}%`);
            }).then(content => {
                const zipUrl = URL.createObjectURL(content);
                const a = document.createElement('a');
                a.href = zipUrl;
                a.download = `批量备份${urls.length}个片单.zip`;
                a.click();
                URL.revokeObjectURL(zipUrl);
                closeModal();
        downloadLogFile();
                if (callback) callback();
            });
    // 如果 singleFileDownload 等于假,则执行这里的代码
}


}

function showBanner(text) {
    // 查找现有的横幅元素
    var existingBanner = document.querySelector('.banner');

    if (existingBanner) {
        // 如果横幅已经存在,直接更新文本内容
        existingBanner.textContent = text;
    } else {
        // 如果横幅不存在,创建一个新的横幅
        var banner = document.createElement('div');
        banner.className = 'banner'; // 添加一个类名以便识别
        banner.style.position = 'fixed';
        banner.style.bottom = '20px'; // 距离底部的距离
        banner.style.left = '20px'; // 距离左侧的距离
        banner.style.width = 'auto'; // 根据文本自动调整宽度
        banner.style.backgroundColor = 'rgba(255, 255, 255, 0.9)';
        banner.style.color = '#000'; // 黑色文本
        banner.style.textAlign = 'center';
        banner.style.padding = '20px';
        banner.style.borderRadius = '8px';
        banner.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)';
        banner.style.zIndex = '9999';
        banner.textContent = text; // 将传入的文本设置为横幅内容

        document.body.appendChild(banner); // 将横幅添加到文档的末尾

        // 3秒后移除横幅提示
        setTimeout(function() {
            banner.remove();
        }, 3000);
    }
}



// 创建或更新模态窗口
function showModal(message, autoCloseDelay = 0) {
    // 如果模态窗口不存在,则创建新的模态窗口
    if (!modalContainer) {
        modalContainer = document.createElement('div');
        modalContainer.className = 'modal-container';
        modalContainer.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background-color: rgba(255, 255, 255, 0.9);
            border-radius: 8px;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
            z-index: 9999;
            padding: 20px;
        `;
        document.body.appendChild(modalContainer);
    }

    // 更新模态窗口的内容
    modalContainer.textContent = message;

    // 自动关闭模态窗口
    if (autoCloseDelay > 0) {
        setTimeout(closeModal, autoCloseDelay);
    }
}

// 关闭模态窗口
function closeModal() {
    // 如果模态窗口存在,则从 DOM 中移除
    if (modalContainer) {
        document.body.removeChild(modalContainer);
        modalContainer = null; // 将变量重置为 null,以便下次创建新的模态窗口
    }
}

// 创建JSONindex

function createReportUI(data, itemsPerPage) {
  temporaryData = data;
 // 创建全屏遮罩层
    const overlay = document.createElement('div');
    overlay.className = 'overlay';
    overlay.style.cssText = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100%;
        height: 100%;
        background-color: rgba(0, 0, 0, 1); /* 全黑不透明背景 */
        z-index: 9999; /* 确保遮罩层位于所有内容之上 */
    `;
   // document.body.appendChild(overlay);

    const modalContainer = document.createElement('div');
    modalContainer.className = 'modal-container';
    modalContainer.style.cssText = `
        position: fixed;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%);
        background-color: rgba(255, 255, 255, 1);
        border-radius: 8px;
        box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
        z-index: 10000; /* 确保弹出框位于遮罩层之上 */
        padding: 20px;
        width: 80%;
        max-width: 800px;
    `;
    const title = document.createElement('h2');
    title.textContent =`当前共有片单数量: ${temporaryData.length}`;

    title.style.textAlign = 'center';
    modalContainer.appendChild(title);

    const closeButton = document.createElement('button');
    closeButton.textContent = '×';
    closeButton.style.position = 'absolute';
    closeButton.style.top = '10px';
    closeButton.style.right = '10px';
    closeButton.style.backgroundColor = 'transparent';
    closeButton.style.border = 'none';
    closeButton.style.fontSize = '24px';
    closeButton.style.cursor = 'pointer';
    modalContainer.appendChild(closeButton);

    closeButton.addEventListener('click', () => {

    //   document.body.removeChild(overlay); // 移除遮罩层
        document.body.removeChild(modalContainer); // 移除模态框
    });

    const tableContainer = document.createElement('div');
    tableContainer.style.cssText = `
        max-height: 60vh;
        overflow-y: auto;
    `;
    modalContainer.appendChild(tableContainer);

    const table = document.createElement('table');
    table.style.width = '100%';
    table.style.borderCollapse = 'collapse';
    table.style.fontSize = '16px';
    tableContainer.appendChild(table);

    const thead = document.createElement('thead');
    table.appendChild(thead);

    const headerRow = document.createElement('tr');
    thead.appendChild(headerRow);

    const checkboxHeader = document.createElement('th');
    checkboxHeader.textContent = '选择';
    checkboxHeader.style.textAlign = 'center';
    checkboxHeader.style.padding = '10px';
    headerRow.appendChild(checkboxHeader);

    const nameHeader = document.createElement('th');
    nameHeader.textContent = '片单';
    nameHeader.style.padding = '10px';
    nameHeader.style.width = '40%';
    headerRow.appendChild(nameHeader);

    const urlHeader = document.createElement('th');
    urlHeader.textContent = '地址';
    urlHeader.style.padding = '10px';
    urlHeader.style.width = '40%';
    headerRow.appendChild(urlHeader);

    const tbody = document.createElement('tbody');
    table.appendChild(tbody);

    let currentPage = 1;
    const totalItems = data.length;
    const totalPages = Math.ceil(totalItems / itemsPerPage);

    function generateTableData(page) {
        tbody.innerHTML = '';

        const startIndex = (page - 1) * itemsPerPage;
        const endIndex = startIndex + itemsPerPage;

        for (let i = startIndex; i < endIndex && i < data.length; i++) {
            const row = document.createElement('tr');
            tbody.appendChild(row);
    // 序号列
        const indexCell = document.createElement('td');
        indexCell.textContent = i + 1; // 显示序号,从1开始
        indexCell.style.textAlign = 'center';
        indexCell.style.padding = '5px';
        row.appendChild(indexCell);
            const checkboxCell = document.createElement('td');
            checkboxCell.style.textAlign = 'center';
            checkboxCell.style.padding = '5px';
            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.id = `checkbox_${i}`;
            checkbox.value = i;
            checkboxCell.appendChild(checkbox);
            row.appendChild(checkboxCell);

            const nameCell = document.createElement('td');
            nameCell.textContent = data[i].name;
            nameCell.style.padding = '10px';
            nameCell.style.borderBottom = '1px solid #ddd';
            row.appendChild(nameCell);

const urlCell = document.createElement('td');
const fullUrl = "https://missav.com/playlists/" + data[i].key;
const link = document.createElement('a');
link.textContent = fullUrl;
link.href = fullUrl;
link.target = '_blank'; // 在新标签页中打开链接
urlCell.appendChild(link);
urlCell.style.padding = '10px';
urlCell.style.borderBottom = '1px solid #ddd';
row.appendChild(urlCell);
        }
    }

    generateTableData(currentPage);

    const paginationContainer = document.createElement('div');
    paginationContainer.style.marginTop = '20px';
    paginationContainer.style.textAlign = 'center';
    modalContainer.appendChild(paginationContainer);

    const prevButton = document.createElement('button');
    prevButton.textContent = '上一页';
    prevButton.style.marginRight = '10px';
    prevButton.disabled = true;

    const pageIndicator = document.createElement('span');
    pageIndicator.style.marginRight = '10px';
    updatePageIndicator();
    paginationContainer.appendChild(pageIndicator);

    const nextButton = document.createElement('button');
    nextButton.textContent = '下一页';
    nextButton.style.marginLeft = '10px';
    if (totalPages <= 1) {
        nextButton.disabled = true;
    }

    prevButton.addEventListener('click', () => {
        currentPage--;
        generateTableData(currentPage);
        updatePaginationButtons();
        updatePageIndicator();
    });

    nextButton.addEventListener('click', () => {
        currentPage++;
        generateTableData(currentPage);
        updatePaginationButtons();
        updatePageIndicator();
    });

    function updatePaginationButtons() {
        prevButton.disabled = currentPage === 1;
        nextButton.disabled = currentPage === totalPages;
    }

    function updatePageIndicator() {
        pageIndicator.textContent = `第 ${currentPage}/${totalPages} 页`;
    }
 const selectAllButton = document.createElement('button');
selectAllButton.textContent = '全部选择';
selectAllButton.style.marginRight = '10px';
selectAllButton.style.marginTop = '20px';
selectAllButton.style.padding = '10px 20px';
selectAllButton.style.fontSize = '16px';
selectAllButton.style.backgroundColor = '#007bff';
selectAllButton.style.color = '#fff';
selectAllButton.style.border = 'none';
selectAllButton.style.borderRadius = '5px';
selectAllButton.style.cursor = 'pointer';
selectAllButton.style.float = 'left';
    modalContainer.appendChild(selectAllButton);

let selectAll = true;

selectAllButton.addEventListener('click', () => {
    const checkboxes = document.querySelectorAll('input[type="checkbox"]');
    checkboxes.forEach(checkbox => {
        checkbox.checked = selectAll;
    });

    if (selectAll) {
        selectAllButton.textContent = '取消选择';
    } else {
        selectAllButton.textContent = '全部选择';
    }

    selectAll = !selectAll;
});
//

    const confirmButton = document.createElement('button');
    confirmButton.textContent = '确认选择';
    confirmButton.style.marginTop = '20px';
    confirmButton.style.padding = '10px 20px';
    confirmButton.style.fontSize = '16px';
    confirmButton.style.backgroundColor = '#007bff';
    confirmButton.style.color = '#fff';
    confirmButton.style.border = 'none';
    confirmButton.style.borderRadius = '5px';
    confirmButton.style.cursor = 'pointer';
    confirmButton.style.float = 'right';
    modalContainer.appendChild(confirmButton);

    document.body.appendChild(modalContainer);

confirmButton.addEventListener('click', () => {
    const checkboxes = document.querySelectorAll('input[type="checkbox"]');
      let anyCheckboxChecked = false;
    checkboxes.forEach(checkbox => {
        if (checkbox.checked) {

            const index = parseInt(checkbox.value, 10);
            if (index >= 0 && index < temporaryData.length) {
                // 检查 temporaryData[index] 是否为 undefined 或 null
                if (temporaryData[index]) {
                    // 将选中的名称和URL推送到全局变量
                    names.push(temporaryData[index].name);
                    urls.push("https://missav.com/playlists/"+temporaryData[index].key);
                  anyCheckboxChecked = true;
                } else {
                    console.error(`temporaryData[${index}] is undefined or null.`);
                }
            } else {
                console.error(`Index ${index} is out of bounds for temporaryData.`);
            }
        }

    });

 document.body.removeChild(modalContainer);
//  document.body.removeChild(overlay);
       if (anyCheckboxChecked) {

        processUrls();
         window.showLogContainer();
    }

});
}
function ini() {
  delayTime= GM_getValue('delayTime', 1000); // 从GM存储中读取延迟时间
   shouldReplace=GM_getValue('shouldReplace', false); // 从GM存储中读取状态
  saveJson=GM_getValue('saveJson', false); // 从GM存储中读取状态
 useDefaultTitle = GM_getValue('useDefaultTitle', true);
  pageCount= GM_getValue('pageCount', true);
saveVideoInfo= GM_getValue('saveVideoInfo', false);
  saveImage= GM_getValue('saveImage', true);
downloadLogFileA= GM_getValue('downloadLogFileA', false);
  savetowebdav= GM_getValue('savetowebdav', false);
 webdavUrl = GM_getValue('webdavUrl', '');
 webdavUsername = GM_getValue('webdavUsername', '');
 webdavPassword = GM_getValue('webdavPassword', '');

}
 // 创建设置界面
function createControl(tagName, attributes = {}, styles = {}, parent = document.body) {
    const element = document.createElement(tagName);

    // 设置属性
    for (const key in attributes) {
        element[key] = attributes[key];
    }

    // 设置样式
    Object.assign(element.style, styles);

    // 添加到父元素
    if (parent) {
        parent.appendChild(element);
    }

    return element;
}

function createSettingsUI() {
    const modalContainer = createControl('div', {
        className: 'settings-modal'
    }, {
        position: 'fixed',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        backgroundColor: 'rgba(255, 255, 255, 1)',
        borderRadius: '8px',
        boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
        zIndex: '9999',
        padding: '20px',
        width: '400px',
        maxWidth: '80%'
    });

    const title = createControl('h2', {
        textContent: '设置'
    }, {
        textAlign: 'center'
    });
    modalContainer.appendChild(title);

    // 创建控件并添加到模态框
    const controls = [
        {
            type: 'checkbox',
            id: 'saveImageCheckbox',
            label: '下载图片',
            checked: GM_getValue('saveImage', true),
            onchange: function() { GM_setValue('saveImage', this.checked); }
        },
      {
            type: 'checkbox',
            id: 'saveJsonCheckbox',
            label: '下载JSON',
            checked: GM_getValue('saveJson', false),
            onchange: function() { GM_setValue('saveJson', this.checked); }
        },
        {
            type: 'checkbox',
            id: 'hdImageCheckbox',
            label: '下载高清大图',
            checked: GM_getValue('shouldReplace', false),
            onchange: function() { GM_setValue('shouldReplace', this.checked); }
        },
        {
            type: 'checkbox',
            id: 'defaultTitleCheckbox',
            label: '使用网页标题名保存',
            checked: GM_getValue('useDefaultTitle', true),
            onchange: function() { GM_setValue('useDefaultTitle', this.checked); }
        },
        {
            type: 'checkbox',
            id: 'saveVideoInfoCheckbox',
            label: '下载视频信息',
            checked: GM_getValue('saveVideoInfo', false),
            onchange: function() { GM_setValue('saveVideoInfo', this.checked); }
        },
        {
            type: 'checkbox',
            id: 'pageCountCheckbox',
            label: '自定义抓取页数',
            checked: GM_getValue('pageCount', true),
            onchange: function() { GM_setValue('pageCount', this.checked); }
        },
      {
            type: 'checkbox',
            id: 'downloadLogFileA',
            label: '保存下载日志',
            checked: GM_getValue('downloadLogFileA', false),
            onchange: function() { GM_setValue('downloadLogFileA', this.checked); }
        },
      {
            type: 'checkbox',
            id: 'savetowebdav',
            label: '上传JSON到WebDav',
            checked: GM_getValue('savetowebdav', false),
            onchange: function() { GM_setValue('savetowebdav', this.checked); }
        },
        {
            type: 'number',
            id: 'delayInput',
            label: '设置延迟(毫秒)',
            value: GM_getValue('delayTime', 1000),
            placeholder: '设置延迟(毫秒)',
            onchange: function() { GM_setValue('delayTime', this.value); },
            style: {
                width: '1px' // 设置输入框宽度为100%
            }
        },
      {
            type: 'text',
            id: 'webdavUrlInput',
            label: 'WebDAV 网址',
            value: GM_getValue('webdavUrl', ''),
            placeholder: '输入WebDAV网址',
            onchange: function() { GM_setValue('webdavUrl', this.value); }
        },
        {
            type: 'text',
            id: 'webdavUsernameInput',
            label: 'WebDAV 账号',
            value: GM_getValue('webdavUsername', ''),
            placeholder: '输入WebDAV账号',
            onchange: function() { GM_setValue('webdavUsername', this.value); }
        },
        {
            type: 'text',
            id: 'webdavPasswordInput',
            label: 'WebDAV 密码',
            value: GM_getValue('webdavPassword', ''),
            placeholder: '输入WebDAV密码',
            onchange: function() { GM_setValue('webdavPassword', this.value); }
        }
    ];

    controls.forEach(control => {
        const input = createControl('input', {
            type: control.type,
            id: control.id,
            checked: control.checked,
            value: control.value,
            placeholder: control.placeholder,
            onchange: control.onchange
        }, {
            marginRight: '10px'
        });

        const label = createControl('label', {
            textContent: control.label,
            htmlFor: control.id
        }, {
            fontSize: '14px',
            marginLeft: '5px'
        });

        modalContainer.appendChild(input);
        modalContainer.appendChild(label);
        modalContainer.appendChild(createControl('br'));
    });

    // 关闭按钮
    const closeButton = createControl('button', {
        textContent: '关闭',
        onclick: () => {
          ini();
           WebDAVManager.updateConfig(webdavUrl, webdavUsername, webdavPassword);
            document.body.removeChild(modalContainer);
        }
    }, {
        marginTop: '10px',
        padding: '10px 20px',
        fontSize: '16px',
        backgroundColor: '#007bff',
        color: '#fff',
        border: 'none',
        borderRadius: '5px',
        cursor: 'pointer',
        float: 'right'
    });

    modalContainer.appendChild(closeButton);

    // 将模态框添加到页面
    document.body.appendChild(modalContainer);
}
// 调用示例


  const WebDAVManager = (function() {
        // WebDAV 配置
   let url = webdavUrl;
    let username = webdavUsername;
    let password =webdavPassword;


        // 通用 GM_xmlhttpRequest 封装函数
        function GM_xhr({ path = '/', method, success, fail, headers = {}, data, ...config }) {

            return new Promise(resolve => {
                GM_xmlhttpRequest({
                    url: url + path,
                    method,
                    ...config,
                    headers: {
                        'Connection': 'Keep-Alive', // 保持连接
                        'User-Agent': 'Mozilla/5.0', // 用户代理
                        'Authorization': 'Basic ' + btoa(username + ':' + password), // 基本认证
                        ...headers
                    },
                    data,
                    onload: xhr => {
                        if (xhr.status >= 200 && xhr.status < 300) {
                            if (success) success(xhr);
                        } else {
                            if (fail) fail(xhr);
                        }
                        resolve(xhr);
                    },
                    onerror: xhr => {
                        if (fail) fail(xhr);
                        resolve(xhr);
                    }
                });
            });
        }
//登录
    async function login() {
    let retryCount = 2; // 设置重试次数为2次

    while (retryCount > 0) {
        // 构建登录请求
        const LOGIN = {
            method: 'PROPFIND', // 使用 PROPFIND 方法检查根目录
            path: retryCount === 2 ? '/' : '', // 根据重试次数设置 path
            headers: {
                'Depth': '1',
                'Authorization': 'Basic ' + btoa(username + ':' + password),
                'Connection': 'Keep-Alive', // 保持连接
                'User-Agent': 'Mozilla/5.0' // 用户代理
            }
        };

        // 发起登录请求
        const loginResponse = await GM_xhr(LOGIN);

        // 判断登录结果
        if (loginResponse.status === 207) {
            console.log('登录成功');
            // 登录成功后,可以执行其他操作
            showModal("Webdav登录成功!",1000);
            return true; // 返回登录成功标志
        } else {
            console.error('登录失败:', loginResponse.status);
            if (retryCount === 1) {
                showModal("Webdav登录失败!" + loginResponse.status,1000);
                return false; // 返回登录失败标志
            } else {
                retryCount--; // 减少重试次数
            }
        }
    }

    // 如果重试次数用尽仍未登录成功,执行其他操作(可根据实际情况添加代码)
    console.error('重试次数用尽,登录失败');
    showModal("Webdav登录失败!重试次数用尽",1000);
    return false;
}

    //刷新
   function updateConfig(newUrl, newUsername, newPassword) {
        if (newUrl && newUsername && newPassword) {
            url = newUrl;
            username = newUsername;
            password = newPassword;
            // 在这里调用登录函数
            login();
        } else {
            console.error('WebDAV 配置信息不完整');
        }
    }
        // 获取 WebDAV 中指定路径下的所有文件和文件夹
        async function listFilesAndFolders(folderName) {
            const path = folderName.endsWith('/') ? folderName : folderName + '/';
            const PROPFIND = {
                method: 'PROPFIND',
                headers: {
                    'Depth': '1'
                },
                success: (xhr) => {
                    const parser = new DOMParser();
                    const xmlDoc = parser.parseFromString(xhr.responseText, 'text/xml');
                    const responses = xmlDoc.getElementsByTagNameNS('DAV:', 'response');

           let fileCount = 0;
           //let fileList = `<div class="custom-hr"></div>`;
 let fileList = "";

for (let i = 0; i < responses.length; i++) {
    const href = decodeURIComponent(responses[i].getElementsByTagNameNS('DAV:', 'href')[0].textContent.trim());
    const displayName = href.substring(href.lastIndexOf('/') + 1);
    const isCollection = responses[i].getElementsByTagNameNS('DAV:', 'collection').length > 0;

    if (!isCollection) {
        fileList += `
            <li class="file-item">
    <input type="checkbox" name="fileCheckbox" class="file-checkbox">
    <span>${fileCount + 1}.</span> <!-- 文件序号 -->
    <a href="#" data-display-name="${displayName}" onclick="WebDAVManager.downloadAndDisplayFile('${folderName}', '${displayName}')" class="file-link">${displayName}</a> <!-- 文件名链接 -->    
    <div class="file-actions">
        <a href="#" onclick="WebDAVManager.renameFile('${folderName}', '${displayName}')" class="file-action">更名</a>
        <a href="#" onclick="WebDAVManager.deleteFile('${folderName}', '${displayName}')" class="file-action">删除</a>
        <a href="#" onclick="WebDAVManager.downloadFile('${folderName}', '${displayName}')" class="file-action">下载</a>
    </div>
    <div style="clear: both;"></div> <!-- 清除浮动 -->
</li>
        `;
        fileCount++;
    }
}
/*fileList += `
    <div class="custom-hr"></div>
    <p>文件数量: ${fileCount}</p>
    </ul>
`;*/
fileList += '</ul>';

                    showDialog(`
        <span style="font-size: 24px;">文件夹 ${folderName}</span><br>
        <span style="color: green;"><span id="fileCountLabel">文件数量:</span> <span class="file-count">${fileCount}</span></span>
        <div class="custom-hr"></div>
    `, fileList);

                },
                fail: (xhr) => {
                    alert('获取文件列表失败:' + xhr.status);
                }
            };

            await GM_xhr({ ...PROPFIND, path });
        }

        // 下载文件函数

        // 在 WebDAV 中创建新文件夹
        async function createFolder(folderName) {
            const MKCOL = {
                method: 'MKCOL',
                path: folderName.endsWith('/') ? folderName : folderName + '/',
                success: () => {
                    alert('文件夹创建成功');
                },
                fail: (xhr) => {
                    if (xhr.status === 409) {
                       // alert('冲突:文件夹可能已经存在或路径不正确');
                    } else {
                        //alert('创建文件夹失败:' + xhr.status);
                    }
                }
            };

            await GM_xhr({ ...MKCOL });
        }

        // 上传文件到 WebDAV

    async function uploadFile(folderName, fileName, fileContent) {
    // 检查文件是否已存在
    const HEAD = {
        method: 'HEAD',
        path: folderName + '/' + fileName,
        success: () => {
            //alert('文件已存在,无需重复上传');
          window.addToLog(fileName+'文件存在', 'info');
            // 可以在这里执行文件已存在时的逻辑,比如显示提示信息或执行其他操作
        },
        fail: async (xhr) => {
            if (xhr.status === 404) {
                // 文件不存在,执行上传操作
                const PUT = {
                    method: 'PUT',
                    path: folderName + '/' + fileName,
                    data: fileContent,
                    success: () => {

                       window.addToLog(fileName+'文件上传成功', 'info');
                    },
                    fail: (xhr) => {
                      window.addToLog(fileName+'文件上传失败', 'error');
                    }
                };

                await GM_xhr({ ...PUT });
            } else {
                 window.addToLog(fileName+'文件检查上传失败', 'error');
            }
        }
    };

    await GM_xhr({ ...HEAD });
}

        // 重命名文件
     async function renameFile(folderName, oldFileName) {
    // 弹出对话框让用户输入新文件名
    const newFileName = prompt(`请输入 ${oldFileName} 的新文件名:`);
    if (!newFileName) {
        alert('未输入新文件名,操作已取消。');
        return;
    }
    const encodedOldFileName = encodeURIComponent(oldFileName); // 对旧文件名进行编码

    const encodedNewFileName = encodeURIComponent(newFileName+ '.json'); // 对新文件名进行编码
    const sourcePath = folderName + '/' + encodedOldFileName;
    const destinationPath = folderName + '/' + encodedNewFileName;

    const MOVE = {
        method: 'MOVE',
        path: sourcePath,
        headers: {
            'Destination': url + destinationPath,
            'Overwrite': 'T' // 允许覆盖目标文件
        },
        success: () => {
            alert(`文件 ${oldFileName} 已成功重命名为 ${newFileName}`);
            listFilesAndFolders(folderName); // 刷新文件列表
        },
        fail: (xhr) => {
            if (xhr.status === 409) {
                alert('冲突:文件名可能已存在或路径不正确');
            } else {
                alert('重命名文件失败:' + xhr.status);
            }
        }
    };

    await GM_xhr({ ...MOVE });
}


        // 删除文件
async function deleteFile(folderName, fileName) {
        let confirmdelete=false;
  if(deleteSelected){
            confirmdelete =true;
         }else{
            confirmdelete = confirm(`确定要删除文件 ${fileName} 吗?`);
         }

            if (confirmdelete) {
                try {
                    await deleteFileFromServer(folderName, fileName);
   const fileItem = document.querySelector(`a[onclick*="${fileName}"]`).closest('li');
                   const checkbox = fileItem.querySelector('input[type="checkbox"]');
            if (checkbox && checkbox.checked) {
                checkbox.checked = false; // 清除选中状态
                checkbox.disabled = true; // 禁止操作
            }
                    markItemDeleted(fileItem);
                } catch (error) {
                    alert('删除文件失败:' + error.message);
                }
            }
        }
       function markItemDeleted(fileItem) {
            // 将文件名链接标记为已删除
            const fileNameLink = fileItem.querySelector('a');
            fileNameLink.style.textDecoration = 'line-through';
            fileNameLink.removeAttribute('onclick');
            fileNameLink.style.pointerEvents = 'none'; // 不可点击
           fileNameLink.style.color = 'gray'; // 设置为灰色

            // 将后续按钮标记为已删除
            const actionButtons = fileItem.querySelectorAll('div > a');
            actionButtons.forEach(button => {
                button.style.textDecoration = 'line-through';
                button.removeAttribute('onclick');
                button.style.pointerEvents = 'none'; // 不可点击
               button.style.color = 'gray'; // 设置为灰色
            });
        }
        // 实际删除文件的函数
        async function deleteFileFromServer(folderName, fileName) {
            const DELETE = {
                method: 'DELETE',
                path: folderName + '/' + fileName,
            };

            return new Promise((resolve, reject) => {
                GM_xhr({ ...DELETE, success: resolve, fail: reject });
            });
        }

        // 检查文件夹是否存在
        async function checkFolderExists(folderName) {
            const HEAD = {
                method: 'HEAD',
                path: folderName.endsWith('/') ? folderName : folderName + '/',
                success: (xhr) => {
                    if (xhr.status === 200) {
                      //  alert('文件夹存在');
                    } else if (xhr.status === 404) {
                        alert('文件夹不存在');
                    } else {
                        alert('检查文件夹状态失败:' + xhr.status);
                    }
                },
                fail: (xhr) => {
                    alert('检查文件夹失败:' + xhr.status);
                }
            };

            await GM_xhr({ ...HEAD });
        }

        // 下载并展示文件
async function downloadFile(folderName, fileName, zip = null) {
    return new Promise((resolve, reject) => {
        // 发起 GET 请求下载文件内容
        const GET = {
            method: 'GET',
            path: folderName + '/' + fileName,
            success: (xhr) => {
                const fileContent = xhr.responseText;
                const jsonData = JSON.parse(fileContent);
                const content = generateJsonIndexContent(jsonData);

                if (zip) {
                    // 如果传入了压缩包实例,则将文件内容添加到压缩包中
                    const sanitizedFileName = sanitizeFileName(fileName.replace('.json', '')) + '.html';
                    zip.file(sanitizedFileName, content);
                    resolve();
                } else {
                    // 否则直接下载文件
                    const blob = new Blob([content], { type: 'text/html' });
                    const htmlUrl = URL.createObjectURL(blob);
                    const a = document.createElement('a');
                    a.href = htmlUrl;
                    a.download = sanitizeFileName(fileName.replace('.json', '')) + '.html';
                    a.click();
                    resolve();
                }
            },
            fail: (xhr) => {
                reject(new Error('下载文件失败:' + xhr.status));
            }
        };

        GM_xhr({ ...GET });
    });
}



        async function downloadAndDisplayFile(folderName, fileName) {
            const GET = {
                method: 'GET',
                path: folderName + '/' + fileName,
                success: (xhr) => {
                    try {
                        const fileContent = xhr.responseText;
                        const jsonData = JSON.parse(fileContent);
                        const content = generateJsonIndexContent(jsonData);

                        // 打开一个新的浏览器标签页显示内容
                        const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(content);
                        GM_openInTab(dataUrl);
                    } catch (e) {
                        alert('解析 JSON 文件失败:' + e.message);
                    }
                },
                fail: (xhr) => {
                    alert('下载文件失败:' + xhr.status);
                }
            };

            await GM_xhr({ ...GET });
        }
//我是你爹啊
        // 显示对话框
function showDialog(title, fileList, folderName) {
    // 添加CSS样式
    const style = document.createElement('style');
 style.textContent = `
        .overlay {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background-color: rgba(0, 0, 0, 0.5);
            z-index: 9998;
        }
         .close-x-button {
            position: absolute;
            top: 10px;
            right: 10px;
            font-size: 24px;
            background: none;
            border: none;
            cursor: pointer;
            color: red;
        }
        .dialog {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #fff;
            padding: 20px;
            border-radius: 10px;
            width: 80%;
            max-height: 80%;
            overflow-y: auto;
            box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
            z-index: 9999;
        }
        .dialog-title {
            margin-bottom: 20px;
        }
        .dialog-button {
            padding: 10px 20px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            margin-bottom: 20px;
        }
        .select-button {
            background-color: #007BFF;
            color: #fff;
        }
        .delete-button {
            background-color: #dc3545;
            color: #fff;
            margin-left: 10px;
        }
         .select-all-button {
            background-color: #28a745;
            color: #fff;
            margin-left: 10px;
        }
        .download-button {
            background-color: #007BFF;
            color: #fff;
            margin-left: 10px;
        }
        .search-button {
    background-color: #6c757d;
    color: #fff;
    margin-left: 10px;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}

.search-button:hover {
    background-color: #495057;
}
        .close-button {
            background-color: #007BFF;
            color: #fff;
            margin-top: 20px;
        }
        .file-list {
            list-style-type: none;
            padding: 0;
        }
        .file-action {
            margin-right: 10px;
            float: left;
            color: blue;
            cursor: pointer;
        }
        .file-actions-container {
            float: right;
        }
        .clear-float {
            clear: both;
        }
        .file-item {
    border-bottom: 1px solid #ddd; /* 添加下划线 */
    padding: 10px 0; /* 添加上下内边距,使下划线与内容保持一定距离 */
    display: flex;
    align-items: center;
}

.file-checkbox {
    margin-right: 10px;
}

.file-link {
    margin-right: 10px;
    color: blue;
    flex-grow: 1; /* 使链接占据剩余空间 */
}

.file-actions {
    display: flex;
    gap: 10px; /* 按钮之间的间距 */
}

.file-action {
    color: #007BFF;
    cursor: pointer;
}
.custom-hr {
        height: 1px;
        background-color: #007BFF; /* 设置分割线的颜色为蓝色 */
        margin: 20px 0; /* 设置分割线的上下间距 */
    }

.file-action:hover {
    text-decoration: underline;
}
      
     @media (max-width: 600px) {
            .dialog-button {
                padding: 5px 10px;
                font-size: 12px;
                margin-bottom: 10px;
            }
            .delete-button,
            .download-button,
            .select-all-button,
            .search-button {
                margin-left: 0;
            }
            .button-container {
                right: 5px;
            }
        }
    `;
    document.head.appendChild(style);




    // 创建对话框
    const dialog = document.createElement('div');
    dialog.className = 'dialog';
        // 创建遮罩
  const overlay = document.createElement('div');
    overlay.className = 'overlay';
    overlay.onclick = function() {
        document.body.removeChild(dialog);
        document.body.removeChild(overlay);
    };
  // 创建右上角的关闭 "X" 按钮
   const closeXButton = document.createElement('button');
    closeXButton.textContent = '✖';
    closeXButton.className = 'close-x-button';
    closeXButton.onclick = function() {
      document.body.removeChild(dialog);
       document.body.removeChild(overlay);
    };
    dialog.appendChild(closeXButton);
  document.addEventListener('click', function(event) {
    // 检查点击事件发生的目标元素是否在对话框内部
    if (!dialog.contains(event.target) && !overlay.contains(event.target)) {
        // 如果点击位置不在对话框内部,则移除对话框和覆盖层
        document.body.removeChild(dialog);
        document.body.removeChild(overlay);
    }
});
    // 标题
    const titleElement = document.createElement('h1');
    titleElement.innerHTML =title;

    titleElement.className = 'dialog-title';
    dialog.appendChild(titleElement);

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

// 创建“选择”按钮
const selectButton = document.createElement('button');
selectButton.textContent = '选择列表';
selectButton.className = 'dialog-button select-button';
selectButton.onclick = function() {
    toggleSelection();
};
buttonContainer.appendChild(selectButton);
// 创建“搜索”按钮
const searchButton = document.createElement('button');
searchButton.textContent = '搜索列表'; // 按钮显示的文本内容
searchButton.className = 'dialog-button search-button'; // 按钮的CSS类名
searchButton.onclick = function() {
    if (searchButton.textContent === '搜索列表') {
        // 如果当前是搜索状态,则进行搜索
        const searchTerm = prompt('请输入搜索内容:'); // 弹出输入框等待用户输入搜索内容
        if (searchTerm !== null) { // 用户点击了确定按钮
            filterFiles(searchTerm); // 调用过滤文件函数,传入搜索关键词
            searchButton.textContent = '回到列表'; // 将搜索按钮文本改为"Back"
        }
    } else {
        // 如果当前是Back状态,则恢复初始文件列表
        resetFileList();
    }
};
function filterFiles(searchTerm) {
    const files = Array.from(fileListContainer.children); // 获取文件列表的所有子元素(文件项)
    let matchCount = 0; // 初始化匹配数量为 0
    
    // 遍历文件列表,根据搜索关键词过滤显示符合条件的文件项
    files.forEach(file => {
        const fileName = file.querySelector('.file-link').textContent.toLowerCase(); // 获取文件名并转换为小写
        if (fileName.includes(searchTerm.toLowerCase())) {
            file.style.display = ''; // 匹配到的文件项显示
            matchCount++; // 匹配数量加一
        } else {
            file.style.display = 'none'; // 不匹配的文件项隐藏
        }
    });

    // 更新文件数量标签内容
    const fileCountLabel = document.getElementById('fileCountLabel');
    if (fileCountLabel) {
        fileCountLabel.textContent = `搜索数量: `;
    }

    // 更新包裹文件数量的元素内容
    const fileCountElement = document.querySelector('.file-count');
    if (fileCountElement) {
        fileCountElement.textContent = matchCount;
    }
}


// 恢复初始文件列表函数
function resetFileList() {
    const files = Array.from(fileListContainer.children); // 获取文件列表的所有子元素(文件项)
    files.forEach(file => {
        file.style.display = ''; // 显示所有文件项
    });
    searchButton.textContent = '搜索列表'; // 恢复搜索按钮文本为"搜索"

    // 更新文件数量标签内容为 "文件数量:"
    const fileCountLabel = document.getElementById('fileCountLabel');
    if (fileCountLabel) {
        fileCountLabel.textContent = '文件数量:';
    }

    // 更新包裹文件数量的元素内容为当前文件数量
    const fileCountElement = document.querySelector('.file-count');
    if (fileCountElement) {
        const currentFileCount = files.length; // 获取当前文件数量
        fileCountElement.textContent = currentFileCount;
    }
}

buttonContainer.appendChild(searchButton);

 // 创建“全部”按钮
 const selectAllButton = document.createElement('button');
    selectAllButton.textContent = '全部选中';

    selectAllButton.className = 'dialog-button select-all-button';
  selectAllButton.style.display = 'none'; // 初始隐藏
    selectAllButton.onclick = function() {
        toggleSelectAll(selectAllButton);
    };
    buttonContainer.appendChild(selectAllButton);

// 创建“删除选中”按钮
const deleteSelectedButton = document.createElement('button');
deleteSelectedButton.textContent = '删除选中';
deleteSelectedButton.className = 'dialog-button delete-button';
deleteSelectedButton.style.display = 'none'; // 初始隐藏
deleteSelectedButton.onclick = function() {
    deleteSelectedFiles();
};
buttonContainer.appendChild(deleteSelectedButton);

// 创建“下载选中”按钮
const downloadSelectedButton = document.createElement('button');
downloadSelectedButton.textContent = '下载选中';
downloadSelectedButton.className = 'dialog-button download-button';
downloadSelectedButton.style.display = 'none'; // 初始隐藏
downloadSelectedButton.onclick = function() {
    downloadSelectedFiles();
};
buttonContainer.appendChild(downloadSelectedButton);

// 将按钮容器添加到对话框中
dialog.appendChild(buttonContainer);

    // 文件列表
    const fileListContainer = document.createElement('ul');
    fileListContainer.className = 'file-list';
    fileListContainer.innerHTML = fileList;
    dialog.appendChild(fileListContainer);



    // 添加到body
    document.body.appendChild(overlay);
    document.body.appendChild(dialog);
let selectButtonInitialTop = 0;

// 记录选择按钮的初始位置
if (selectButton) {
    selectButtonInitialTop = selectButton.offsetTop;
}

// 监听对话框的滚动事件
dialog.addEventListener('scroll', function() {
    const buttons = [
        { button: selectButton, offsetHeight: true },
        { button: selectAllButton, offsetHeight: true },
        { button: deleteSelectedButton, offsetHeight: true },
        { button: downloadSelectedButton, offsetHeight: true },
        { button: searchButton, offsetHeight: true }
        // 添加更多按钮对象,如果有的话
    ];

    if (buttons.every(buttonObj => buttonObj.button)) {
        const dialogRect = dialog.getBoundingClientRect();
        const fileListRect = fileListContainer.getBoundingClientRect();
        const newButtonTopBase = Math.max(fileListRect.top, dialog.scrollTop);
        let newButtonTop = newButtonTopBase;

        for (const { button, offsetHeight } of buttons) {
            if (button) {
                button.style.position = dialog.scrollTop === 0 ? 'static' : 'absolute';
                button.style.top = `${newButtonTop}px`;
                button.style.right = '10px';
                if (offsetHeight) {
                    newButtonTop += button.offsetHeight;
                }
            }
        }
    }
});


function toggleSelectAll(selectAllButton) {
    const checkboxes = document.querySelectorAll('input[name="fileCheckbox"]');
    const allChecked = Array.from(checkboxes).every(checkbox => checkbox.checked);

    checkboxes.forEach(checkbox => {
        checkbox.checked = !allChecked;
    });

    // 切换按钮文本和背景颜色
    if (allChecked) {
        selectAllButton.textContent = '全部选中';
        selectAllButton.style.backgroundColor = ''; // 恢复默认背景颜色
    } else {
        selectAllButton.textContent = '全部取消选中';
        selectAllButton.style.backgroundColor = 'red'; // 改为红色背景
    }
}



    // 获取所有复选框
    const checkboxes = fileListContainer.querySelectorAll('input[type="checkbox"]');

    // 初始隐藏复选框
    checkboxes.forEach(checkbox => {
        checkbox.style.display = 'none';
    });

    // 复选框处理函数
    function toggleSelection() {
        checkboxes.forEach(checkbox => {
            checkbox.style.display = checkbox.style.display === 'none' ? 'inline-block' : 'none';
            checkbox.checked = false; // 取消复选框的选中状态
        });

        // 切换显示“删除选中”和“下载选中”按钮
        deleteSelectedButton.style.display = deleteSelectedButton.style.display === 'none' ? 'inline-block' : 'none';
        downloadSelectedButton.style.display = downloadSelectedButton.style.display === 'none' ? 'inline-block' : 'none';
      selectAllButton.style.display = selectAllButton.style.display === 'none' ? 'inline-block' : 'none';
        selectButton.textContent = downloadSelectedButton.style.display === 'none' ? '选择列表' : '取消选择';
    }

}

async function downloadSelectedFiles() {
    // 获取所有选中的复选框
    const checkboxes = document.querySelectorAll('input[name="fileCheckbox"]:checked');
    if (checkboxes.length === 0) {
        showModal('没有选中的文件', 2000);
        return;
    }
    // 创建一个 JSZip 实例
    const zip = new JSZip();

    try {
        // 遍历所有选中的复选框
        for (const checkbox of checkboxes) {
            // 获取文件项信息
            const fileItem = checkbox.closest('li');
            const fileName = fileItem.querySelector('a[data-display-name]').dataset.displayName;

            // 调用 downloadFile 函数,将文件添加到压缩包中
            await downloadFile(webdavfold, fileName, zip);
        }

        // 生成 ZIP 文件
        const zipBlob = await zip.generateAsync({ type: 'blob' });

        // 创建下载链接并触发下载
        const zipUrl = URL.createObjectURL(zipBlob);
        const a = document.createElement('a');
        a.href = zipUrl;
        a.download = 'selected_files.zip';
        a.click();

        console.log('压缩包下载完成');
showModal('下载选中完成:',2000);
    } catch (error) {
        console.error('下载选中文件失败:', error);
        showModal('下载选中文件失败: ' + error.message,2000);

    }
}



function deleteSelectedFiles() {
    // 获取所有选中的复选框
   const selectedCheckboxes = document.querySelectorAll('input[name="fileCheckbox"]:checked');

    if (selectedCheckboxes.length === 0) {
        alert('没有选中文件');
        return;
    }

    // 弹出确认删除的提示框
    const confirmDelete = confirm('确定要删除选中的文件吗?');

    if (confirmDelete) {
      deleteSelected=true;
        selectedCheckboxes.forEach(checkbox => {
            const fileItem = checkbox.closest('li');
            const deleteButton = fileItem.querySelector('a[onclick*="deleteFile"]');

            // 调试日志
            console.log('fileItem:', fileItem);
            console.log('deleteButton:', deleteButton);

            // 获取文件名
            const fileName = fileItem.querySelector('a[data-display-name]').dataset.displayName;

            // 添加调试日志
            console.log('Deleting file:', fileName);

            // 触发删除按钮的点击事件
            deleteButton.click();
//deleteFile(webdavfold,fileName);

            // 将文件项标记为已删除
           // markItemDeleted(fileItem);
        });
deleteSelected=false;
        console.log('删除选中文件');
    }
}


    // 将 API 方法公开
        return {
            listFilesAndFolders,
           updateConfig,
            createFolder,
            uploadFile,
            renameFile,
            deleteFile,
            downloadFile,
            downloadAndDisplayFile,
            checkFolderExists
        };
    })();

    // 将库暴露到全局作用域
    unsafeWindow.WebDAVManager = WebDAVManager;

    // 示例用法:列出文件和文件夹,创建新文件夹,上传文件,删除文件
 if (!url || !username || !password) {
      console.error('WebDAV 配置更新失败:缺少 URL、用户名或密码');
        return;
 }
   WebDAVManager.updateConfig(webdavUrl, webdavUsername, webdavPassword);
   WebDAVManager.createFolder(webdavfold);
WebDAVManager.checkFolderExists(webdavfold);

    //WebDAVManager.uploadFile(webdavfold, 'nidie.txt', '这是 example.txt 的内容');
    // WebDAVManager.deleteFile('Missav保存', 'example.txt');

})();