fancaps 图片批量导出

批量选择 fancaps 的图片并导出图片链接

  1. // ==UserScript==
  2. // @name fancaps 图片批量导出
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  5. // @description 批量选择 fancaps 的图片并导出图片链接
  6. // @author ctrn43062
  7. // @match https://fancaps.net/anime/episodeimages.php*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=fancaps.net
  9. // @grant GM_addStyle
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. const constants = {
  14. checkboxClassName: 'fan-checkbox'
  15. }
  16.  
  17. const states = {
  18. selectedCount: 0,
  19. }
  20.  
  21. GM_addStyle(`
  22. .image-selected {
  23. position: relative;
  24. }
  25.  
  26. .image-selected .${constants.checkboxClassName} {
  27. position: absolute;
  28. top:10px;
  29. left: 10px;
  30. width: 16px;
  31. height: 16px;
  32. line-height: 16px;
  33. text-align: center;
  34. }
  35.  
  36. .side-operation {
  37. position: fixed;
  38. right: 2rem;
  39. top: 45%;
  40. font-size: 2rem;
  41. cursor: pointer;
  42. }
  43. `)
  44.  
  45. function download(blob, name) {
  46. const url = URL.createObjectURL(blob)
  47. const a = document.createElement('a')
  48. a.download = name
  49. a.href = url
  50. a.click()
  51. URL.revokeObjectURL(url)
  52. // a.remove()
  53. //document.body.appendChild(a)
  54. }
  55.  
  56. function addSelectionListener(img) {
  57. img.addEventListener('click', e => {
  58. e.preventDefault()
  59.  
  60. const p = img.parentElement
  61. p.classList.toggle('image-selected')
  62. if(p.classList.contains('image-selected')) {
  63. states.selectedCount += 1
  64.  
  65. const checkbox = document.createElement('div')
  66. checkbox.className = constants.checkboxClassName
  67. checkbox.innerText = '☑️'
  68. checkbox.setAttribute('data-id', img.href.split('/').slice(-1)[0])
  69.  
  70. p.appendChild(checkbox)
  71. } else {
  72. states.selectedCount -= 1
  73. p.removeChild(p.querySelector(`.${constants.checkboxClassName}`))
  74. }
  75.  
  76. document.querySelector('.selected-count').innerText = ` (${states.selectedCount})`
  77. })
  78. }
  79.  
  80. function addDownloadButton() {
  81. const sideEl = document.createElement('div')
  82. sideEl.className = 'side-operation'
  83. sideEl.innerHTML = `
  84. <span class="download-btn">🔽<span class="selected-count"></span></span>
  85. `
  86.  
  87. sideEl.querySelector('.download-btn').addEventListener('click', e => {
  88. const selectedImageEls = document.querySelectorAll(`.${constants.checkboxClassName}`)
  89.  
  90. if(!selectedImageEls) {
  91. return
  92. }
  93. // el.href like: https://fancaps.net/anime/picture.php?/3335084
  94. // urlslike: https://cdni.fancaps.net/file/fancaps-animeimages/3335084.jpg
  95. const oriURL = 'https://cdni.fancaps.net/file/fancaps-animeimages'
  96. const urls = Array.from(selectedImageEls).map(el => `${oriURL}/${el.getAttribute('data-id')}.jpg`)
  97. let filename = document.title.replace(/[ |]/, '_')
  98. try {
  99. // href like: https://fancaps.net/anime/episodeimages.php?4963-Yuruyuri___Happy_Go_Lily/Episode_1&page=2
  100. filename = location.href.split('?')[1].split('/') // [4963-Yuruyuri___Happy_Go_Lily, Episode_1&page=2]
  101. filename = filename[0] + '_' + filename[1].split('&')[0] // 963-Yuruyuri___Happy_Go_Lily_Episode_1
  102. } catch (e) {}
  103.  
  104. download(new Blob([urls.join('\n')], {type: 'text/plain'}), `${filename}.txt`)
  105. })
  106.  
  107. document.body.appendChild(sideEl)
  108. }
  109.  
  110. (function() {
  111. 'use strict';
  112. const contentBodyEl = document.querySelector('#contentbody')
  113.  
  114. const obs = new MutationObserver((mutationList) => {
  115. const nodes = []
  116. for(const mutation of mutationList) {
  117. let { addedNodes } = mutation
  118.  
  119. if(!addedNodes || addedNodes.length != 1 || !addedNodes[0].classList.contains('single_post_area')) {
  120. continue
  121. }
  122. const post_area = addedNodes[0]
  123. const rows = post_area.querySelectorAll('.row:has( img)')
  124. nodes.push(...(rows || []))
  125. console.log(rows)
  126. }
  127.  
  128. // nodes'length should be always eq 2
  129. nodes.forEach(node => {
  130. const imageEls = node.querySelectorAll('a')
  131. for(const img of imageEls) {
  132. addSelectionListener(img)
  133. }
  134. })
  135. })
  136.  
  137. obs.observe(contentBodyEl, { childList: true, subtree: true })
  138.  
  139. contentBodyEl.querySelectorAll('.row a:has(>.imageFade)').forEach(img => {
  140. addSelectionListener(img)
  141. })
  142.  
  143. addDownloadButton()
  144. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址