图片下载器

批量下载图片,一个可扩展的图片下载器。

  1. // ==UserScript==
  2. // @name 图片下载器
  3. // @namespace http://tampermonkey.net/
  4. // @version 3.5.1
  5. // @description 批量下载图片,一个可扩展的图片下载器。
  6. // @author Gscsd
  7. // @include *
  8. // @icon data:image/jpeg;base64,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
  9. // @grant GM_xmlhttpRequest
  10. // @grant GM_download
  11. // @grant GM_setValue
  12. // @grant GM_log
  13. // @grant GM_notification
  14. // @grant GM_registerMenuCommand
  15. // @connect *
  16. // @require https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js
  17. // @require https://cdn.bootcdn.net/ajax/libs/jszip/3.7.1/jszip.js
  18. // @run-at document-end
  19. // @noframes
  20. // @compatible Chrome
  21. // @compatible Edge
  22. // ==/UserScript==
  23. (function () {
  24. 'use strict';
  25.  
  26. function depthTest(fa, a) {
  27. let sum = 0;
  28. while (1) {
  29. if (a === fa) break;
  30. a = $(a).parent()[0];
  31. sum++;
  32.  
  33. }
  34. return sum
  35. }
  36.  
  37. function FindBrothers(a) {
  38. let par = $(a).parent()[0], sea = $(par).find('img').toArray();
  39. if (sea.length === 1) return FindBrothers(par)
  40. else {
  41. let depth = depthTest(par, getimg), sea1 = [];
  42. sea.forEach((item) => {
  43. if (depthTest(par, item) === depth) sea1.push(item)
  44. })
  45. sea = sea1;
  46. if (sea.length === 1) return FindBrothers(par);
  47. return sea
  48. }
  49. }
  50.  
  51. class TaskQueue {
  52. downloadIndex = 0;
  53. retryIndex = 0;
  54. results = [];
  55. transfer = [];
  56. error = [];
  57. //0未下载 1下载排队中 2下载排队完成,等待中 3下载成功 4下载失败 5下载完成
  58. downloadStatus = 0
  59.  
  60. /**
  61. * @description 图片下载类
  62. * @class
  63. * @constructor
  64. * @param {Object} o 配置对象
  65. * @param {Array} o.imglist 图片下载链接列表
  66. * @param {number=} [o.thread=20] 启用下载线程数
  67. * @param {number=} [o.retryNum = 3] 下载出错,重试次数
  68. * @param {Object=} [o.headers = {}] 图片请求头
  69. * @param {string=} [o.downloadMode = "Zip"] 下载模式,Epub下载需配置扩展名白名单
  70. * @param {string=} [o.author ="佚名"] 作者,生成Epub会用到
  71. * @param {string=} o.filename 文件名,不包含拓展名
  72. * @param {number=} o.timeout 请求超时,默认1min
  73. * @param {Boolean=} [o.autoRetry = false] 自动重试
  74. * @param {Boolean=} [o.autoDownload = false] 重试失败后自动下载
  75. * @param {?Function=} [o.onload = null] 成功回调
  76. * @param {?Function=} [o.onerror = null] 失败回调
  77. * @return {null}
  78. */
  79. constructor(o) {
  80. if ($('#v_bar').length) {
  81. GM_notification({text: '下载中,请稍等...', timeout: 3000})
  82. return
  83. }
  84. ({
  85. imglist: this.queue = [],
  86. thread: this.thread = 20,
  87. retryNum: this.retryNum = 3,
  88. headers: this.headers = {},
  89. downloadMode: this.downloadMode = "Zip",
  90. author: this.author = "佚名",
  91. filename: this.filename = document.title.replace(/- .*?$/, '').trim(),
  92. timeout: this.timeout = 60 * 1000,
  93. autoRetry: this.autoRetry = false,
  94. autoDownload: this.autoDownload = false,
  95. onload: this.onload = null,
  96. onerror: this.onerror = null
  97. } = o);
  98. this.progressList = Array(this.queue.length)
  99. $('body').append(`<div id="v_bar"><div></div></div>`)
  100. $('head').append(`<style>
  101. #v_bar{
  102. width: 1%;
  103. height: 80%;
  104. background-color: #00ffff;
  105. position: fixed;
  106. top: 50%;
  107. left: 0;
  108. z-index: 999999999;
  109. transform: translate(0,-50%);
  110. }
  111. #v_bar>div{
  112. height: 100%;
  113. background-color: #8c939d;
  114. }
  115. </style>`)
  116. GM_notification({text: '开始下载', title: this.filename, timeout: 3000})
  117. this.downloadStart()
  118. }
  119.  
  120. // 下载开始
  121. downloadStart() {
  122. this.downloadStatus = 1;
  123. let download = () => {
  124. let index = this.downloadIndex
  125. if (!this.transfer[index] || ($.inArray(index, this.error) > -1 && this.error.shift() + 1)) {
  126. this.downloadIndex < this.queue.length - 1 && this.downloadIndex++
  127. this.transfer[index] = Promise.race([new Promise((resolve, reject) => {
  128. GM_xmlhttpRequest({
  129. method: "GET",
  130. url: this.queue[index],
  131. headers: this.headers,
  132. responseType: "blob",
  133. timeout: this.timeout,
  134. onload: r => {
  135. if (r.readyState === 4 && r.status === 200) {
  136. resolve(r.response)
  137. } else {
  138. reject(new Blob([], {type: "image/jpeg"}))
  139. }
  140. this.judgeFinish(download)
  141. },
  142. onprogress: xhr => {
  143. if (xhr.lengthComputable) {
  144. this.progressList[index] = xhr.loaded / xhr.total * 100;
  145. this.progressBar()
  146. }
  147. },
  148. onerror: _ => {
  149. reject(new Blob([], {type: "image/jpeg"}))
  150. this.judgeFinish(download)
  151. }
  152. });
  153. }), new Promise((_, reject) => {
  154. setTimeout(reject, this.timeout, new Blob([], {type: "image/jpeg"}))
  155. })])
  156. } else {
  157. //快速跳过
  158. this.downloadIndex < this.queue.length - 1 && this.downloadIndex++ && download()
  159. }
  160. }
  161. // 建立多少个线程
  162. let thread = [this.thread, this.queue.length];
  163. //重试则按出错数开启线程
  164. this.error.length > 0 ? thread.push(this.error.length) : null
  165. for (let i = 0; i < Math.min(...thread); i++) {
  166. download(i)
  167. }
  168. }
  169.  
  170. //判断队列是否完成,并进行后续处理
  171. judgeFinish(callback) {
  172. //当下载列表已遍历完成,检验是否每段都下载成功
  173. if (this.queue.length === this.transfer.length && this.error.length === 0) {
  174. if (this.downloadStatus !== 1) return
  175. this.downloadStatus = 2;
  176. //下载状态改变
  177. Promise.allSettled(this.transfer).then(all => {
  178. all.forEach((item, index) => {
  179. if (item.status === "rejected") {
  180. this.progressList[index] = 0
  181. this.error.push(index)
  182. }
  183. })
  184. //下载出错,尝试重新下载
  185. if (this.error.length > 0) {
  186. let choice = !this.retryIndex && !this.autoRetry && confirm(`${this.error.length}张图片下载出错。强行打包下载?尝试重新下载?`)
  187. if (choice) {
  188. this.downloadStatus = 3;
  189. this.results = all.map(one => one.value || one.reason);
  190. this[this.downloadMode]()
  191. return
  192. }
  193. if (this.retryIndex === this.retryNum) {
  194. GM_notification({text: '下载失败', title: this.filename, timeout: 3000})
  195. GM_log('下载失败')
  196. let choice1 = this.autoDownload || confirm(`重试5次下载失败。强行打包下载?放弃下载?`)
  197. if (choice1) {
  198. this.downloadStatus = 3;
  199. this.results = all.map(one => one.value || one.reason);
  200. this[this.downloadMode]()
  201. } else {
  202. this.downloadStatus = 4
  203. $('#v_bar').remove()
  204. this.onerror && this.onerror()
  205. }
  206.  
  207. } else !choice && this.retryAll()
  208. }
  209. //下载成功
  210. else {
  211. this.downloadStatus = 3;
  212. this.results = all.map(one => one.value);
  213. this[this.downloadMode]()
  214. }
  215. })
  216.  
  217. } else callback && callback()
  218.  
  219. }
  220.  
  221. retryAll() {
  222. this.downloadIndex = 0;
  223. this.retryIndex++
  224. this.downloadStart()
  225. }
  226.  
  227. //进度条
  228. progressBar() {
  229. let all = 0
  230. this.progressList.forEach(item => {
  231. all += item || 0
  232. })
  233. $('#v_bar>div').height((100 - all / this.progressList.length).toFixed(2) + '%')
  234. return 1
  235. }
  236.  
  237.  
  238. //处理非法文件名称
  239. legalize(str) {
  240. let pattern = new RegExp("[\\\\:<>/?*|]"), rs = "";
  241. for (let i = 0; i < str.length; i++) {
  242. rs += str.substr(i, 1).replace(pattern, '');
  243. }
  244. return rs.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "");
  245. //const invalidchar = `~!@#$%^&*,。;‘’\\{【】[]}|`;
  246. }
  247.  
  248. //下载
  249. Download(content, file_extension) {
  250. let blob_url = URL.createObjectURL(content)
  251. // 重置进度条状态
  252. this.progressList.fill(0) && this.progressBar() && $('#v_bar').css("background-color", "#e0e052")
  253. GM_download({
  254. url: blob_url,
  255. name: this.legalize(this.filename) + file_extension,
  256. onload: _ => {
  257. $('#v_bar').remove()
  258. GM_notification({text: '下载成功', title: this.filename, timeout: 3000})
  259. GM_log('下载成功')
  260. this.downloadStatus = 5;
  261. URL.revokeObjectURL(blob_url)
  262. this.onload && this.onload()
  263. },
  264. onprogress: r => {
  265. if (r.lengthComputable) {
  266. this.progressList.fill(r.loaded / r.total * 100);
  267. this.progressBar()
  268. }
  269. },
  270. onerror: _ => {
  271. $('#v_bar').remove()
  272. GM_notification({text: '下载至本地失败', title: this.filename, timeout: 3000})
  273. GM_log('下载至本地失败')
  274. this.downloadStatus = 5;
  275. URL.revokeObjectURL(blob_url)
  276. this.onerror && this.onerror()
  277. }
  278. });
  279. }
  280.  
  281. //Zip打包下载
  282. Zip() {
  283. let zip = new JSZip(), num = this.queue.length.toString().length;
  284. this.results.forEach((content, index) => {
  285. // 重置进度条状态
  286. !index && this.progressList.fill(0) && this.progressBar() && $('#v_bar').css("background-color", "red")
  287. //处理可能无content type情况
  288. let img_type=content.type||"image/jpeg"
  289. let name = `${(index + 1).toString().padStart(num, '0')}.${img_type.split('/')[1].replace('jpeg', 'jpg')}`
  290. zip.file(name, content, {blob: true});
  291. zip.file(name).async("blob", metadata => {
  292. this.progressList[index] = metadata.percent;
  293. this.progressBar()
  294.  
  295. })
  296. })
  297. zip.generateAsync({type: "blob"}).then(content => this.Download(content, '.zip'));
  298. }
  299.  
  300. //Epub打包下载
  301. Epub() {
  302. let epub = new JSZip(), num = this.queue.length.toString().length;
  303. //指定文件类型
  304. epub.file('mimetype', 'application/epub+zip');
  305. //container文件
  306. epub.file('META-INF/container.xml', `<?xml version="1.0"?>
  307. <container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
  308. <rootfiles>
  309. <rootfile full-path="OEBPS/metadata.opf" media-type="application/oebps-package+xml"/>
  310. </rootfiles>
  311. </container>`);
  312. //配置元数据
  313. let uuid = URL.createObjectURL(new Blob()).split('/').reverse()[0];
  314. let metadata = `<?xml version="1.0" encoding="UTF-8"?>
  315. <package xmlns="http://www.idpf.org/2007/opf" unique-identifier="uuid_id" version="2.0">
  316. <metadata xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:opf="http://www.idpf.org/2007/opf">
  317. <dc:title>${this.filename}</dc:title>
  318. <dc:creator opf:role="aut" opf:file-as="${this.author}">${this.author}</dc:creator>
  319. <dc:identifier opf:scheme="uuid" id="uuid_id">${uuid}</dc:identifier>
  320. <dc:contributor opf:file-as="GSCSD" opf:role="bkp">GSCSD</dc:contributor>
  321. <dc:publisher>GSCSD</dc:publisher>
  322. <dc:date>${new Date().toISOString()}</dc:date>
  323. <dc:language>zh</dc:language>
  324. <meta name="cover" content="cover"/>
  325. </metadata>
  326. <manifest>
  327. <item id="ncx" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
  328. <item id="main" href="main.html" media-type="application/xhtml+xml"/>
  329. ${this.results.map((content, index) => {
  330. let id = (index + 1).toString().padStart(num, '0'),
  331. type_name = content.type.split('/')[1].replace('jpeg', 'jpg')
  332. return `<item id="image${id}" href="Images/${id}.${type_name}" media-type="${content.type}"/>`
  333. }).join('\n ')}
  334. <item id="cover" href="Images/${(1).toString().padStart(num, '0')}.${this.results[0].type.split('/')[1].replace('jpeg', 'jpg')}" media-type="image/jpeg"/>
  335. </manifest>
  336. <spine toc="ncx">
  337. <itemref idref="main"/>
  338. </spine>
  339. </package>
  340. `
  341. epub.file('OEBPS/metadata.opf', metadata);
  342. //配置目录
  343. let toc = `<?xml version='1.0' encoding='utf-8'?>
  344. <ncx xmlns="http://www.daisy.org/z3986/2005/ncx/" version="2005-1" xml:lang="zh">
  345. <head>
  346. <meta name="dtb:uid" content="${uuid}"/>
  347. <meta name="dtb:depth" content="2"/>
  348. <meta name="dtb:generator" content="GSCSD"/>
  349. <meta name="dtb:totalPageCount" content="0"/>
  350. <meta name="dtb:maxPageNumber" content="0"/>
  351. </head>
  352. <docTitle>
  353. <text>${this.filename}</text>
  354. </docTitle>
  355. <navMap>
  356. <navPoint id="num_1" playOrder="1">
  357. <navLabel>
  358. <text>图片 ${this.results.length}P</text>
  359. </navLabel>
  360. <content src="main.html"/>
  361. </navPoint>
  362. </navMap>
  363. </ncx>
  364. `
  365. epub.file('OEBPS/toc.ncx', toc);
  366. //配置主html
  367. let html = `<!DOCTYPE html>
  368. <html lang="zh">
  369. <head>
  370. <meta charset="UTF-8">
  371. <title>图片页</title>
  372. <style>
  373. img{
  374. width: 100%;
  375. }
  376. </style>
  377. </head>
  378. <body>
  379. <div>
  380. ${this.results.map((content, index) => {
  381. let id = (index + 1).toString().padStart(num, '0'),
  382. type_name = content.type.split('/')[1].replace('jpeg', 'jpg')
  383. return `<img src="Images/${id}.${type_name}" alt="加载失败">`
  384. }).join('\n ')}
  385. </div>
  386. </body>
  387. </html>`
  388. epub.file('OEBPS/main.html', html);
  389. this.results.forEach((content, index) => {
  390. // 重置进度条状态
  391. !index && this.progressList.fill(0) && this.progressBar() && $('#v_bar').css("background-color", "red")
  392. //处理可能无content type情况
  393. let img_type=content.type||"image/jpeg"
  394. let name = `OEBPS/Images/${(index + 1).toString().padStart(num, '0')}.${img_type.split('/')[1].replace('jpeg', 'jpg')}`
  395. epub.file(name, content, {blob: true});
  396. epub.file(name).async("blob", metadata => {
  397. this.progressList[index] = metadata.percent;
  398. this.progressBar()
  399.  
  400. })
  401. })
  402. epub.generateAsync({type: "blob"}).then(content => this.Download(content, '.epub'));
  403. }
  404.  
  405. }
  406.  
  407. unsafeWindow.TaskQueue = TaskQueue;
  408.  
  409. function add_listener() {
  410. let el_list = [];
  411. document.querySelectorAll("img").forEach(img => {
  412. let a_el = $(img).parents('a'), click_el = a_el.length ? a_el[0] : img
  413. el_list.push(click_el)
  414. //先移除之前事件监听
  415. $(click_el).off('click').click(e => {
  416. // e.stopImmediatePropagation()
  417. if ($('#v_bar').length) {
  418. GM_notification({text: '下载中,请稍等...', timeout: 3000})
  419. return
  420. }
  421. window.getimg = e.target;
  422. // let imglist, width = [], height = [];
  423. let imglist = FindBrothers(e.target).map((one, index) => {
  424. //[width[index], height[index]] = [one.naturalWidth, one.naturalHeight]
  425. return one.src
  426. });
  427. let len = imglist.length;
  428. if (len === 0) return;
  429. if (confirm(`下载全部${len}张图片?`)) new TaskQueue({imglist: imglist})
  430. });
  431. })
  432. return el_list
  433.  
  434. }
  435.  
  436. //重生成元素,去除事件监听绝杀无解
  437. function remake(el) {
  438. let parent = $(el).parent(), next = $(el).next(), html = el.outerHTML;
  439. $(el).remove();
  440. next.length ? $(next).before(html) : $(parent).append(html)
  441. }
  442.  
  443. //1.a标签新窗口打开
  444. $('a').attr("target", "_blank")
  445. //2.移除onclick属性,主流网站基本弃用
  446. $("[onclick]").removeAttr("onclick");
  447. add_listener();
  448. //3.尝试移除图片上事件监听
  449. GM_registerMenuCommand("绝招,启用后再点试试!", _ => {
  450. let els = add_listener();
  451. els.forEach(el => remake(el));
  452. add_listener()
  453. });
  454. //4.直接重构除script的整个body,釜底抽薪
  455. GM_registerMenuCommand("终招,启用后再点试试!", _ => {
  456. document.querySelectorAll('body>*:not(script)').forEach(el => remake(el))
  457. add_listener()
  458. });
  459. //循环添加图片监听,为动态加载图片而准备
  460. setInterval(add_listener, 2000)
  461. })();