daum漫画图片下载

一键下载Daum的整话漫画

  1. // ==UserScript==
  2. // @name daum漫画图片下载
  3. // @namespace http://weibo.com/liangxiafengge/
  4. // @version 0.4.2
  5. // @description 一键下载Daum的整话漫画
  6. // @author CW2012
  7. // @icon http://s1.daumcdn.net/photo-section/-cartoon10/favicon/201312/favicon.ico
  8. // @match http*://webtoon.daum.net/webtoon/view/*
  9. // @match http*://webtoon.daum.net/webtoon/viewer/*
  10. // @match http*://webtoon.daum.net/league/viewer/*
  11. // @match http*://webtoon.daum.net/league/view/*
  12. // @connect http://webtoon.daum.net/
  13. // @connect http://t1.daumcdn.net/
  14. // @require https://cdn.bootcdn.net/ajax/libs/jszip/3.5.0/jszip.min.js
  15. // @grant GM_xmlhttpRequest
  16. // @grant GM_download
  17. // @run-at document-end
  18. // ==/UserScript==
  19. /****************************************************************************
  20. 超级坑爹的一点:
  21. 千万不要在上面的配置区里使用tab键,否则会出现配置失败的情况
  22. // @grant(tab) GM_xmlhttpRequest
  23. 我就是无意中在这里点了一下tab键,它又不显示出来,导致我以为tab键被自动清除了
  24. 但是它居然还TM在!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  25. 最后grant的是tab,而不是GM_xmlhttpRequest
  26. 还特么报错说“而不是GM_xmlhttpRequest is not defined”靠!
  27. ****************************************************************************/
  28. let downloadCount = 0; // 下载一话时,这一话的图片数目
  29. let picCount;
  30. let txt = '';
  31. let finished = true; // 是否完成下载作业
  32. let zip;
  33. let eposideName;
  34. let comicType;
  35. (function() {
  36. 'use strict';
  37. let tmpStr = location.href.split('#')[0];
  38. tmpStr = tmpStr.split('\/');
  39. comicType = tmpStr[3];
  40. if(tmpStr[4]=='view'){
  41. // 漫画列表页
  42. downloadFromEposideList();
  43. }else if(tmpStr[4]=='viewer'){
  44. // 漫画阅读页
  45. downloadFromRead();
  46. }
  47. })();
  48.  
  49. function downloadFromEposideList(){
  50. // 考虑到页面加载数据还需要一段时间,如果需要依附的元素还没生成,下面的添加按钮的动作将无法执行
  51. if(document.querySelectorAll('.clear_g.list_update>li').length == 0){
  52. // 这里上列表还没加载进来时的逻辑
  53.  
  54. // 奇怪,就算不请求任何数据,这个元素也是存在的,为什么没有触发元素改变的事件???
  55. let eposideList = document.querySelector(comicType =='league'?'#episodeList .list_update':'.clear_g.list_update');
  56. // 监听eposideList元素,如果数据加载进来了,它的li子元素的个数会大于0
  57. // 监听子元素变化需要开启childList,attributes是监听本元素的变化,如class变化等
  58. new MutationObserver(eposideListChanged).observe(eposideList, {
  59. attributes: false,
  60. childList: true,
  61. subtree: false
  62. });
  63. return;
  64. }
  65. }
  66.  
  67. // 发生数据加载时,添加按钮
  68. function eposideListChanged(){
  69. // 等待一秒钟,让元素全部添加到网页上后,再添加我们的按钮
  70. setTimeout(()=>{
  71. let smallPicList= document.querySelectorAll(comicType =='league'?'#episodeList .list_update>li':'.clear_g.list_update>li');
  72. if(smallPicList.length == 0){
  73. return;
  74. }
  75. // 按钮应该出现的位置:缩略图所在的元素
  76. smallPicList.forEach((item,index)=>{
  77. let btnParent = item.lastElementChild;
  78. btnParent.style.display='flex';
  79. btnParent.style.diapley = 'flex';
  80. btnParent.style.justifyContent = 'space-between';
  81. // 分割线
  82. let line = document.createElement('span');
  83. line.className = 'ico_comm ico_bar';
  84. btnParent.appendChild(line);
  85. // 问题来了,收费的篇章是不能下载的,那么,不如收费篇章就不要添加下载按钮好了
  86. if(btnParent.children.length>2){
  87. return;
  88. }
  89. // 创建一个“下载”锚点(a),并美化亿下
  90. let btn = document.createElement('a');
  91. btn.innerText = '下载这一话';
  92. btn.style.background ='#e83d3d'; //使用和原网页一样的配色,没有突兀感
  93. btn.style.color = '#fff';
  94. btn.style.padding = '3px';
  95. btn.style.borderRadius = '3px';
  96. btn.style.textDecoration='none';// 不显示下划线
  97. btn.style.cursor = 'pointer'; // 鼠标悬浮时,指针变成手形,让用户知道可以点击
  98. btn.addEventListener('click', e=>{
  99. // 获得这一话的ID,并根据ID下载
  100. if(!finished){
  101. alert('上一个下载作业还未完成,请等待完成后继续');
  102. return;
  103. }
  104. getEposideInfoAndDownload(item.firstElementChild.href.split('/')[5]);
  105. });
  106. btnParent.appendChild(btn); //添加到网页中
  107. });
  108. },1000);
  109. }
  110.  
  111. // 根据这一话的ID,下载一整话
  112. function downloadEposide(eposideId){
  113. // 一话的链接,请求API
  114. let tmp = (comicType == 'league')?'leaguetoon':'webtoon';
  115. let url = `http://webtoon.daum.net/data/pc/${tmp}/viewer_images/${eposideId}`;
  116. GM_xmlhttpRequest({
  117. method: "GET",
  118. url: url,
  119. onload: function(res){
  120. if(res.status === 200){
  121. // 分析所得数据,并下载图片
  122. let response = JSON.parse(res.responseText); // 这里返加的是文本,需要转换成JSON对象才能用
  123. if(parseInt(response.result.status) == 200){
  124. let data = response.data;
  125. // data虽然是Array,但是不能用foreach??
  126. picCount = data.length;
  127. downloadCount = 0;
  128. zip = new JSZip();
  129. finished = false;
  130. txt = '';
  131. for(let i=0;i<data.length;i++){
  132. // 下载单幅图片
  133. let item = data[i];
  134. if(item.mediaType == 'image') {
  135. downloadSinglePic(item.url, item.imageOrder);
  136. } else{
  137. ++downloadCount;
  138. txt +=`编号${i+1},`;
  139. // downloadSingleMp4(item.url, item.imageOrder);
  140. }
  141. }
  142. }else{
  143. console.log(`ID${eposideId}的这一话下载失败`);
  144. }
  145. }else{ console.log(`ID${eposideId}的这一话下载失败`); }
  146. },
  147. onerror : function(err){ console.log(`ID${eposideId}的这一话下载失败,原因为${err}`); }
  148. });
  149. }
  150.  
  151. // 下载单幅图片
  152. async function downloadSinglePic(picUrl, picNum){
  153. GM_xmlhttpRequest({
  154. method: "GET",
  155. url: picUrl,
  156. responseType:'blob',
  157. onload: function(res){
  158. const imageBlob = res.response;
  159. let picName = `${picNum<10? '0':''}${picNum}.jpg`;
  160. const imgData = new File([imageBlob], picName);
  161. zip.file(picName, imgData, { base64: true });
  162. zip.generateAsync({ type: 'blob' }).then(function(content) {
  163. progress(++downloadCount);
  164. if(downloadCount>=picCount){
  165. if(txt!=''){
  166. txt = txt.substr(0,txt.length-1);
  167. zip.file('部分无法下载的文件(非图片)列表.txt', txt);
  168. zip.generateAsync({type: 'blob'}).then(
  169. contentWithTxt=>
  170. downloadFileByBlob(contentWithTxt, eposideName)
  171. );
  172. return;
  173. }
  174. downloadFileByBlob(content, eposideName);
  175. // delete(document.getElementById('progressBar'))
  176. }
  177. });
  178. },
  179. onerror: ()=>{console.error('下载图片出错')}
  180. });
  181. }
  182.  
  183. // 其实是错误的
  184. function downloadSingleMp4(picUrl, picNum){
  185. // tamperMonkey的官方文档,参数说明 https://www.tampermonkey.net/documentation.php
  186.  
  187. // 下载失败、超时的每一张图片单独提醒用户
  188. // 下载成功不打开“另存为”窗口
  189. GM_download({
  190. url:picUrl,
  191. name: `${picNum<10? '0':''}${picNum}.mp4`,
  192. saveAs:false,
  193. onload:()=>{
  194. // 每下载成功一张图片减一,减到0时,表示这一话已经全部下载完成
  195. progress(++downloadCount);
  196. if(downloadCount>=picCount){
  197. // 但是alert弹窗的样式非常得不银杏化,因此我们来美化一下
  198. toast(1, '这一话的所有图片全部处理完成')
  199. }
  200. },
  201. onerror: ()=>{
  202. toast(-1, `下载第${picNum}个视频时出错,请按Ctrl+shift+I打开开发者工具自行下载`)
  203. console.log(`下载第${picNum}幅视频时出错,它的下载链接是:\n${picUrl}`);
  204. },
  205. ontimeout: ()=>{
  206. toast(-1,`下载第${picNum}幅视频时超时,请按Ctrl+shift+I打开开发者工具自行下载`)
  207. console.log(`下载第${picNum}幅视频时超时,它的下载链接是:\n${picUrl}`);
  208. }
  209. });
  210. }
  211.  
  212. // 显示自定义的弹窗
  213. // type 消息类型:1成功,0提示,-1错误
  214. // msg消息体
  215. const colors = ['rgba(7 123 11 / 73%)','rgb(8 99 3 / 73%)','rgba(7 135 247 / 73%)'];
  216. function toast(type, msg){
  217. let toastBox = document.createElement('div');
  218. toastBox.style.position = 'fixed';
  219. toastBox.style.background='white';
  220. toastBox.style.borderRadius='10px';
  221. toastBox.style.background= colors[type+1];
  222. toastBox.style.boxShadow='rgb(25 25 25) 1px 1px 10px 1px';
  223. toastBox.innerText=msg;
  224. toastBox.style.color = '#fff';
  225. toastBox.style.bottom='12vh'; // 显示的位置不能离底部太低了,会被其他元素遮挡
  226. toastBox.style.left='2vh';
  227. toastBox.style.transition='1.5s';
  228. toastBox.style.padding = '10px';
  229. document.body.appendChild(toastBox);
  230. // 6秒后自动隐藏
  231. setTimeout(()=>{
  232. toastBox.style.opacity = '0';
  233. delete(toastBox);
  234. }, 6000);
  235. }
  236. // 显示下载进度
  237. function progress(prog){
  238. let bar = document.getElementById('progressBar');
  239. if(bar){
  240. bar.innerText = `正在下载:${prog}/${picCount}`;
  241. if(prog == -1){
  242. bar.remove();
  243. }
  244. return;
  245. }
  246. let progressBox = document.createElement('div');
  247. progressBox.id = 'progressBar';
  248. progressBox.style.position = 'fixed';
  249. progressBox.style.background='white';
  250. progressBox.style.borderRadius='10px';
  251. progressBox.style.background= colors[2];
  252. progressBox.style.boxShadow='0 8px 16px 0 rgba(0,0,0,.2), 0 6px 20px 0 rgba(0,0,0,.19)';
  253. progressBox.innerText=`正在下载:0/${picCount}`;
  254. progressBox.style.color = '#fff';
  255. progressBox.style.bottom='200px'; // 显示的位置不能离底部太低了,会被其他元素遮挡
  256. progressBox.style.left='2vh';
  257. progressBox.style.transition='1.5s';
  258. progressBox.style.padding = '10px';
  259. document.body.appendChild(progressBox);
  260. }
  261.  
  262. // 在阅读页上添加下载的功能
  263. function downloadFromRead(){
  264. // 先添加一个悬浮按钮???
  265. // 创建一个“下载”锚点(a),并美化一下
  266. let btn = document.createElement('a');
  267. btn.innerText = '下载这一话';
  268. btn.style.position = 'fixed';
  269. btn.style.left = '20px';
  270. btn.style.top = '45vh';
  271. btn.style.background ='#e83d3d'; //使用和原网页一样的配色,没有突兀感
  272. btn.style.color = '#fff';
  273. btn.style.padding = '13px';
  274. btn.style.fontSize= '20px';
  275. btn.style.boxShadow='gray 1px 1px 35px 0';
  276. btn.style.borderRadius = '13px';
  277. btn.style.textDecoration='none';// 不显示下划线
  278. btn.style.cursor = 'pointer'; // 鼠标悬浮时,指针变成手形,让用户知道可以点击
  279. btn.addEventListener('click', e=>{
  280. // 获得这一话的ID,并根据ID下载,跟上面的逻辑是一模一样的
  281. if(!finished){
  282. alert('上一个下载作业还未完成,请等待完成后继续');
  283. return;
  284. }
  285. let eposideId = location.href.split('/')[5];
  286. getEposideInfoAndDownload(eposideId);
  287. });
  288. document.body.appendChild(btn); //添加到网页中
  289. }
  290.  
  291. // 获得这一话的信息
  292. function getEposideInfoAndDownload(eposideId){
  293. picCount = '正在获取....';
  294. progress(0);
  295. let tmp = (comicType == 'league')?'leaguetoon':'webtoon';
  296. // http://webtoon.daum.net/data/pc/webtoon/viewer/93187?timeStamp=1607874553918
  297. GM_xmlhttpRequest({
  298. method: "GET",
  299. url: `http://webtoon.daum.net/data/pc/${tmp}/viewer/${eposideId}`,
  300. onload: function(res){
  301. if(res.status === 200){
  302. let data = JSON.parse(res.responseText);
  303. if(data.result.status == 200){
  304. data = data.data;
  305. let prop = (comicType == 'league')?'leaguetoon':'webtoonEpisode';
  306. eposideName = data[prop].title;
  307. downloadEposide(eposideId);
  308. }else{
  309. alert('获取信息出错')
  310. }
  311.  
  312. }
  313. }
  314. });
  315. }
  316.  
  317. // 利用blob下载文件
  318. function downloadFileByBlob(blobContent, filename) {
  319. const blobUrl = URL.createObjectURL(blobContent)
  320. const eleLink = document.createElement('a')
  321. eleLink.download = filename
  322. eleLink.style.display = 'none'
  323. eleLink.href = blobUrl
  324. eleLink.click();
  325. progress(-1);
  326. finished = true;
  327. }

QingJ © 2025

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