MissAv批量备份收藏视频

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

Tính đến 17-08-2024. Xem phiên bản mới nhất.

  1. // ==UserScript==
  2. // @name MissAv批量备份收藏视频
  3. // @namespace https://github.com/100816100886
  4. // @version 1.2.3.41
  5. // @description 从当前missav页面获取图片文件和视频信息,并合并结果后提供下载生成的网页文件
  6. // @license MIT
  7. // @author 人民的勤务员 <toniaiwanowskiskr47@gmail.com> & ChatGPT
  8. // @match https://missav.com/*
  9. // @grant GM_setValue
  10. // @grant GM_getValue
  11. // @grant GM_xmlhttpRequest
  12. // @grant GM_download
  13. // @grant GM_openInTab
  14. // @icon https://pic.616pic.com/ys_bnew_img/00/35/79/Gv93yQh7v6.jpg
  15. // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.7.1/jszip.min.js
  16. // @require https://update.greasyfork.org/scripts/498124/1396763/video.js
  17. // @require https://update.greasyfork.org/scripts/498149/1395619/%E4%BF%A1%E6%81%AF%E6%9F%A5%E7%9C%8B%E5%99%A8.js
  18. // @supportURL https://github.com/ChinaGodMan/UserScripts/issues
  19. // @homepageURL https://github.com/ChinaGodMan/UserScripts
  20. // ==/UserScript==
  21.  
  22. (function () {
  23. 'use strict'
  24.  
  25. var controlButton = createButton('备份', '10px', '10px')
  26. var buttonA = createButton('备份当前', '70px', '10px')
  27. var buttonB = createButton('备份片单', '130px', '10px')
  28. var buttonC = createButton('设置选项', '190px', '10px')
  29. var webdavbutton = createButton('WebDav', '250px', '10px')
  30. // 设置按钮的背景颜色和样式
  31. controlButton.style.backgroundColor = 'blue' // 控制按钮改为蓝色背景
  32. buttonA.style.backgroundColor = 'green'
  33. buttonB.style.backgroundColor = 'blue'
  34. buttonC.style.backgroundColor = 'red'
  35. webdavbutton.style.backgroundColor = 'blue'
  36. // 隐藏初始的三个按钮
  37. buttonA.style.display = 'none'
  38. buttonB.style.display = 'none'
  39. buttonC.style.display = 'none'
  40. webdavbutton.style.display = 'none'
  41. // 添加按钮到页面
  42. document.body.appendChild(controlButton)
  43. document.body.appendChild(buttonA)
  44. document.body.appendChild(buttonB)
  45. document.body.appendChild(buttonC)
  46. document.body.appendChild(webdavbutton)
  47. // 控制按钮的点击事件
  48. controlButton.addEventListener('click', function () {
  49. if (buttonA.style.display === 'none') {
  50. // 显示三个按钮
  51. buttonA.style.display = 'block'
  52. buttonB.style.display = 'block'
  53. buttonC.style.display = 'block'
  54. webdavbutton.style.display = 'block'
  55. controlButton.innerHTML = '隐藏'
  56. } else {
  57. // 隐藏三个按钮
  58. buttonA.style.display = 'none'
  59. buttonB.style.display = 'none'
  60. buttonC.style.display = 'none'
  61. webdavbutton.style.display = 'none'
  62. controlButton.innerHTML = '备份'
  63. }
  64. })
  65. webdavbutton.addEventListener('click', function () {
  66. // 点击按钮时执行的操作
  67. WebDAVManager.listFilesAndFolders(webdavfold)
  68.  
  69.  
  70. })
  71.  
  72. // 按钮A的点击事件
  73. buttonA.addEventListener('click', function () {
  74. resetGlobalVariables()
  75. singleFileDownload = true
  76. window.showLogContainer()
  77. var currentDate = new Date()
  78. var currentTime = currentDate.getFullYear() + '-' + (currentDate.getMonth() + 1) + '-' + currentDate.getDate() + '_' + currentDate.getHours() + '-' + currentDate.getMinutes() + '-' + currentDate.getSeconds()
  79. if (useDefaultTitle) {
  80. name = document.querySelector('meta[name="twitter:title"]').content
  81. } else {
  82. const twitterTitleContent = document.querySelector('meta[name="twitter:title"]').content
  83. name = prompt("请输入自定义名称:", twitterTitleContent)
  84. if (name === null) {
  85. name = twitterTitleContent
  86. }
  87. }
  88. inurl = window.location.href
  89. const defaultPages = getTotalPagesd()
  90.  
  91. const totalPages = setTotalPage(defaultPages)
  92. allpages = totalPages
  93. //const delay = settime();
  94. if (totalPages) {
  95. start(totalPages)
  96. }
  97.  
  98. })
  99.  
  100. // 按钮B的点击事件
  101. buttonB.addEventListener('click', function () {
  102. // 点击按钮时执行的操作
  103. resetGlobalVariables()
  104. fetchJsonData()
  105.  
  106.  
  107. })
  108.  
  109. // 按钮C的点击事件
  110. buttonC.addEventListener('click', function () {
  111. createSettingsUI()
  112.  
  113. // 这里可以添加按钮C点击后的具体操作,比如打开链接或执行其他动作
  114. })
  115.  
  116. // 创建按钮的辅助函数
  117. function createButton(text, top, left) {
  118. var button = document.createElement('button')
  119. button.innerHTML = text
  120. button.style.position = 'fixed'
  121. button.style.bottom = top
  122. button.style.right = left
  123. button.style.zIndex = '1000'
  124. button.style.padding = '10px'
  125. button.style.border = 'none'
  126. button.style.cursor = 'pointer'
  127. button.style.color = '#fff'
  128. button.style.fontSize = '14px'
  129. button.style.fontWeight = 'bold'
  130. button.style.textAlign = 'center'
  131. button.style.width = '100px' // 调整按钮宽度
  132. return button
  133. }
  134. // 全局变量
  135. var allResults = [] // 存储所有的结果数据
  136. var zip = new JSZip() // 创建一个压缩文件实例
  137. var allzip = new JSZip() // 另一个可能的压缩文件实例
  138. var imgFolder = zip.folder("img") // 在 zip 中创建一个名为 "img" 的文件夹,用于存储图片文件
  139. var allimgFolder = allzip.folder("img") // 在 allzip 中创建一个名为 "img" 的文件夹,可能用于另一个压缩文件的图片存储
  140. var ALLfiledown = false // 标识是否所有文件已下载完毕的布尔变量
  141. var videos = [] // 存储视频文件或相关信息的数组
  142. var finalData = [] // 存储最终处理数据的数组
  143. var inurl = "" // 当前下载地址的变量
  144. var pendingRequests = 0 // 当前待处理的请求数量
  145. var delayTime // 延迟时间,以毫秒为单位,用于控制异步操作的时间间隔
  146. var currentPage = 1 // 当前处理的页数,可能用于分页处理或其他进度跟踪
  147. var currentUrlIndex = 0 // 当前处理的 urls 数组中的索引位置
  148. var name = "" // 当前下载的名称
  149. var urls = [] // 存储需要处理的网址数组
  150. var a = -1 // 循环中的计数或索引,初始值为 -1
  151. var allZipContents = [] // 存储所有压缩文件内容的数组
  152. var singleFileDownload = false // 标识是否为单个文件下载模式的布尔变量
  153. var names = [] // 存储下载名称列表的数组
  154. var allpages = 0 // 存储总页数或其他页面处理相关信息的变量
  155. var modalContainer = null // 存储模态窗口容器的全局变量,用于显示下载进度或其他信息
  156. var shouldReplace = false // 控制是否在下载大图时进行替换操作的布尔变量
  157. var temporaryData = []
  158. var saveJson = false
  159. var useDefaultTitle = true
  160. var pageCount = true
  161. var saveVideoInfo = false
  162. var saveImage = false
  163. var downloadLog = {}
  164. var errorLogs = {}
  165. var downloadLogFileA = false // 这里设置为 true 时载日志
  166. var webdavfold = "missavsave"
  167. //var webdavfold="1111";
  168. var savetowebdav = false
  169. var webdavUrl = ''
  170. var webdavUsername = ''
  171. var webdavPassword = ''
  172. var deleteSelected = false
  173. ini()//读取配置
  174. function resetGlobalVariables() {
  175. zip = new JSZip() // 重置为一个新的 JSZip 实例,用于创建新的压缩文件
  176. allzip = new JSZip() // 可能是另一个新的 JSZip 实例,用于其他用途的压缩文件
  177. if (saveImage) {
  178. imgFolder = zip.folder("img") // 在 zip 中创建一个名为 "img" 的文件夹,用于存储图片文件
  179. allimgFolder = allzip.folder("img") // 在 allzip 中创建一个名为 "img" 的文件夹,可能用于另一个压缩文件的图片存储
  180. }
  181. downloadLog = {}
  182. errorLogs = {}
  183. ALLfiledown = false // 重置为 false,表示所有文件未下载完毕
  184. videos = [] // 清空存储视频文件或相关信息的数组
  185. finalData = [] // 清空存储最终处理数据的数组
  186. inurl = "" // 重置当前下载地址为空字符串
  187. pendingRequests = 0 // 重置待处理的请求数量为 0
  188.  
  189. currentPage = 1 // 重置当前处理的页数为 1
  190. currentUrlIndex = 0 // 重置当前处理的 urls 数组索引为 0
  191. name = "" // 重置当前下载的名称为空字符串
  192. urls = [] // 清空存储需要处理的网址数组
  193. a = -1 // 重置循环中的计数或索引为 -1
  194. allZipContents = [] // 清空存储所有压缩文件内容的数组
  195. singleFileDownload = false // 重置为 false,表示不是单个文件下载模式
  196. names = [] // 清空存储下载名称列表的数组
  197. allpages = 0 // 重置存储总页数或其他页面处理相关信息的变量为 0
  198. temporaryData = []
  199. }
  200.  
  201.  
  202. async function processUrls() {
  203. //delayTime = 20;
  204. let completedTasks = 0 // 计数已完成的任务数量
  205.  
  206. for (const url of urls) {
  207. a = a + 1 // 每次循环递增 a
  208. inurl = url
  209. console.log("正在处理网址:", url, names[a])
  210. window.addToLog("处理:" + url + names[a], 'info')
  211. name = names[a]
  212.  
  213. try {
  214. const totalPages = await getTotalPages(url) // 等待 getTotalPages 返回结果
  215. console.log("Total pages for", url, ":", totalPages) // 显示总页数
  216. window.addToLog(name + " 总页数:" + url + totalPages, 'info')
  217. allpages = totalPages
  218. start(totalPages) // 启动处理流程
  219.  
  220. // 等待当前页面的请求完成
  221. while (pendingRequests > 0) {
  222. await new Promise(resolve => setTimeout(resolve, 100)) // 每隔 100 毫秒检查一次是否所有请求都已完成
  223. }
  224.  
  225. completedTasks++ // 标记当前任务已完成
  226. } catch (error) {
  227. console.error("Error processing URL:", url, error) // 处理错误信息
  228. allpages = 1
  229. start(1) // 启动处理流程
  230. while (pendingRequests > 0) {
  231. await new Promise(resolve => setTimeout(resolve, 100)) // 每隔 100 毫秒检查一次是否所有请求都已完成
  232. }
  233. completedTasks++ // 标记当前任务已完成
  234. }
  235. }
  236.  
  237. // 如果所有任务都已完成且 urls 数组不为空,则调用下载函数
  238. if (completedTasks === urls.length && urls.length !== 0) {
  239. downloadAllZips()
  240. }
  241. }
  242.  
  243.  
  244.  
  245.  
  246. function getAllCookies() {
  247. return document.cookie
  248. }
  249.  
  250. // 获取指定 JSON 数据的函数
  251.  
  252.  
  253.  
  254.  
  255. function fetchJsonData() {
  256. const cookies = getAllCookies()
  257. //alert(cookies);
  258. console.log('Current page cookies:', cookies)
  259.  
  260. // 构建 API URL
  261. const apiUrl = 'https://missav.com/api/playlists/dfe-057'
  262.  
  263. // 发送带有 cookies 的请求
  264. GM_xmlhttpRequest({
  265. method: 'GET',
  266. url: apiUrl,
  267. headers: {
  268. 'Cookie': cookies
  269. },
  270. onload: function (response) {
  271. if (response.status === 200) {
  272. try {
  273. const jsonResponse = JSON.parse(response.responseText)
  274. if (jsonResponse && Array.isArray(jsonResponse.data)) {
  275. createReportUI(jsonResponse.data, 500) // 假设每页显示 10 个项目
  276.  
  277. // 调用 processUrls 函数处理 URLs
  278. } else {
  279. console.error('JSON 格式无效')
  280. showModal("JSON 格式无效", 2000)
  281. }
  282. } catch (error) {
  283. console.error('Error parsing JSON:', error)
  284. showModal("解析错误" + error, 2000)
  285. }
  286. } else {
  287. console.error('Request failed with status:', response.status)
  288. showModal("解析错误", 2000)
  289. }
  290. },
  291. onerror: function (error) {
  292. showModal("解析错误" + error, 2000)
  293. }
  294. })
  295. }
  296.  
  297. function processUrl(url) {
  298. // 检查是否包含 `page=` 参数
  299. var pageIndex = url.indexOf('page=')
  300.  
  301. if (pageIndex !== -1) {
  302. // 找到 `page=` 参数并删除它及其后的所有内容
  303. var baseUrl = url.substring(0, pageIndex + 5) // +5 to include `page=`
  304. return baseUrl
  305. } else {
  306. // 检查是否已有其他参数
  307. if (url.includes('?')) {
  308. // 有其他参数,添加 `&page=`
  309. return url + '&page='
  310. } else {
  311. // 没有其他参数,添加 `?page=`
  312. return url + '?page='
  313. }
  314. }
  315. }
  316. function settime() {
  317. // 让用户输入延时时间
  318. delayTime = prompt("请输入每页请求的延时时间(毫秒):", "1000")
  319.  
  320. // 检查用户是否取消输入
  321. if (delayTime === null) {
  322. alert("输入取消")
  323. return
  324. }
  325.  
  326. delayTime = parseInt(delayTime)
  327.  
  328. // 检查输入的延时时间是否有效
  329. if (isNaN(delayTime) || delayTime <= 0) {
  330. alert("请输入有效的延时时间(正整数)!")
  331. return
  332. }
  333.  
  334. // 返回有效的延时时间
  335. return delayTime
  336. }
  337.  
  338.  
  339. function getTotalPagesd() { // 获取总页数
  340. var totalPagesElement = document.querySelector('#price-currency')
  341. var totalPagesText = totalPagesElement ? totalPagesElement.innerText : ''
  342. var totalPages = parseInt(totalPagesText.replace('/', '').trim(), 10)
  343.  
  344. // 如果获取总页数失败,则返回 1
  345. if (isNaN(totalPages) || totalPages <= 0) {
  346. totalPages = 1
  347. }
  348.  
  349. return totalPages
  350. }
  351. function getTotalPages(url) {
  352. return new Promise((resolve, reject) => {
  353. // 如果没有提供 URL,则使用当前页面的 URL
  354. if (!url) {
  355. url = window.location.href
  356. }
  357.  
  358. // 发起 GM_xmlhttpRequest 请求获取页面内容
  359. GM_xmlhttpRequest({
  360. method: "GET",
  361. url: url,
  362. headers: { "Cookie": document.cookie },
  363. onload: function (response) {
  364. // 处理响应
  365. if (response.status === 200) {
  366. const parser = new DOMParser()
  367. const doc = parser.parseFromString(response.responseText, 'text/html')
  368. const totalPagesElement = doc.querySelector('#price-currency') // 替换为实际选择器
  369. if (totalPagesElement) {
  370. const totalPagesText = totalPagesElement.innerText
  371. const totalPages = parseInt(totalPagesText.replace('/', '').trim(), 10)
  372. resolve(totalPages) // 成功时返回总页数
  373. } else {
  374. window.addToLog('页面中没有找到总页数,默认为1页', 'warning')
  375. reject('Total pages element not found') // 页面中没有找到总页数元素
  376. }
  377. } else {
  378. window.addToLog('请求失败', 'warning')
  379. reject(`Request failed with status ${response.status}`) // 请求失败
  380. }
  381. },
  382. onerror: function () {
  383. window.addToLog('请求出错', 'warning')
  384. reject('Request failed') // 请求出错
  385.  
  386. }
  387. })
  388. })
  389. }
  390.  
  391.  
  392. // 设置总页数
  393. function setTotalPage(defaultPages) {
  394. if (!pageCount) {
  395. return defaultPages
  396. }
  397.  
  398. const inputPages = parseInt(prompt(`当前 ${name} 总页数为 ${defaultPages}。请输入你想要抓取的页数(不输入抓取全部):`, defaultPages), 10)
  399. if (isNaN(inputPages) || inputPages <= 0) {
  400. return defaultPages
  401. }
  402. return inputPages
  403. }
  404. // 开始处理页面抓取
  405. function start(totalPages, callback) {
  406. const pages = Array.from({ length: totalPages }, (_, i) => i + 1)
  407. pendingRequests = pages.length
  408. fetchPage(pages.shift(), pages, callback)
  409. }
  410.  
  411. // 点击按钮时执行操作
  412.  
  413.  
  414.  
  415. // 异步获取页面内容
  416. function fetchPage(pageNum, pages, callback) {
  417. const pageUrl = `${processUrl(inurl)}${pageNum}`
  418. console.log(`正在获取第 ${pageNum} 页的内容...`)
  419. //showModal(`正在获取${name} 第 ${pageNum} / ${allpages}页 `);
  420. if (a !== -1) {
  421. showModal(`${a + 1}/${names.length} 正在获取 ${name} ${pageNum} / ${allpages} 页`)
  422. } else {
  423. showModal(`正在获取 ${name} ${pageNum} / ${allpages} 页`)
  424. }
  425. GM_xmlhttpRequest({
  426. method: "GET",
  427. url: pageUrl,
  428. headers: { "Cookie": document.cookie },
  429. onload: function (response) {
  430. if (response.status === 200) {
  431. processPageContent(response.responseText, pageNum, pages, callback)
  432. } else {
  433. pendingRequests--
  434. checkIfComplete(callback)
  435. if (pages.length > 0) {
  436. setTimeout(() => fetchPage(pages.shift(), pages, callback), delayTime)
  437. }
  438. }
  439. }
  440. })
  441. }
  442.  
  443.  
  444. //获取视频信息
  445. function extractInformation(htmlContent) {
  446. let data = {} // 创建一个对象来存储提取的数据
  447. let xhr = new XMLHttpRequest()
  448. xhr.open('GET', htmlContent, false) // 同步方式打开请求
  449. xhr.send()
  450.  
  451. // 创建一个虚拟的 <div> 元素来加载 HTML 内容
  452. let tempDiv = document.createElement('div')
  453. tempDiv.innerHTML = xhr.responseText
  454.  
  455. // 获取所有包含信息的父元素列表
  456. let parentElements = tempDiv.querySelectorAll('div.space-y-2 > div')
  457.  
  458. if (parentElements.length > 0) {
  459. let allInfo = {} // 初始化一个空对象来存储所有信息
  460.  
  461. // 遍历每个包含信息的 <div> 元素
  462. parentElements.forEach(div => {
  463. let span = div.querySelector('span') // 获取第一个 <span> 元素
  464.  
  465. if (span) {
  466. let category = span.textContent.trim() // 获取主分类名称
  467.  
  468. if (!allInfo[category]) {
  469. allInfo[category] = [] // 初始化一个空数组来存储该分类下的所有信息
  470. }
  471.  
  472. // 查找所有的 <a> 元素和 <time> 元素
  473. div.querySelectorAll('a, time').forEach(element => {
  474. let info = {}
  475.  
  476. // 判断元素是否是 <a> 元素
  477. if (element.tagName === 'A') {
  478. info['name'] = element.textContent.trim() // 获取名称
  479. info['link'] = element.href.trim() // 获取链接
  480. } else if (element.tagName === 'TIME') {
  481. info = element.textContent.trim() // 如果是 <time> 元素,则直接保存其文本内容
  482. }
  483.  
  484. // 添加信息对象到相应的主分类数组中
  485. allInfo[category].push(info)
  486. })
  487.  
  488. // 如果没有找到 <a> 元素,则尝试获取 <span> 标签内的文本内容
  489. if (div.querySelectorAll('a').length === 0) {
  490. let spanText = div.querySelector('span.font-medium')
  491. if (spanText) {
  492. let info = spanText.textContent.trim()
  493. allInfo[category].push(info)
  494. }
  495. }
  496. }
  497. })
  498.  
  499. // 提取 class="mb-1 text-secondary break-all line-clamp-2" 的内容
  500. let descriptionElement = tempDiv.querySelector('.mb-1.text-secondary.break-all.line-clamp-2')
  501. let descriptionContent = descriptionElement ? descriptionElement.textContent.trim() : ''
  502. allInfo['简介'] = descriptionContent
  503.  
  504. // 将所有信息存储到 data 对象中
  505. data['videosinfo'] = allInfo
  506.  
  507. // 查找包含 x-cloak 和 x-show="currentTab === 'magnets'" 的第二个元素
  508. let secondElement = tempDiv.querySelector('div[x-cloak][x-show="currentTab === \'magnets\'"]')
  509.  
  510. if (secondElement) {
  511. let linksAndInfo = []
  512.  
  513. // 遍历第二个元素内的 <a> 元素
  514. secondElement.querySelectorAll('a[rel="nofollow"]').forEach(a => {
  515. let linkInfo = {
  516. name: a.textContent.trim(),
  517. link: a.href.trim()
  518. }
  519.  
  520. // 查找相邻的 <td> 元素,获取大小和日期信息
  521. let sizeTd = a.closest('td').nextElementSibling
  522. if (sizeTd && sizeTd.classList.contains('font-mono')) {
  523. linkInfo['size'] = sizeTd.textContent.trim() // 获取大小信息
  524. }
  525.  
  526. let dateTd = sizeTd ? sizeTd.nextElementSibling : null
  527. if (dateTd && dateTd.classList.contains('hidden')) {
  528. linkInfo['date'] = dateTd.textContent.trim() // 获取日期信息
  529. }
  530. let nextSibling = a.nextElementSibling
  531.  
  532. // 循环处理所有紧邻的<span>元素
  533. while (nextSibling && nextSibling.tagName === 'SPAN') {
  534. let spanText = nextSibling.textContent.trim()
  535. linkInfo['name'] += ' ' + spanText // 将<span>元素的文本内容追加到name中
  536.  
  537. nextSibling = nextSibling.nextElementSibling // 继续查找下一个兄弟元素
  538. }
  539. linksAndInfo.push(linkInfo)
  540. })
  541.  
  542. // 将第二个元素的链接和信息添加到 data 中
  543. data['secondElementLinksInfo'] = linksAndInfo
  544. } else {
  545. console.error('未找到包含 x-cloak 和 x-show="currentTab === \'magnets\'" 的第二个元素。')
  546. }
  547.  
  548. return data // 返回结构化的数据
  549.  
  550. } else {
  551. console.error('未找到匹配的父元素 div.space-y-2')
  552. return null // 如果未找到匹配的父元素,返回 null
  553. }
  554. } ///大
  555.  
  556.  
  557.  
  558.  
  559. // 使用XMLHttpRequest获取页面内容
  560. function fetchPageforinfo(url) {
  561. let xhr = new XMLHttpRequest()
  562.  
  563. xhr.onreadystatechange = function () {
  564. if (xhr.readyState === XMLHttpRequest.DONE) {
  565. if (xhr.status === 200) {
  566. extractInformation(xhr.responseText) // 将获取的页面内容传递给提取信息的函数
  567. } else {
  568. console.error('请求失败:' + xhr.status)
  569. }
  570. }
  571. }
  572.  
  573. xhr.open('GET', url, true)
  574. xhr.send()
  575. }
  576.  
  577.  
  578. // 处理获取到的页面内容
  579. function processPageContent(htmlContent, pageNum, pages, callback) {
  580. const parser = new DOMParser()
  581. const doc = parser.parseFromString(htmlContent, 'text/html')
  582. const divElements = doc.querySelectorAll('div.relative.aspect-w-16.aspect-h-9.rounded.overflow-hidden.shadow-lg')
  583. const logEntry = {
  584. url: `${processUrl(inurl)}${pageNum}`,
  585. elementsFetched: divElements.length
  586. }
  587.  
  588. // 如果当前名称的日志组不存在,则创建一个新数组
  589. if (!downloadLog[name]) {
  590. downloadLog[name] = []
  591. }
  592.  
  593. // 将日志条目添加到日志数组中
  594. downloadLog[name].push(logEntry)
  595. if (divElements.length === 0) {
  596. const logEntry = {
  597. url: `${processUrl(inurl)}${pageNum}`,
  598. elementsFetched: 0, // 这里可以根据实际需求设置其他信息
  599. errorMessage: `获取第 ${pageNum} 页失败。`
  600. }
  601. if (!errorLogs[name]) {
  602. errorLogs[name] = []
  603. }
  604. errorLogs[name].push(logEntry)
  605. console.log(`获取第 ${pageNum} 页失败。`)
  606. window.addToLog(`${name}${processUrl(inurl)}${pageNum}+获取失败 数量:` + divElements.length, 'error')
  607. }
  608.  
  609.  
  610.  
  611.  
  612. divElements.forEach(div => {
  613. var imgUrl = div.querySelector('img').getAttribute('data-src')
  614.  
  615.  
  616. if (shouldReplace) {
  617. imgUrl = imgUrl.replace('cover-t.jpg', 'cover-n.jpg')
  618. }
  619. const video = {
  620. fileName: div.querySelector('a').getAttribute('alt'),
  621. imgUrl: imgUrl,
  622. videoUrl: div.querySelector('video').getAttribute('data-src'),
  623. markContent: Array.from(div.querySelectorAll('span')).map(mark => mark.textContent).join(' '),
  624. altText: div.querySelector('img').getAttribute('alt'),
  625. jumpUrl: div.querySelector('a').getAttribute('href'),
  626.  
  627. }
  628.  
  629. if (saveVideoInfo) {
  630. video.info = extractInformation(video.jumpUrl)
  631. //showBanner(`正在获取 ${video.fileName} 信息`);
  632. window.addToLog(`正在获取 ${video.fileName} 信息`, 'info')
  633. console.log()
  634. };
  635.  
  636.  
  637.  
  638. if (video.imgUrl && video.altText) {
  639. videos.push(video)
  640. if (saveImage) {
  641. window.addToLog(`保存` + video.imgUrl, 'info')
  642. pendingRequests++
  643. GM_xmlhttpRequest({
  644. method: 'GET',
  645. url: video.imgUrl,
  646. responseType: 'blob',
  647. onload: function (response) {
  648. if (response.status === 200) {
  649. if (saveImage) {
  650. if (singleFileDownload) {
  651. console.log("这是单个文件下载")
  652. imgFolder.file(`${video.fileName}.jpg`, response.response, { binary: true })
  653. } else {
  654. console.log("这是批量文件下载")
  655. allimgFolder.file(`${video.fileName}.jpg`, response.response, { binary: true })
  656. }
  657. }
  658.  
  659. pendingRequests--
  660. checkIfComplete(callback)
  661. } else {
  662. pendingRequests--
  663. checkIfComplete(callback)
  664. }
  665. }
  666. })
  667. }
  668. } else {
  669. pendingRequests--
  670. checkIfComplete(callback)
  671. }
  672. })
  673.  
  674. showModal(`获取第 ${pageNum} 页的内容完成,等待 ${delayTime} 毫秒加载第 ${pageNum + 1} 页。`)
  675. pendingRequests--
  676. checkIfComplete(callback)
  677. if (pages.length > 0) {
  678. setTimeout(() => fetchPage(pages.shift(), pages, callback), delayTime)
  679. } else {
  680.  
  681. }
  682. }
  683.  
  684. closeModal()
  685.  
  686. function downloadLogFile() {
  687.  
  688.  
  689. if (!downloadLogFileA) {
  690. console.log('日志下载已被跳过')
  691. return
  692. }
  693. if (Object.keys(errorLogs).length === 0) {
  694. // 如果错误日志为空,直接下载正常日志文件
  695. const logBlob = new Blob([JSON.stringify(downloadLog, null, 4)], { type: 'application/json' })
  696. const logUrl = URL.createObjectURL(logBlob)
  697. const logLink = document.createElement('a')
  698. logLink.href = logUrl
  699. logLink.download = 'download_log.json'
  700. logLink.click()
  701. URL.revokeObjectURL(logUrl)
  702. } else {
  703. // 创建一个JSZip实例
  704. const zip = new JSZip()
  705.  
  706. // 添加正常日志文件到压缩包
  707. const logBlob = new Blob([JSON.stringify(downloadLog, null, 4)], { type: 'application/json' })
  708. zip.file('download_log.json', logBlob)
  709.  
  710. // 添加错误日志文件到压缩包
  711. const errorLogBlob = new Blob([JSON.stringify(errorLogs, null, 4)], { type: 'application/json' })
  712. zip.file('error_log.json', errorLogBlob)
  713.  
  714. // 生成压缩包并触发下载
  715. zip.generateAsync({ type: 'blob' }).then(function (content) {
  716. const zipUrl = URL.createObjectURL(content)
  717. const link = document.createElement('a')
  718. link.href = zipUrl
  719. link.download = 'logs.zip'
  720. link.click()
  721. URL.revokeObjectURL(zipUrl)
  722. })
  723. }
  724. }
  725.  
  726.  
  727.  
  728. function sanitizeFileName(name) {
  729. return name.replace(/[\\\/:*?"<>|]/g, '_')
  730. }
  731.  
  732. function checkIfComplete(callback) {
  733. if (pendingRequests === 0) {
  734.  
  735. const additionalInfo = {
  736. timestamp: new Date().toISOString(),
  737. inurl: inurl
  738.  
  739. }
  740. if (singleFileDownload) {
  741. showModal("获取完毕,正在生成单个文件...")
  742.  
  743. finalData = {
  744. info: additionalInfo,
  745. video: videos
  746. }
  747. if (saveJson) {
  748. zip.file("data.json", JSON.stringify(finalData, null, 4))
  749. }
  750. if (savetowebdav) {
  751.  
  752. WebDAVManager.uploadFile(webdavfold, `${sanitizeFileName(name)}.json`, JSON.stringify(finalData, null, 4))
  753. }
  754.  
  755.  
  756.  
  757. const jsonIndexContent = generateJsonIndexContent(finalData)
  758. const numFiles = Object.keys(zip.files).length // 获取压缩包中文件的数量
  759.  
  760. if (numFiles === 0) {
  761. const htmlContent = jsonIndexContent // 替换为实际的HTML内容
  762. const htmlBlob = new Blob([htmlContent], { type: 'text/html' })
  763. const htmlUrl = URL.createObjectURL(htmlBlob)
  764.  
  765. const a = document.createElement('a')
  766. a.href = htmlUrl
  767. a.download = `${sanitizeFileName(name)}.html`
  768. a.click()
  769. closeModal()
  770. downloadLogFile()
  771.  
  772. if (callback) callback()
  773. } else {
  774. zip.file(`${sanitizeFileName(name)}.html`, jsonIndexContent)
  775.  
  776. // 生成并下载单个文件
  777. zip.generateAsync({ type: "blob" }, function updateCallback(metadata) {
  778. const progress = metadata.percent.toFixed(2)
  779. showModal(`压缩进度: ${progress}%`)
  780. }).then(content => {
  781. const zipUrl = URL.createObjectURL(content)
  782. const a = document.createElement('a')
  783. a.href = zipUrl
  784. a.download = `${name}.zip`
  785. a.click()
  786. URL.revokeObjectURL(zipUrl)
  787. closeModal()
  788. downloadLogFile()
  789. if (callback) callback()
  790. })
  791. }
  792.  
  793.  
  794. } else {
  795. finalData = {
  796. info: additionalInfo,
  797. video: videos
  798. }
  799. if (saveJson) {
  800. allzip.file(`${sanitizeFileName(name)}.json`, JSON.stringify(finalData, null, 4))
  801. }
  802. if (savetowebdav) {
  803. WebDAVManager.uploadFile(webdavfold, `${sanitizeFileName(name)}.json`, JSON.stringify(finalData, null, 4))
  804. }
  805.  
  806. const jsonIndexContent = generateJsonIndexContent(finalData);;
  807. allzip.file(`${sanitizeFileName(name)}.html`, jsonIndexContent)
  808. finalData = []
  809.  
  810. videos = []
  811. if (callback) callback()
  812. }
  813.  
  814. }
  815. }
  816. function downloadAllZips() {
  817. if (singleFileDownload === false) {
  818. showModal("获取完毕,正在生成压缩文件...")
  819.  
  820. const numFiles = Object.keys(allzip.files).length // 获取压缩包中文件的数量
  821.  
  822. if (numFiles === 1) {
  823. // 如果压缩包中只有一个文件,直接处理该文件
  824. const fileName = Object.keys(allzip.files)[0] // 获取唯一的文件名
  825. const file = allzip.files[fileName]
  826.  
  827. // 根据文件类型获取文件内容
  828. file.async('blob').then(content => {
  829. // 创建一个Blob对象,并下载
  830. const blob = new Blob([content])
  831. const url = URL.createObjectURL(blob)
  832. const a = document.createElement('a')
  833. a.href = url
  834. a.download = fileName
  835. document.body.appendChild(a) // 添加到文档中以确保点击有效
  836. a.click()
  837. document.body.removeChild(a) // 下载完成后移除元素
  838. URL.revokeObjectURL(url)
  839. closeModal()
  840. }).catch(error => {
  841. console.error('Error fetching file content:', error)
  842. closeModal()
  843. })
  844. downloadLogFile()
  845. return // 结束函数执行,不生成压缩包
  846. }
  847.  
  848.  
  849.  
  850. allzip.generateAsync({ type: "blob" }, function updateCallback(metadata) {
  851. const progress = metadata.percent.toFixed(2)
  852. showModal(`压缩进度: ${progress}%`)
  853. }).then(content => {
  854. const zipUrl = URL.createObjectURL(content)
  855. const a = document.createElement('a')
  856. a.href = zipUrl
  857. a.download = `批量备份${urls.length}个片单.zip`
  858. a.click()
  859. URL.revokeObjectURL(zipUrl)
  860. closeModal()
  861. downloadLogFile()
  862. if (callback) callback()
  863. })
  864. // 如果 singleFileDownload 等于假,则执行这里的代码
  865. }
  866.  
  867.  
  868. }
  869.  
  870. function showBanner(text) {
  871. // 查找现有的横幅元素
  872. var existingBanner = document.querySelector('.banner')
  873.  
  874. if (existingBanner) {
  875. // 如果横幅已经存在,直接更新文本内容
  876. existingBanner.textContent = text
  877. } else {
  878. // 如果横幅不存在,创建一个新的横幅
  879. var banner = document.createElement('div')
  880. banner.className = 'banner' // 添加一个类名以便识别
  881. banner.style.position = 'fixed'
  882. banner.style.bottom = '20px' // 距离底部的距离
  883. banner.style.left = '20px' // 距离左侧的距离
  884. banner.style.width = 'auto' // 根据文本自动调整宽度
  885. banner.style.backgroundColor = 'rgba(255, 255, 255, 0.9)'
  886. banner.style.color = '#000' // 黑色文本
  887. banner.style.textAlign = 'center'
  888. banner.style.padding = '20px'
  889. banner.style.borderRadius = '8px'
  890. banner.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.1)'
  891. banner.style.zIndex = '9999'
  892. banner.textContent = text // 将传入的文本设置为横幅内容
  893.  
  894. document.body.appendChild(banner) // 将横幅添加到文档的末尾
  895.  
  896. // 3秒后移除横幅提示
  897. setTimeout(function () {
  898. banner.remove()
  899. }, 3000)
  900. }
  901. }
  902.  
  903.  
  904.  
  905. // 创建或更新模态窗口
  906. function showModal(message, autoCloseDelay = 0) {
  907. // 如果模态窗口不存在,则创建新的模态窗口
  908. if (!modalContainer) {
  909. modalContainer = document.createElement('div')
  910. modalContainer.className = 'modal-container'
  911. modalContainer.style.cssText = `
  912. position: fixed;
  913. top: 50%;
  914. left: 50%;
  915. transform: translate(-50%, -50%);
  916. background-color: rgba(255, 255, 255, 0.9);
  917. border-radius: 8px;
  918. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
  919. z-index: 9999;
  920. padding: 20px;
  921. `
  922. document.body.appendChild(modalContainer)
  923. }
  924.  
  925. // 更新模态窗口的内容
  926. modalContainer.textContent = message
  927.  
  928. // 自动关闭模态窗口
  929. if (autoCloseDelay > 0) {
  930. setTimeout(closeModal, autoCloseDelay)
  931. }
  932. }
  933.  
  934. // 关闭模态窗口
  935. function closeModal() {
  936. // 如果模态窗口存在,则从 DOM 中移除
  937. if (modalContainer) {
  938. document.body.removeChild(modalContainer)
  939. modalContainer = null // 将变量重置为 null,以便下次创建新的模态窗口
  940. }
  941. }
  942.  
  943. // 创建JSONindex
  944.  
  945. function createReportUI(data, itemsPerPage) {
  946. temporaryData = data
  947. // 创建全屏遮罩层
  948. const overlay = document.createElement('div')
  949. overlay.className = 'overlay'
  950. overlay.style.cssText = `
  951. position: fixed;
  952. top: 0;
  953. left: 0;
  954. width: 100%;
  955. height: 100%;
  956. background-color: rgba(0, 0, 0, 1); /* 全黑不透明背景 */
  957. z-index: 9999; /* 确保遮罩层位于所有内容之上 */
  958. `
  959. // document.body.appendChild(overlay);
  960.  
  961. const modalContainer = document.createElement('div')
  962. modalContainer.className = 'modal-container'
  963. modalContainer.style.cssText = `
  964. position: fixed;
  965. top: 50%;
  966. left: 50%;
  967. transform: translate(-50%, -50%);
  968. background-color: rgba(255, 255, 255, 1);
  969. border-radius: 8px;
  970. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  971. z-index: 10000; /* 确保弹出框位于遮罩层之上 */
  972. padding: 20px;
  973. width: 80%;
  974. max-width: 800px;
  975. `
  976. const title = document.createElement('h2')
  977. title.textContent = `当前共有片单数量: ${temporaryData.length}`
  978.  
  979. title.style.textAlign = 'center'
  980. modalContainer.appendChild(title)
  981.  
  982. const closeButton = document.createElement('button')
  983. closeButton.textContent = '×'
  984. closeButton.style.position = 'absolute'
  985. closeButton.style.top = '10px'
  986. closeButton.style.right = '10px'
  987. closeButton.style.backgroundColor = 'transparent'
  988. closeButton.style.border = 'none'
  989. closeButton.style.fontSize = '24px'
  990. closeButton.style.cursor = 'pointer'
  991. modalContainer.appendChild(closeButton)
  992.  
  993. closeButton.addEventListener('click', () => {
  994.  
  995. // document.body.removeChild(overlay); // 移除遮罩层
  996. document.body.removeChild(modalContainer) // 移除模态框
  997. })
  998.  
  999. const tableContainer = document.createElement('div')
  1000. tableContainer.style.cssText = `
  1001. max-height: 60vh;
  1002. overflow-y: auto;
  1003. `
  1004. modalContainer.appendChild(tableContainer)
  1005.  
  1006. const table = document.createElement('table')
  1007. table.style.width = '100%'
  1008. table.style.borderCollapse = 'collapse'
  1009. table.style.fontSize = '16px'
  1010. tableContainer.appendChild(table)
  1011.  
  1012. const thead = document.createElement('thead')
  1013. table.appendChild(thead)
  1014.  
  1015. const headerRow = document.createElement('tr')
  1016. thead.appendChild(headerRow)
  1017.  
  1018. const checkboxHeader = document.createElement('th')
  1019. checkboxHeader.textContent = '选择'
  1020. checkboxHeader.style.textAlign = 'center'
  1021. checkboxHeader.style.padding = '10px'
  1022. headerRow.appendChild(checkboxHeader)
  1023.  
  1024. const nameHeader = document.createElement('th')
  1025. nameHeader.textContent = '片单'
  1026. nameHeader.style.padding = '10px'
  1027. nameHeader.style.width = '40%'
  1028. headerRow.appendChild(nameHeader)
  1029.  
  1030. const urlHeader = document.createElement('th')
  1031. urlHeader.textContent = '地址'
  1032. urlHeader.style.padding = '10px'
  1033. urlHeader.style.width = '40%'
  1034. headerRow.appendChild(urlHeader)
  1035.  
  1036. const tbody = document.createElement('tbody')
  1037. table.appendChild(tbody)
  1038.  
  1039. let currentPage = 1
  1040. const totalItems = data.length
  1041. const totalPages = Math.ceil(totalItems / itemsPerPage)
  1042.  
  1043. function generateTableData(page) {
  1044. tbody.innerHTML = ''
  1045.  
  1046. const startIndex = (page - 1) * itemsPerPage
  1047. const endIndex = startIndex + itemsPerPage
  1048.  
  1049. for (let i = startIndex; i < endIndex && i < data.length; i++) {
  1050. const row = document.createElement('tr')
  1051. tbody.appendChild(row)
  1052. // 序号列
  1053. const indexCell = document.createElement('td')
  1054. indexCell.textContent = i + 1 // 显示序号,从1开始
  1055. indexCell.style.textAlign = 'center'
  1056. indexCell.style.padding = '5px'
  1057. row.appendChild(indexCell)
  1058. const checkboxCell = document.createElement('td')
  1059. checkboxCell.style.textAlign = 'center'
  1060. checkboxCell.style.padding = '5px'
  1061. const checkbox = document.createElement('input')
  1062. checkbox.type = 'checkbox'
  1063. checkbox.id = `checkbox_${i}`
  1064. checkbox.value = i
  1065. checkboxCell.appendChild(checkbox)
  1066. row.appendChild(checkboxCell)
  1067.  
  1068. const nameCell = document.createElement('td')
  1069. nameCell.textContent = data[i].name
  1070. nameCell.style.padding = '10px'
  1071. nameCell.style.borderBottom = '1px solid #ddd'
  1072. row.appendChild(nameCell)
  1073.  
  1074. const urlCell = document.createElement('td')
  1075. const fullUrl = "https://missav.com/playlists/" + data[i].key
  1076. const link = document.createElement('a')
  1077. link.textContent = fullUrl
  1078. link.href = fullUrl
  1079. link.target = '_blank' // 在新标签页中打开链接
  1080. urlCell.appendChild(link)
  1081. urlCell.style.padding = '10px'
  1082. urlCell.style.borderBottom = '1px solid #ddd'
  1083. row.appendChild(urlCell)
  1084. }
  1085. }
  1086.  
  1087. generateTableData(currentPage)
  1088.  
  1089. const paginationContainer = document.createElement('div')
  1090. paginationContainer.style.marginTop = '20px'
  1091. paginationContainer.style.textAlign = 'center'
  1092. modalContainer.appendChild(paginationContainer)
  1093.  
  1094. const prevButton = document.createElement('button')
  1095. prevButton.textContent = '上一页'
  1096. prevButton.style.marginRight = '10px'
  1097. prevButton.disabled = true
  1098.  
  1099. const pageIndicator = document.createElement('span')
  1100. pageIndicator.style.marginRight = '10px'
  1101. updatePageIndicator()
  1102. paginationContainer.appendChild(pageIndicator)
  1103.  
  1104. const nextButton = document.createElement('button')
  1105. nextButton.textContent = '下一页'
  1106. nextButton.style.marginLeft = '10px'
  1107. if (totalPages <= 1) {
  1108. nextButton.disabled = true
  1109. }
  1110.  
  1111. prevButton.addEventListener('click', () => {
  1112. currentPage--
  1113. generateTableData(currentPage)
  1114. updatePaginationButtons()
  1115. updatePageIndicator()
  1116. })
  1117.  
  1118. nextButton.addEventListener('click', () => {
  1119. currentPage++
  1120. generateTableData(currentPage)
  1121. updatePaginationButtons()
  1122. updatePageIndicator()
  1123. })
  1124.  
  1125. function updatePaginationButtons() {
  1126. prevButton.disabled = currentPage === 1
  1127. nextButton.disabled = currentPage === totalPages
  1128. }
  1129.  
  1130. function updatePageIndicator() {
  1131. pageIndicator.textContent = `第 ${currentPage}/${totalPages} 页`
  1132. }
  1133. const selectAllButton = document.createElement('button')
  1134. selectAllButton.textContent = '全部选择'
  1135. selectAllButton.style.marginRight = '10px'
  1136. selectAllButton.style.marginTop = '20px'
  1137. selectAllButton.style.padding = '10px 20px'
  1138. selectAllButton.style.fontSize = '16px'
  1139. selectAllButton.style.backgroundColor = '#007bff'
  1140. selectAllButton.style.color = '#fff'
  1141. selectAllButton.style.border = 'none'
  1142. selectAllButton.style.borderRadius = '5px'
  1143. selectAllButton.style.cursor = 'pointer'
  1144. selectAllButton.style.float = 'left'
  1145. modalContainer.appendChild(selectAllButton)
  1146.  
  1147. let selectAll = true
  1148.  
  1149. selectAllButton.addEventListener('click', () => {
  1150. const checkboxes = document.querySelectorAll('input[type="checkbox"]')
  1151. checkboxes.forEach(checkbox => {
  1152. checkbox.checked = selectAll
  1153. })
  1154.  
  1155. if (selectAll) {
  1156. selectAllButton.textContent = '取消选择'
  1157. } else {
  1158. selectAllButton.textContent = '全部选择'
  1159. }
  1160.  
  1161. selectAll = !selectAll
  1162. })
  1163. //
  1164.  
  1165. const confirmButton = document.createElement('button')
  1166. confirmButton.textContent = '确认选择'
  1167. confirmButton.style.marginTop = '20px'
  1168. confirmButton.style.padding = '10px 20px'
  1169. confirmButton.style.fontSize = '16px'
  1170. confirmButton.style.backgroundColor = '#007bff'
  1171. confirmButton.style.color = '#fff'
  1172. confirmButton.style.border = 'none'
  1173. confirmButton.style.borderRadius = '5px'
  1174. confirmButton.style.cursor = 'pointer'
  1175. confirmButton.style.float = 'right'
  1176. modalContainer.appendChild(confirmButton)
  1177.  
  1178. document.body.appendChild(modalContainer)
  1179.  
  1180. confirmButton.addEventListener('click', () => {
  1181. const checkboxes = document.querySelectorAll('input[type="checkbox"]')
  1182. let anyCheckboxChecked = false
  1183. checkboxes.forEach(checkbox => {
  1184. if (checkbox.checked) {
  1185.  
  1186. const index = parseInt(checkbox.value, 10)
  1187. if (index >= 0 && index < temporaryData.length) {
  1188. // 检查 temporaryData[index] 是否为 undefined 或 null
  1189. if (temporaryData[index]) {
  1190. // 将选中的名称和URL推送到全局变量
  1191. names.push(temporaryData[index].name)
  1192. urls.push("https://missav.com/playlists/" + temporaryData[index].key)
  1193. anyCheckboxChecked = true
  1194. } else {
  1195. console.error(`temporaryData[${index}] is undefined or null.`)
  1196. }
  1197. } else {
  1198. console.error(`Index ${index} is out of bounds for temporaryData.`)
  1199. }
  1200. }
  1201.  
  1202. })
  1203.  
  1204. document.body.removeChild(modalContainer)
  1205. // document.body.removeChild(overlay);
  1206. if (anyCheckboxChecked) {
  1207.  
  1208. processUrls()
  1209. window.showLogContainer()
  1210. }
  1211.  
  1212. })
  1213. }
  1214. function ini() {
  1215. delayTime = GM_getValue('delayTime', 1000) // 从GM存储中读取延迟时间
  1216. shouldReplace = GM_getValue('shouldReplace', false) // 从GM存储中读取状态
  1217. saveJson = GM_getValue('saveJson', false) // 从GM存储中读取状态
  1218. useDefaultTitle = GM_getValue('useDefaultTitle', true)
  1219. pageCount = GM_getValue('pageCount', true)
  1220. saveVideoInfo = GM_getValue('saveVideoInfo', false)
  1221. saveImage = GM_getValue('saveImage', true)
  1222. downloadLogFileA = GM_getValue('downloadLogFileA', false)
  1223. savetowebdav = GM_getValue('savetowebdav', false)
  1224. webdavUrl = GM_getValue('webdavUrl', '')
  1225. webdavUsername = GM_getValue('webdavUsername', '')
  1226. webdavPassword = GM_getValue('webdavPassword', '')
  1227.  
  1228. }
  1229. // 创建设置界面
  1230. function createControl(tagName, attributes = {}, styles = {}, parent = document.body) {
  1231. const element = document.createElement(tagName)
  1232.  
  1233. // 设置属性
  1234. for (const key in attributes) {
  1235. element[key] = attributes[key]
  1236. }
  1237.  
  1238. // 设置样式
  1239. Object.assign(element.style, styles)
  1240.  
  1241. // 添加到父元素
  1242. if (parent) {
  1243. parent.appendChild(element)
  1244. }
  1245.  
  1246. return element
  1247. }
  1248.  
  1249. function createSettingsUI() {
  1250. const modalContainer = createControl('div', {
  1251. className: 'settings-modal'
  1252. }, {
  1253. position: 'fixed',
  1254. top: '50%',
  1255. left: '50%',
  1256. transform: 'translate(-50%, -50%)',
  1257. backgroundColor: 'rgba(255, 255, 255, 1)',
  1258. borderRadius: '8px',
  1259. boxShadow: '0 4px 8px rgba(0, 0, 0, 0.2)',
  1260. zIndex: '9999',
  1261. padding: '20px',
  1262. width: '400px',
  1263. maxWidth: '80%'
  1264. })
  1265.  
  1266. const title = createControl('h2', {
  1267. textContent: '设置'
  1268. }, {
  1269. textAlign: 'center'
  1270. })
  1271. modalContainer.appendChild(title)
  1272.  
  1273. // 创建控件并添加到模态框
  1274. const controls = [
  1275. {
  1276. type: 'checkbox',
  1277. id: 'saveImageCheckbox',
  1278. label: '下载图片',
  1279. checked: GM_getValue('saveImage', true),
  1280. onchange: function () { GM_setValue('saveImage', this.checked) }
  1281. },
  1282. {
  1283. type: 'checkbox',
  1284. id: 'saveJsonCheckbox',
  1285. label: '下载JSON',
  1286. checked: GM_getValue('saveJson', false),
  1287. onchange: function () { GM_setValue('saveJson', this.checked) }
  1288. },
  1289. {
  1290. type: 'checkbox',
  1291. id: 'hdImageCheckbox',
  1292. label: '下载高清大图',
  1293. checked: GM_getValue('shouldReplace', false),
  1294. onchange: function () { GM_setValue('shouldReplace', this.checked) }
  1295. },
  1296. {
  1297. type: 'checkbox',
  1298. id: 'defaultTitleCheckbox',
  1299. label: '使用网页标题名保存',
  1300. checked: GM_getValue('useDefaultTitle', true),
  1301. onchange: function () { GM_setValue('useDefaultTitle', this.checked) }
  1302. },
  1303. {
  1304. type: 'checkbox',
  1305. id: 'saveVideoInfoCheckbox',
  1306. label: '下载视频信息',
  1307. checked: GM_getValue('saveVideoInfo', false),
  1308. onchange: function () { GM_setValue('saveVideoInfo', this.checked) }
  1309. },
  1310. {
  1311. type: 'checkbox',
  1312. id: 'pageCountCheckbox',
  1313. label: '自定义抓取页数',
  1314. checked: GM_getValue('pageCount', true),
  1315. onchange: function () { GM_setValue('pageCount', this.checked) }
  1316. },
  1317. {
  1318. type: 'checkbox',
  1319. id: 'downloadLogFileA',
  1320. label: '保存下载日志',
  1321. checked: GM_getValue('downloadLogFileA', false),
  1322. onchange: function () { GM_setValue('downloadLogFileA', this.checked) }
  1323. },
  1324. {
  1325. type: 'checkbox',
  1326. id: 'savetowebdav',
  1327. label: '上传JSON到WebDav',
  1328. checked: GM_getValue('savetowebdav', false),
  1329. onchange: function () { GM_setValue('savetowebdav', this.checked) }
  1330. },
  1331. {
  1332. type: 'number',
  1333. id: 'delayInput',
  1334. label: '设置延迟(毫秒)',
  1335. value: GM_getValue('delayTime', 1000),
  1336. placeholder: '设置延迟(毫秒)',
  1337. onchange: function () { GM_setValue('delayTime', this.value) },
  1338. style: {
  1339. width: '1px' // 设置输入框宽度为100%
  1340. }
  1341. },
  1342. {
  1343. type: 'text',
  1344. id: 'webdavUrlInput',
  1345. label: 'WebDAV 网址',
  1346. value: GM_getValue('webdavUrl', ''),
  1347. placeholder: '输入WebDAV网址',
  1348. onchange: function () { GM_setValue('webdavUrl', this.value) }
  1349. },
  1350. {
  1351. type: 'text',
  1352. id: 'webdavUsernameInput',
  1353. label: 'WebDAV 账号',
  1354. value: GM_getValue('webdavUsername', ''),
  1355. placeholder: '输入WebDAV账号',
  1356. onchange: function () { GM_setValue('webdavUsername', this.value) }
  1357. },
  1358. {
  1359. type: 'text',
  1360. id: 'webdavPasswordInput',
  1361. label: 'WebDAV 密码',
  1362. value: GM_getValue('webdavPassword', ''),
  1363. placeholder: '输入WebDAV密码',
  1364. onchange: function () { GM_setValue('webdavPassword', this.value) }
  1365. }
  1366. ]
  1367.  
  1368. controls.forEach(control => {
  1369. const input = createControl('input', {
  1370. type: control.type,
  1371. id: control.id,
  1372. checked: control.checked,
  1373. value: control.value,
  1374. placeholder: control.placeholder,
  1375. onchange: control.onchange
  1376. }, {
  1377. marginRight: '10px'
  1378. })
  1379.  
  1380. const label = createControl('label', {
  1381. textContent: control.label,
  1382. htmlFor: control.id
  1383. }, {
  1384. fontSize: '14px',
  1385. marginLeft: '5px'
  1386. })
  1387.  
  1388. modalContainer.appendChild(input)
  1389. modalContainer.appendChild(label)
  1390. modalContainer.appendChild(createControl('br'))
  1391. })
  1392.  
  1393. // 关闭按钮
  1394. const closeButton = createControl('button', {
  1395. textContent: '关闭',
  1396. onclick: () => {
  1397. ini()
  1398. WebDAVManager.updateConfig(webdavUrl, webdavUsername, webdavPassword)
  1399. document.body.removeChild(modalContainer)
  1400. }
  1401. }, {
  1402. marginTop: '10px',
  1403. padding: '10px 20px',
  1404. fontSize: '16px',
  1405. backgroundColor: '#007bff',
  1406. color: '#fff',
  1407. border: 'none',
  1408. borderRadius: '5px',
  1409. cursor: 'pointer',
  1410. float: 'right'
  1411. })
  1412.  
  1413. modalContainer.appendChild(closeButton)
  1414.  
  1415. // 将模态框添加到页面
  1416. document.body.appendChild(modalContainer)
  1417.  
  1418. // 添加移动端样式
  1419. const mediaQuery = window.matchMedia('(max-width: 600px)')
  1420. if (mediaQuery.matches) {
  1421. modalContainer.style.width = '90%'
  1422. modalContainer.style.maxWidth = '90%'
  1423. modalContainer.style.padding = '10px'
  1424. }
  1425. }
  1426.  
  1427. // 在页面加载时调用设置界面创建函数
  1428.  
  1429.  
  1430. // 调用示例
  1431.  
  1432.  
  1433. const WebDAVManager = (function () {
  1434. // WebDAV 配置
  1435. let url = webdavUrl
  1436. let username = webdavUsername
  1437. let password = webdavPassword
  1438.  
  1439.  
  1440. // 通用 GM_xmlhttpRequest 封装函数
  1441. function GM_xhr({ path = '/', method, success, fail, headers = {}, data, ...config }) {
  1442.  
  1443. return new Promise(resolve => {
  1444. GM_xmlhttpRequest({
  1445. url: url + path,
  1446. method,
  1447. ...config,
  1448. headers: {
  1449. 'Connection': 'Keep-Alive', // 保持连接
  1450. 'User-Agent': 'Mozilla/5.0', // 用户代理
  1451. 'Authorization': 'Basic ' + btoa(username + ':' + password), // 基本认证
  1452. ...headers
  1453. },
  1454. data,
  1455. onload: xhr => {
  1456. if (xhr.status >= 200 && xhr.status < 300) {
  1457. if (success) success(xhr)
  1458. } else {
  1459. if (fail) fail(xhr)
  1460. }
  1461. resolve(xhr)
  1462. },
  1463. onerror: xhr => {
  1464. if (fail) fail(xhr)
  1465. resolve(xhr)
  1466. }
  1467. })
  1468. })
  1469. }
  1470. //登录
  1471. async function login() {
  1472. let retryCount = 2 // 设置重试次数为2次
  1473.  
  1474. while (retryCount > 0) {
  1475. // 构建登录请求
  1476. const LOGIN = {
  1477. method: 'PROPFIND', // 使用 PROPFIND 方法检查根目录
  1478. path: retryCount === 2 ? '/' : '', // 根据重试次数设置 path
  1479. headers: {
  1480. 'Depth': '1',
  1481. 'Authorization': 'Basic ' + btoa(username + ':' + password),
  1482. 'Connection': 'Keep-Alive', // 保持连接
  1483. 'User-Agent': 'Mozilla/5.0' // 用户代理
  1484. }
  1485. }
  1486.  
  1487. // 发起登录请求
  1488. const loginResponse = await GM_xhr(LOGIN)
  1489.  
  1490. // 判断登录结果
  1491. if (loginResponse.status === 207) {
  1492. console.log('登录成功')
  1493. // 登录成功后,可以执行其他操作
  1494. showModal("Webdav登录成功!", 1000)
  1495. return true // 返回登录成功标志
  1496. } else {
  1497. console.error('登录失败:', loginResponse.status)
  1498. if (retryCount === 1) {
  1499. showModal("Webdav登录失败!" + loginResponse.status, 1000)
  1500. return false // 返回登录失败标志
  1501. } else {
  1502. retryCount-- // 减少重试次数
  1503. }
  1504. }
  1505. }
  1506.  
  1507. // 如果重试次数用尽仍未登录成功,执行其他操作(可根据实际情况添加代码)
  1508. console.error('重试次数用尽,登录失败')
  1509. showModal("Webdav登录失败!重试次数用尽", 1000)
  1510. return false
  1511. }
  1512.  
  1513. //刷新
  1514. function updateConfig(newUrl, newUsername, newPassword) {
  1515. if (newUrl && newUsername && newPassword) {
  1516. url = newUrl
  1517. username = newUsername
  1518. password = newPassword
  1519. // 在这里调用登录函数
  1520. login()
  1521. } else {
  1522. console.error('WebDAV 配置信息不完整')
  1523. }
  1524. }
  1525. // 获取 WebDAV 中指定路径下的所有文件和文件夹
  1526. async function listFilesAndFolders(folderName) {
  1527. const path = folderName.endsWith('/') ? folderName : folderName + '/'
  1528. const PROPFIND = {
  1529. method: 'PROPFIND',
  1530. headers: {
  1531. 'Depth': '1'
  1532. },
  1533. success: (xhr) => {
  1534. const parser = new DOMParser()
  1535. const xmlDoc = parser.parseFromString(xhr.responseText, 'text/xml')
  1536. const responses = xmlDoc.getElementsByTagNameNS('DAV:', 'response')
  1537.  
  1538. let fileCount = 0
  1539. //let fileList = `<div class="custom-hr"></div>`;
  1540. let fileList = ""
  1541.  
  1542. for (let i = 0; i < responses.length; i++) {
  1543. const href = decodeURIComponent(responses[i].getElementsByTagNameNS('DAV:', 'href')[0].textContent.trim())
  1544. const displayName = href.substring(href.lastIndexOf('/') + 1)
  1545. const isCollection = responses[i].getElementsByTagNameNS('DAV:', 'collection').length > 0
  1546.  
  1547. if (!isCollection) {
  1548. fileList += `
  1549. <li class="file-item">
  1550. <input type="checkbox" name="fileCheckbox" class="file-checkbox">
  1551. <span>${fileCount + 1}.</span> <!-- 文件序号 -->
  1552. <a href="#" data-display-name="${displayName}" onclick="WebDAVManager.downloadAndDisplayFile('${folderName}', '${displayName}')" class="file-link">${displayName}</a> <!-- 文件名链接 -->
  1553. <div class="file-actions">
  1554. <a href="#" onclick="WebDAVManager.renameFile('${folderName}', '${displayName}')" class="file-action">更名</a>
  1555. <a href="#" onclick="WebDAVManager.deleteFile('${folderName}', '${displayName}')" class="file-action">删除</a>
  1556. <a href="#" onclick="WebDAVManager.downloadFile('${folderName}', '${displayName}')" class="file-action">下载</a>
  1557. </div>
  1558. <div style="clear: both;"></div> <!-- 清除浮动 -->
  1559. </li>
  1560. `
  1561. fileCount++
  1562. }
  1563. }
  1564. /*fileList += `
  1565. <div class="custom-hr"></div>
  1566. <p>文件数量: ${fileCount}</p>
  1567. </ul>
  1568. `;*/
  1569. fileList += '</ul>'
  1570.  
  1571. showDialog(`
  1572. <span style="font-size: 24px;">文件夹 ${folderName}</span><br>
  1573. <span style="color: green;"><span id="fileCountLabel">文件数量:</span> <span class="file-count">${fileCount}</span></span>
  1574. <div class="custom-hr"></div>
  1575. `, fileList)
  1576.  
  1577. },
  1578. fail: (xhr) => {
  1579. alert('获取文件列表失败:' + xhr.status)
  1580. }
  1581. }
  1582.  
  1583. await GM_xhr({ ...PROPFIND, path })
  1584. }
  1585.  
  1586. // 下载文件函数
  1587.  
  1588. // 在 WebDAV 中创建新文件夹
  1589. async function createFolder(folderName) {
  1590. const MKCOL = {
  1591. method: 'MKCOL',
  1592. path: folderName.endsWith('/') ? folderName : folderName + '/',
  1593. success: () => {
  1594. alert('文件夹创建成功')
  1595. },
  1596. fail: (xhr) => {
  1597. if (xhr.status === 409) {
  1598. // alert('冲突:文件夹可能已经存在或路径不正确');
  1599. } else {
  1600. //alert('创建文件夹失败:' + xhr.status);
  1601. }
  1602. }
  1603. }
  1604.  
  1605. await GM_xhr({ ...MKCOL })
  1606. }
  1607.  
  1608. // 上传文件到 WebDAV
  1609.  
  1610. async function uploadFile(folderName, fileName, fileContent) {
  1611. // 检查文件是否已存在
  1612. const HEAD = {
  1613. method: 'HEAD',
  1614. path: folderName + '/' + fileName,
  1615. success: () => {
  1616. //alert('文件已存在,无需重复上传');
  1617. window.addToLog(fileName + '文件存在', 'info')
  1618. // 可以在这里执行文件已存在时的逻辑,比如显示提示信息或执行其他操作
  1619. },
  1620. fail: async (xhr) => {
  1621. if (xhr.status === 404) {
  1622. // 文件不存在,执行上传操作
  1623. const PUT = {
  1624. method: 'PUT',
  1625. path: folderName + '/' + fileName,
  1626. data: fileContent,
  1627. success: () => {
  1628.  
  1629. window.addToLog(fileName + '文件上传成功', 'info')
  1630. },
  1631. fail: (xhr) => {
  1632. window.addToLog(fileName + '文件上传失败', 'error')
  1633. }
  1634. }
  1635.  
  1636. await GM_xhr({ ...PUT })
  1637. } else {
  1638. window.addToLog(fileName + '文件检查上传失败', 'error')
  1639. }
  1640. }
  1641. }
  1642.  
  1643. await GM_xhr({ ...HEAD })
  1644. }
  1645.  
  1646. // 重命名文件
  1647. async function renameFile(folderName, oldFileName) {
  1648. // 弹出对话框让用户输入新文件名
  1649. const newFileName = prompt(`请输入 ${oldFileName} 的新文件名:`)
  1650. if (!newFileName) {
  1651. alert('未输入新文件名,操作已取消。')
  1652. return
  1653. }
  1654. const encodedOldFileName = encodeURIComponent(oldFileName) // 对旧文件名进行编码
  1655.  
  1656. const encodedNewFileName = encodeURIComponent(newFileName + '.json') // 对新文件名进行编码
  1657. const sourcePath = folderName + '/' + encodedOldFileName
  1658. const destinationPath = folderName + '/' + encodedNewFileName
  1659.  
  1660. const MOVE = {
  1661. method: 'MOVE',
  1662. path: sourcePath,
  1663. headers: {
  1664. 'Destination': url + destinationPath,
  1665. 'Overwrite': 'T' // 允许覆盖目标文件
  1666. },
  1667. success: () => {
  1668. alert(`文件 ${oldFileName} 已成功重命名为 ${newFileName}`)
  1669. listFilesAndFolders(folderName) // 刷新文件列表
  1670. },
  1671. fail: (xhr) => {
  1672. if (xhr.status === 409) {
  1673. alert('冲突:文件名可能已存在或路径不正确')
  1674. } else {
  1675. alert('重命名文件失败:' + xhr.status)
  1676. }
  1677. }
  1678. }
  1679.  
  1680. await GM_xhr({ ...MOVE })
  1681. }
  1682.  
  1683.  
  1684. // 删除文件
  1685. async function deleteFile(folderName, fileName) {
  1686. let confirmdelete = false
  1687. if (deleteSelected) {
  1688. confirmdelete = true
  1689. } else {
  1690. confirmdelete = confirm(`确定要删除文件 ${fileName} 吗?`)
  1691. }
  1692.  
  1693. if (confirmdelete) {
  1694. try {
  1695. await deleteFileFromServer(folderName, fileName)
  1696. const fileItem = document.querySelector(`a[onclick*="${fileName}"]`).closest('li')
  1697. const checkbox = fileItem.querySelector('input[type="checkbox"]')
  1698. if (checkbox && checkbox.checked) {
  1699. checkbox.checked = false // 清除选中状态
  1700. checkbox.disabled = true // 禁止操作
  1701. }
  1702. markItemDeleted(fileItem)
  1703. } catch (error) {
  1704. alert('删除文件失败:' + error.message)
  1705. }
  1706. }
  1707. }
  1708. function markItemDeleted(fileItem) {
  1709. // 将文件名链接标记为已删除
  1710. const fileNameLink = fileItem.querySelector('a')
  1711. fileNameLink.style.textDecoration = 'line-through'
  1712. fileNameLink.removeAttribute('onclick')
  1713. fileNameLink.style.pointerEvents = 'none' // 不可点击
  1714. fileNameLink.style.color = 'gray' // 设置为灰色
  1715.  
  1716. // 将后续按钮标记为已删除
  1717. const actionButtons = fileItem.querySelectorAll('div > a')
  1718. actionButtons.forEach(button => {
  1719. button.style.textDecoration = 'line-through'
  1720. button.removeAttribute('onclick')
  1721. button.style.pointerEvents = 'none' // 不可点击
  1722. button.style.color = 'gray' // 设置为灰色
  1723. })
  1724. }
  1725. // 实际删除文件的函数
  1726. async function deleteFileFromServer(folderName, fileName) {
  1727. const DELETE = {
  1728. method: 'DELETE',
  1729. path: folderName + '/' + fileName,
  1730. }
  1731.  
  1732. return new Promise((resolve, reject) => {
  1733. GM_xhr({ ...DELETE, success: resolve, fail: reject })
  1734. })
  1735. }
  1736.  
  1737. // 检查文件夹是否存在
  1738. async function checkFolderExists(folderName) {
  1739. const HEAD = {
  1740. method: 'HEAD',
  1741. path: folderName.endsWith('/') ? folderName : folderName + '/',
  1742. success: (xhr) => {
  1743. if (xhr.status === 200) {
  1744. // alert('文件夹存在');
  1745. } else if (xhr.status === 404) {
  1746. alert('文件夹不存在')
  1747. } else {
  1748. alert('检查文件夹状态失败:' + xhr.status)
  1749. }
  1750. },
  1751. fail: (xhr) => {
  1752. alert('检查文件夹失败:' + xhr.status)
  1753. }
  1754. }
  1755.  
  1756. await GM_xhr({ ...HEAD })
  1757. }
  1758.  
  1759. // 下载并展示文件
  1760. async function downloadFile(folderName, fileName, zip = null) {
  1761. return new Promise((resolve, reject) => {
  1762. // 发起 GET 请求下载文件内容
  1763. const GET = {
  1764. method: 'GET',
  1765. path: folderName + '/' + fileName,
  1766. success: (xhr) => {
  1767. const fileContent = xhr.responseText
  1768. const jsonData = JSON.parse(fileContent)
  1769. const content = generateJsonIndexContent(jsonData)
  1770.  
  1771. if (zip) {
  1772. // 如果传入了压缩包实例,则将文件内容添加到压缩包中
  1773. const sanitizedFileName = sanitizeFileName(fileName.replace('.json', '')) + '.html'
  1774. zip.file(sanitizedFileName, content)
  1775. resolve()
  1776. } else {
  1777. // 否则直接下载文件
  1778. const blob = new Blob([content], { type: 'text/html' })
  1779. const htmlUrl = URL.createObjectURL(blob)
  1780. const a = document.createElement('a')
  1781. a.href = htmlUrl
  1782. a.download = sanitizeFileName(fileName.replace('.json', '')) + '.html'
  1783. a.click()
  1784. resolve()
  1785. }
  1786. },
  1787. fail: (xhr) => {
  1788. reject(new Error('下载文件失败:' + xhr.status))
  1789. }
  1790. }
  1791.  
  1792. GM_xhr({ ...GET })
  1793. })
  1794. }
  1795.  
  1796.  
  1797.  
  1798. async function downloadAndDisplayFile(folderName, fileName) {
  1799. const GET = {
  1800. method: 'GET',
  1801. path: folderName + '/' + fileName,
  1802. success: (xhr) => {
  1803. try {
  1804. const fileContent = xhr.responseText
  1805. const jsonData = JSON.parse(fileContent)
  1806. const content = generateJsonIndexContent(jsonData)
  1807.  
  1808. // 打开一个新的浏览器标签页显示内容
  1809. const dataUrl = 'data:text/html;charset=utf-8,' + encodeURIComponent(content)
  1810. GM_openInTab(dataUrl)
  1811. } catch (e) {
  1812. alert('解析 JSON 文件失败:' + e.message)
  1813. }
  1814. },
  1815. fail: (xhr) => {
  1816. alert('下载文件失败:' + xhr.status)
  1817. }
  1818. }
  1819.  
  1820. await GM_xhr({ ...GET })
  1821. }
  1822. //我是你爹啊
  1823. // 显示对话框
  1824. function showDialog(title, fileList, folderName) {
  1825. // 添加CSS样式
  1826. const style = document.createElement('style')
  1827. style.textContent = `
  1828. .overlay {
  1829. position: fixed;
  1830. top: 0;
  1831. left: 0;
  1832. width: 100%;
  1833. height: 100%;
  1834. background-color: rgba(0, 0, 0, 0.5);
  1835. z-index: 9998;
  1836. }
  1837.  
  1838. .dialog {
  1839. position: fixed;
  1840. top: 50%;
  1841. left: 50%;
  1842. transform: translate(-50%, -50%);
  1843. background: #fff;
  1844. padding: 20px;
  1845. border-radius: 10px;
  1846. width: 80%;
  1847. max-height: 80%;
  1848. overflow-y: auto;
  1849. box-shadow: 0 0 20px rgba(0, 0, 0, 0.2);
  1850. z-index: 9999;
  1851. }
  1852.  
  1853. .dialog-title {
  1854. margin-bottom: 20px;
  1855. }
  1856.  
  1857. .dialog-button {
  1858. padding: 10px 20px;
  1859. border: none;
  1860. border-radius: 5px;
  1861. cursor: pointer;
  1862. margin-bottom: 20px;
  1863. }
  1864.  
  1865. .select-button {
  1866. background-color: #007BFF;
  1867. color: #fff;
  1868. }
  1869.  
  1870. .delete-button {
  1871. background-color: #dc3545;
  1872. color: #fff;
  1873. margin-left: 10px;
  1874. }
  1875.  
  1876. .select-all-button {
  1877. background-color: #28a745;
  1878. color: #fff;
  1879. margin-left: 10px;
  1880. }
  1881.  
  1882. .download-button {
  1883. background-color: #007BFF;
  1884. color: #fff;
  1885. margin-left: 10px;
  1886. }
  1887.  
  1888. .search-button {
  1889. background-color: #6c757d;
  1890. color: #fff;
  1891. margin-left: 10px;
  1892. border: none;
  1893. border-radius: 5px;
  1894. cursor: pointer;
  1895. }
  1896. .close-x-button {
  1897. background-color: #6c757d;
  1898. color: red;
  1899. margin-left: 10px;
  1900. border: none;
  1901. border-radius: 5px;
  1902. cursor: pointer;
  1903. }
  1904.  
  1905. .search-button:hover {
  1906. background-color: #495057;
  1907. }
  1908.  
  1909. .close-button {
  1910. background-color: #007BFF;
  1911. color: #fff;
  1912. margin-top: 20px;
  1913. }
  1914.  
  1915. .file-list {
  1916. list-style-type: none;
  1917. padding: 0;
  1918. }
  1919.  
  1920. .file-action {
  1921. margin-right: 10px;
  1922. float: left;
  1923. color: blue;
  1924. cursor: pointer;
  1925. }
  1926.  
  1927. .file-actions-container {
  1928. float: right;
  1929. }
  1930.  
  1931. .clear-float {
  1932. clear: both;
  1933. }
  1934.  
  1935. .file-item {
  1936. border-bottom: 1px solid #ddd;
  1937. padding: 10px 0;
  1938. display: flex;
  1939. align-items: center;
  1940. }
  1941.  
  1942. .file-checkbox {
  1943. margin-right: 10px;
  1944. }
  1945.  
  1946. .file-link {
  1947. margin-right: 10px;
  1948. color: blue;
  1949. flex-grow: 1;
  1950. }
  1951.  
  1952. .file-actions {
  1953. display: flex;
  1954. gap: 10px;
  1955. }
  1956.  
  1957. .file-action {
  1958. color: #007BFF;
  1959. cursor: pointer;
  1960. }
  1961.  
  1962. .custom-hr {
  1963. height: 1px;
  1964. background-color: #007BFF;
  1965. margin: 20px 0;
  1966. }
  1967.  
  1968. .file-action:hover {
  1969. text-decoration: underline;
  1970. }
  1971.  
  1972. @media (max-width: 600px) {
  1973. .dialog-button {
  1974. padding: 5px 10px;
  1975. font-size: 12px;
  1976. margin-bottom: 10px;
  1977. }
  1978.  
  1979. .delete-button,
  1980. .download-button,
  1981. .select-all-button,
  1982. .close-x-button,
  1983. .search-button {
  1984. margin-left: 0;
  1985. }
  1986.  
  1987. .button-container {
  1988. right: 5px;
  1989. }
  1990. }
  1991.  
  1992. `
  1993. document.head.appendChild(style)
  1994.  
  1995. // 创建对话框
  1996. const dialog = document.createElement('div')
  1997. dialog.className = 'dialog'
  1998. // 创建遮罩
  1999.  
  2000.  
  2001. function close() {
  2002. document.body.removeChild(dialog)
  2003.  
  2004. }
  2005.  
  2006. // 标题
  2007. const titleElement = document.createElement('h1')
  2008. titleElement.innerHTML = title
  2009. titleElement.className = 'dialog-title'
  2010. dialog.appendChild(titleElement)
  2011.  
  2012.  
  2013. const buttonContainer = document.createElement('div')
  2014. const buttonConfigs = [
  2015. { text: '选择列表', className: 'dialog-button select-button', onclick: toggleSelection },
  2016. { text: '搜索列表', className: 'dialog-button search-button', onclick: toggleSearch },
  2017. { text: '关闭窗口', className: 'dialog-button close-x-button', onclick: close },
  2018. { text: '全部选中', className: 'dialog-button select-all-button', onclick: toggleSelectAll },
  2019. { text: '删除选中', className: 'dialog-button delete-button', onclick: deleteSelectedFiles },
  2020. { text: '下载选中', className: 'dialog-button download-button', onclick: downloadSelectedFiles }
  2021.  
  2022. // 可以添加更多按钮配置
  2023. ]
  2024. let count = 0 // 计数器,用于控制显示状态
  2025. buttonConfigs.forEach(config => {
  2026. const button = document.createElement('button')
  2027. button.textContent = config.text
  2028. button.className = config.className
  2029. button.onclick = config.onclick
  2030.  
  2031. if (count < 3) {
  2032. button.style.display = 'inline-block' // 前两个按钮初始可见
  2033. } else {
  2034. button.style.display = 'none' // 后面的按钮初始隐藏
  2035. }
  2036.  
  2037. buttonContainer.appendChild(button)
  2038. count++ // 每创建一个按钮,计数器加一
  2039. })
  2040. // 将按钮容器添加到对话框中
  2041. dialog.appendChild(buttonContainer)
  2042.  
  2043. // 文件列表
  2044. const fileListContainer = document.createElement('ul')
  2045. fileListContainer.className = 'file-list'
  2046. fileListContainer.innerHTML = fileList
  2047. dialog.appendChild(fileListContainer)
  2048.  
  2049. // 添加到body
  2050.  
  2051. document.body.appendChild(dialog)
  2052. let selectButtonInitialTop = 0
  2053. let selectAllButton = document.querySelector('.select-all-button')
  2054. let searchButton = document.querySelector('.search-button')
  2055. let downloadSelectedButton = document.querySelector('.download-button')
  2056. let deleteSelectedButton = document.querySelector('.delete-button')
  2057. let selectButton = document.querySelector('.select-button')
  2058. let closeXButton = document.querySelector('.close-x-button')
  2059. let scrollTimeout = 0 // 定义全局变量存储定时器
  2060. // 记录选择按钮的初始位置
  2061. if (selectButton) {
  2062. selectButtonInitialTop = selectButton.offsetTop
  2063. }
  2064.  
  2065. // 监听对话框的滚动事件
  2066. dialog.addEventListener('scroll', function () {
  2067. clearTimeout(scrollTimeout)
  2068. const buttons = [
  2069. { button: selectButton, offsetHeight: true },
  2070. { button: selectAllButton, offsetHeight: true },
  2071.  
  2072. { button: deleteSelectedButton, offsetHeight: true },
  2073. { button: downloadSelectedButton, offsetHeight: true },
  2074. { button: searchButton, offsetHeight: true },
  2075. { button: closeXButton, offsetHeight: true },
  2076.  
  2077.  
  2078. // 添加更多按钮对象,如果有的话
  2079. ]
  2080. scrollTimeout = setTimeout(() => {
  2081. if (buttons.every(buttonObj => buttonObj.button)) {
  2082. const dialogRect = dialog.getBoundingClientRect()
  2083. const fileListRect = fileListContainer.getBoundingClientRect()
  2084. const newButtonTopBase = Math.max(fileListRect.top, dialog.scrollTop)
  2085. let newButtonTop = newButtonTopBase
  2086.  
  2087. for (const { button, offsetHeight } of buttons) {
  2088. if (button) {
  2089. button.style.position = dialog.scrollTop === 0 ? 'static' : 'absolute'
  2090. button.style.top = `${newButtonTop}px`
  2091. button.style.right = '10px'
  2092. if (offsetHeight) {
  2093. newButtonTop += button.offsetHeight
  2094. }
  2095. }
  2096. }
  2097. }
  2098. }, 300) // 设置 300 毫秒的超时
  2099. })
  2100.  
  2101.  
  2102. // 获取所有复选框
  2103. const checkboxes = fileListContainer.querySelectorAll('input[type="checkbox"]')
  2104.  
  2105. // 初始隐藏复选框
  2106. checkboxes.forEach(checkbox => {
  2107. checkbox.style.display = 'none'
  2108. })
  2109. //选中全部
  2110. function toggleSelectAll() {
  2111. const checkboxes = document.querySelectorAll('input[name="fileCheckbox"]')
  2112. const allChecked = Array.from(checkboxes).every(checkbox => checkbox.checked)
  2113.  
  2114. checkboxes.forEach(checkbox => {
  2115. checkbox.checked = !allChecked
  2116. })
  2117.  
  2118. // 切换按钮文本和背景颜色
  2119. if (allChecked) {
  2120. selectAllButton.textContent = '全部选中'
  2121. selectAllButton.style.backgroundColor = '' // 恢复默认背景颜色
  2122. } else {
  2123. selectAllButton.textContent = '全部取消选中'
  2124. selectAllButton.style.backgroundColor = 'red' // 改为红色背景
  2125. }
  2126. }
  2127. //搜索
  2128. //---------------
  2129. function toggleSearch() {
  2130. if (searchButton.textContent === '搜索列表') {
  2131. // 如果当前是搜索状态,则进行搜索
  2132. const searchTerm = prompt('请输入搜索内容:') // 弹出输入框等待用户输入搜索内容
  2133. if (searchTerm !== null) { // 用户点击了确定按钮
  2134. filterFiles(searchTerm) // 调用过滤文件函数,传入搜索关键词
  2135. searchButton.textContent = '回到列表' // 将搜索按钮文本改为"Back"
  2136. }
  2137. } else {
  2138. // 如果当前是Back状态,则恢复初始文件列表
  2139. resetFileList()
  2140. }
  2141. }
  2142.  
  2143. function filterFiles(searchTerm) {
  2144. const files = Array.from(fileListContainer.children) // 获取文件列表的所有子元素(文件项)
  2145. let matchCount = 0 // 初始化匹配数量为 0
  2146.  
  2147. // 遍历文件列表,根据搜索关键词过滤显示符合条件的文件项
  2148. files.forEach(file => {
  2149. const fileName = file.querySelector('.file-link').textContent.toLowerCase() // 获取文件名并转换为小写
  2150. if (fileName.includes(searchTerm.toLowerCase())) {
  2151. file.style.display = '' // 匹配到的文件项显示
  2152. matchCount++ // 匹配数量加一
  2153. } else {
  2154. file.style.display = 'none' // 不匹配的文件项隐藏
  2155. }
  2156. })
  2157.  
  2158. // 更新文件数量标签内容
  2159. const fileCountLabel = document.getElementById('fileCountLabel')
  2160. if (fileCountLabel) {
  2161. fileCountLabel.textContent = `搜索数量: `
  2162. }
  2163.  
  2164. // 更新包裹文件数量的元素内容
  2165. const fileCountElement = document.querySelector('.file-count')
  2166. if (fileCountElement) {
  2167. fileCountElement.textContent = matchCount
  2168. }
  2169. }
  2170.  
  2171.  
  2172. // 恢复初始文件列表函数
  2173. function resetFileList() {
  2174. const files = Array.from(fileListContainer.children) // 获取文件列表的所有子元素(文件项)
  2175. files.forEach(file => {
  2176. file.style.display = '' // 显示所有文件项
  2177. })
  2178. searchButton.textContent = '搜索列表' // 恢复搜索按钮文本为"搜索"
  2179.  
  2180. // 更新文件数量标签内容为 "文件数量:"
  2181. const fileCountLabel = document.getElementById('fileCountLabel')
  2182. if (fileCountLabel) {
  2183. fileCountLabel.textContent = '文件数量:'
  2184. }
  2185.  
  2186. // 更新包裹文件数量的元素内容为当前文件数量
  2187. const fileCountElement = document.querySelector('.file-count')
  2188. if (fileCountElement) {
  2189. const currentFileCount = files.length // 获取当前文件数量
  2190. fileCountElement.textContent = currentFileCount
  2191. }
  2192. }
  2193. //搜索结束
  2194. //---------------
  2195.  
  2196. // 选择列表
  2197. function toggleSelection() {
  2198.  
  2199. checkboxes.forEach(checkbox => {
  2200. checkbox.style.display = checkbox.style.display === 'none' ? 'inline-block' : 'none'
  2201. checkbox.checked = false // 取消复选框的选中状态
  2202. })
  2203.  
  2204. // 切换显示“删除选中”和“下载选中”按钮
  2205. deleteSelectedButton.style.display = deleteSelectedButton.style.display === 'none' ? 'inline-block' : 'none'
  2206. downloadSelectedButton.style.display = downloadSelectedButton.style.display === 'none' ? 'inline-block' : 'none'
  2207. selectAllButton.style.display = selectAllButton.style.display === 'none' ? 'inline-block' : 'none'
  2208. selectButton.textContent = downloadSelectedButton.style.display === 'none' ? '选择列表' : '取消选择'
  2209. }
  2210.  
  2211. }
  2212. //选中下载
  2213. async function downloadSelectedFiles() {
  2214. // 获取所有选中的复选框
  2215. const checkboxes = document.querySelectorAll('input[name="fileCheckbox"]:checked')
  2216. if (checkboxes.length === 0) {
  2217. showModal('没有选中的文件', 2000)
  2218. return
  2219. }
  2220. // 创建一个 JSZip 实例
  2221. const zip = new JSZip()
  2222.  
  2223. try {
  2224. // 遍历所有选中的复选框
  2225. for (const checkbox of checkboxes) {
  2226. // 获取文件项信息
  2227. const fileItem = checkbox.closest('li')
  2228. const fileName = fileItem.querySelector('a[data-display-name]').dataset.displayName
  2229.  
  2230. // 调用 downloadFile 函数,将文件添加到压缩包中
  2231. await downloadFile(webdavfold, fileName, zip)
  2232. }
  2233.  
  2234. // 生成 ZIP 文件
  2235. const zipBlob = await zip.generateAsync({ type: 'blob' })
  2236.  
  2237. // 创建下载链接并触发下载
  2238. const zipUrl = URL.createObjectURL(zipBlob)
  2239. const a = document.createElement('a')
  2240. a.href = zipUrl
  2241. a.download = 'selected_files.zip'
  2242. a.click()
  2243.  
  2244. console.log('压缩包下载完成')
  2245. showModal('下载选中完成:', 2000)
  2246. } catch (error) {
  2247. console.error('下载选中文件失败:', error)
  2248. showModal('下载选中文件失败: ' + error.message, 2000)
  2249.  
  2250. }
  2251. }
  2252.  
  2253.  
  2254. //选中删除
  2255. function deleteSelectedFiles() {
  2256. // 获取所有选中的复选框
  2257. const selectedCheckboxes = document.querySelectorAll('input[name="fileCheckbox"]:checked')
  2258.  
  2259. if (selectedCheckboxes.length === 0) {
  2260. alert('没有选中文件')
  2261. return
  2262. }
  2263.  
  2264. // 弹出确认删除的提示框
  2265. const confirmDelete = confirm('确定要删除选中的文件吗?')
  2266.  
  2267. if (confirmDelete) {
  2268. deleteSelected = true
  2269. selectedCheckboxes.forEach(checkbox => {
  2270. const fileItem = checkbox.closest('li')
  2271. const deleteButton = fileItem.querySelector('a[onclick*="deleteFile"]')
  2272.  
  2273. // 调试日志
  2274. console.log('fileItem:', fileItem)
  2275. console.log('deleteButton:', deleteButton)
  2276.  
  2277. // 获取文件名
  2278. const fileName = fileItem.querySelector('a[data-display-name]').dataset.displayName
  2279.  
  2280. // 添加调试日志
  2281. console.log('Deleting file:', fileName)
  2282.  
  2283. // 触发删除按钮的点击事件
  2284. deleteButton.click()
  2285. //deleteFile(webdavfold,fileName);
  2286.  
  2287. // 将文件项标记为已删除
  2288. // markItemDeleted(fileItem);
  2289. })
  2290. deleteSelected = false
  2291. console.log('删除选中文件')
  2292. }
  2293. }
  2294.  
  2295.  
  2296. // 将 API 方法公开
  2297. return {
  2298. listFilesAndFolders,
  2299. updateConfig,
  2300. createFolder,
  2301. uploadFile,
  2302. renameFile,
  2303. deleteFile,
  2304. downloadFile,
  2305. downloadAndDisplayFile,
  2306. checkFolderExists
  2307. }
  2308. })()
  2309.  
  2310. // 将库暴露到全局作用域
  2311. unsafeWindow.WebDAVManager = WebDAVManager
  2312.  
  2313. // 示例用法:列出文件和文件夹,创建新文件夹,上传文件,删除文件
  2314. if (!url || !username || !password) {
  2315. console.error('WebDAV 配置更新失败:缺少 URL、用户名或密码')
  2316. return
  2317. }
  2318. WebDAVManager.updateConfig(webdavUrl, webdavUsername, webdavPassword)
  2319. WebDAVManager.createFolder(webdavfold)
  2320. WebDAVManager.checkFolderExists(webdavfold)
  2321.  
  2322. //WebDAVManager.uploadFile(webdavfold, 'nidie.txt', '这是 example.txt 的内容');
  2323. // WebDAVManager.deleteFile('Missav保存', 'example.txt');
  2324.  
  2325. })();
  2326.  
  2327.