Nico dl

下载N站视频 | Download video from nicovideo.jp

  1. // ==UserScript==
  2. // @name Nico dl
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.4
  5. // @description 下载N站视频 | Download video from nicovideo.jp
  6. // @author ctrn43062
  7. // @include *//www.nicovideo.jp/watch/sm*
  8. // @icon https://www.google.com/s2/favicons?domain=nicovideo.jp
  9. // @grant none
  10. // @note 2023-2-20 [fix] 修复打开视频封面失效的问题 (年了,一更)
  11. // @note 2022-2-17 [fix] 修复右键打开视频时可能发生的无法下载视频和视频封面的 bug
  12. // @note 2022-2-10 [feat] 添加下载视频高清封面功能
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. const IS_HLS_DISABLED = 'DMCSource.isHLSDisabled';
  17. const DOWNLOAD_SVG = `<svg version="1.1"id="Capa_1"xmlns="http://www.w3.org/2000/svg"xmlns:xlink="http://www.w3.org/1999/xlink"x="0px"y="0px"viewBox="0 0 330 330"style="enable-background:new 0 0 330 330;"xml:space="preserve"><g><path d="M165,0C74.019,0,0,74.018,0,165c0,90.98,74.019,165,165,165s165-74.02,165-165C330,74.018,255.981,0,165,0z M165,300c-74.439,0-135-60.561-135-135S90.561,30,165,30s135,60.561,135,135S239.439,300,165,300z"/><path d="M211.667,127.121l-31.669,31.666V75c0-8.285-6.716-15-15-15c-8.284,0-15,6.715-15,15v83.787l-31.665-31.666c-5.857-5.857-15.355-5.857-21.213,0c-5.858,5.859-5.858,15.355,0,21.213l57.271,57.271c2.929,2.93,6.768,4.395,10.606,4.395c3.838,0,7.678-1.465,10.607-4.393l57.275-57.271c5.857-5.857,5.858-15.355,0.001-21.215C227.021,121.264,217.524,121.264,211.667,127.121z"/><path d="M195,240h-60c-8.284,0-15,6.715-15,15c0,8.283,6.716,15,15,15h60c8.284,0,15-6.717,15-15C210,246.715,203.284,240,195,240z"/></svg>`
  18. const COVER_SVG = `<svg version="1.1"xmlns="http://www.w3.org/2000/svg"viewBox="0 0 512 512"xmlns:xlink="http://www.w3.org/1999/xlink"enable-background="new 0 0 512 512"><path d="M480.6,11H31.4C20.1,11,11,20.1,11,31.4v449.2c0,11.3,9.1,20.4,20.4,20.4h449.2c11.3,0,20.4-9.1,20.4-20.4V31.4 C501,20.1,491.9,11,480.6,11z M460.2,51.8v133.8c-67.3,8.2-119.4,31.2-159.7,60.9C181.2,235.6,96.9,302,51.8,350.8V51.8H460.2z M51.8,416.1c15-22.2,87-119,203.8-129.1c-58,63.7-79.4,139.1-86.5,173.1H51.8V416.1z M210.5,460.2 c12.7-58.1,63.5-208.3,249.7-233.4v233.4H210.5z"/><path d="m153.8,213.4c35.2,0 63.9-28.7 63.9-63.9 0-35.2-28.6-63.9-63.9-63.9-35.2,0-63.9,28.7-63.9,63.9 0.1,35.2 28.7,63.9 63.9,63.9zm0-86.9c12.7,0 23,10.3 23,23 0,12.7-10.3,23-23,23-12.7,0-23-10.3-23-23 0-12.7 10.3-23 23-23z"/></svg>`
  19.  
  20.  
  21. function createButton(svg, title, disabled = false) {
  22. const button = document.createElement('button');
  23. button.className = 'ActionButton ControllerButton PlayerRepeatOnButton';
  24. button.innerHTML = svg;
  25. button.setAttribute('data-title', title);
  26.  
  27. disabled && button.setAttribute('disabled', disabled);
  28.  
  29. return button;
  30. }
  31.  
  32.  
  33. function insertButtonToVideoControllBar(button) {
  34. const playbackRateButton = document.querySelector('.ActionButton.PlaybackRateButton');
  35. const wrapper = playbackRateButton.parentElement;
  36. wrapper.insertBefore(button, playbackRateButton);
  37. }
  38.  
  39.  
  40. function createDownloadVideoButton() {
  41. const downloadButton = createButton(DOWNLOAD_SVG, '下载视频', true);
  42. insertButtonToVideoControllBar(downloadButton);
  43.  
  44. return {
  45. setSrc: function (src) {
  46. downloadButton.removeAttribute('disabled');
  47. downloadButton.onclick = function () {
  48. window.open(src)
  49. };
  50. },
  51. disable: function () {
  52. downloadButton.setAttribute('disabled', true);
  53. }
  54. }
  55. }
  56.  
  57.  
  58.  
  59.  
  60. function createDownloadCoverButton() {
  61. const button = createButton(COVER_SVG, '下载封面');
  62. const thumbnailSizes = ['ogp', 'largeUrl', 'middleUrl', 'url', 'player',]
  63.  
  64. insertButtonToVideoControllBar(button);
  65.  
  66. const getCoverURL = () => {
  67. debugger
  68. const apiDOM = document.querySelector('#js-initial-watch-data')
  69.  
  70. const thumbnail = JSON.parse(apiDOM.getAttribute('data-api-data'))['video']['thumbnail']
  71.  
  72. for (const size of thumbnailSizes) {
  73. const url = thumbnail[size]
  74. if (url) {
  75. return url
  76. }
  77. }
  78.  
  79. }
  80.  
  81. const updateCoverURL = function () {
  82. const cover_url = getCoverURL();
  83.  
  84. button.removeAttribute('disabled');
  85.  
  86. button.onclick = () => {
  87. open(cover_url);
  88. }
  89.  
  90. return cover_url;
  91. }
  92.  
  93. return {
  94. updateCoverURL: updateCoverURL,
  95. disable: function () {
  96. button.setAttribute('disabled', true);
  97. }
  98. }
  99. }
  100.  
  101.  
  102. (function () {
  103. 'use strict';
  104. const isHttp = localStorage.getItem(IS_HLS_DISABLED);
  105.  
  106. if (isHttp === null || isHttp === 'false') {
  107. localStorage.setItem(IS_HLS_DISABLED, 'true');
  108. location.reload();
  109. }
  110.  
  111.  
  112. const downloadCoverButton = createDownloadCoverButton();
  113. const downloadVideoButton = createDownloadVideoButton();
  114.  
  115. // 循环判断 cover_url 是否存在
  116. const updateCoverURL = () => {
  117. const cover_url = downloadCoverButton.updateCoverURL();
  118.  
  119. if (!cover_url) {
  120. setTimeout(updateCoverURL, 10);
  121. }
  122. }
  123.  
  124. const observer = new MutationObserver(mutationsList => {
  125. for (let mutation of mutationsList) {
  126. const target = mutation.target;
  127. if (mutation.attributeName === 'src') {
  128. if (target.src) {
  129. downloadVideoButton.setSrc(target.src);
  130. updateCoverURL();
  131. } else {
  132. downloadVideoButton.disable();
  133. downloadCoverButton.disable();
  134. }
  135. }
  136. }
  137. });
  138.  
  139.  
  140. const bindVideoObserver = () => {
  141. const video = document.querySelector('#MainVideoPlayer > video');
  142. if (video === null) {
  143. setTimeout(bindVideoObserver, 10);
  144. } else {
  145. observer.observe(video, {
  146. attributes: true
  147. });
  148. }
  149. }
  150.  
  151. bindVideoObserver();
  152. })();