- // ==UserScript==
- // @name MissAv批量备份收藏视频
- // @namespace https://github.com/100816100886
- // @version 1.2.3.41
- // @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
- // @supportURL https://github.com/ChinaGodMan/UserScripts/issues
- // @homepageURL https://github.com/ChinaGodMan/UserScripts
- // ==/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 mediaQuery = window.matchMedia('(max-width: 600px)')
- if (mediaQuery.matches) {
- modalContainer.style.width = '90%'
- modalContainer.style.maxWidth = '90%'
- modalContainer.style.padding = '10px'
- }
- }
-
- // 在页面加载时调用设置界面创建函数
-
-
- // 调用示例
-
-
- 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;
- }
-
- .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;
- }
- .close-x-button {
- background-color: #6c757d;
- color: red;
- 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,
- .close-x-button,
- .search-button {
- margin-left: 0;
- }
-
- .button-container {
- right: 5px;
- }
- }
-
- `
- document.head.appendChild(style)
-
- // 创建对话框
- const dialog = document.createElement('div')
- dialog.className = 'dialog'
- // 创建遮罩
-
-
- function close() {
- document.body.removeChild(dialog)
-
- }
-
- // 标题
- const titleElement = document.createElement('h1')
- titleElement.innerHTML = title
- titleElement.className = 'dialog-title'
- dialog.appendChild(titleElement)
-
-
- const buttonContainer = document.createElement('div')
- const buttonConfigs = [
- { text: '选择列表', className: 'dialog-button select-button', onclick: toggleSelection },
- { text: '搜索列表', className: 'dialog-button search-button', onclick: toggleSearch },
- { text: '关闭窗口', className: 'dialog-button close-x-button', onclick: close },
- { text: '全部选中', className: 'dialog-button select-all-button', onclick: toggleSelectAll },
- { text: '删除选中', className: 'dialog-button delete-button', onclick: deleteSelectedFiles },
- { text: '下载选中', className: 'dialog-button download-button', onclick: downloadSelectedFiles }
-
- // 可以添加更多按钮配置
- ]
- let count = 0 // 计数器,用于控制显示状态
- buttonConfigs.forEach(config => {
- const button = document.createElement('button')
- button.textContent = config.text
- button.className = config.className
- button.onclick = config.onclick
-
- if (count < 3) {
- button.style.display = 'inline-block' // 前两个按钮初始可见
- } else {
- button.style.display = 'none' // 后面的按钮初始隐藏
- }
-
- buttonContainer.appendChild(button)
- count++ // 每创建一个按钮,计数器加一
- })
- // 将按钮容器添加到对话框中
- dialog.appendChild(buttonContainer)
-
- // 文件列表
- const fileListContainer = document.createElement('ul')
- fileListContainer.className = 'file-list'
- fileListContainer.innerHTML = fileList
- dialog.appendChild(fileListContainer)
-
- // 添加到body
-
- document.body.appendChild(dialog)
- let selectButtonInitialTop = 0
- let selectAllButton = document.querySelector('.select-all-button')
- let searchButton = document.querySelector('.search-button')
- let downloadSelectedButton = document.querySelector('.download-button')
- let deleteSelectedButton = document.querySelector('.delete-button')
- let selectButton = document.querySelector('.select-button')
- let closeXButton = document.querySelector('.close-x-button')
- let scrollTimeout = 0 // 定义全局变量存储定时器
- // 记录选择按钮的初始位置
- if (selectButton) {
- selectButtonInitialTop = selectButton.offsetTop
- }
-
- // 监听对话框的滚动事件
- dialog.addEventListener('scroll', function () {
- clearTimeout(scrollTimeout)
- const buttons = [
- { button: selectButton, offsetHeight: true },
- { button: selectAllButton, offsetHeight: true },
-
- { button: deleteSelectedButton, offsetHeight: true },
- { button: downloadSelectedButton, offsetHeight: true },
- { button: searchButton, offsetHeight: true },
- { button: closeXButton, offsetHeight: true },
-
-
- // 添加更多按钮对象,如果有的话
- ]
- scrollTimeout = setTimeout(() => {
- 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
- }
- }
- }
- }
- }, 300) // 设置 300 毫秒的超时
- })
-
-
- // 获取所有复选框
- const checkboxes = fileListContainer.querySelectorAll('input[type="checkbox"]')
-
- // 初始隐藏复选框
- checkboxes.forEach(checkbox => {
- checkbox.style.display = 'none'
- })
- //选中全部
- function toggleSelectAll() {
- 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' // 改为红色背景
- }
- }
- //搜索
- //---------------
- function toggleSearch() {
- 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
- }
- }
- //搜索结束
- //---------------
-
- // 选择列表
- 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');
-
- })();
-
-