【自用】YoutubeTools

!!仅在火狐上测试!! 1.恢复页面布局,2.增加按钮,方便IDM下载(配合IDM使用)【需配合Local YouTube Downloader使用】,3.增加按钮,一键重绘下载png格式的cover,4.增加按钮生成合并命令行,5.用clipboard,火狐v99之后抽风

目前为 2022-04-22 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @require https://cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js
  3. // @icon https://github.com/favicon.ico
  4. // @name 【自用】YoutubeTools
  5. // @namespace Violentmonkey Scripts
  6. // @match *://*.youtube.com/*
  7. // @grant none
  8. // @version 2022.04.22
  9. // @author heckles
  10. // @description !!仅在火狐上测试!! 1.恢复页面布局,2.增加按钮,方便IDM下载(配合IDM使用)【需配合Local YouTube Downloader使用】,3.增加按钮,一键重绘下载png格式的cover,4.增加按钮生成合并命令行,5.用clipboard,火狐v99之后抽风
  11. // @Homepage URL https://greasyfork.org/zh-CN/scripts/431488-%E8%87%AA%E7%94%A8-youtubetools
  12. // ==/UserScript==
  13.  
  14. //0. 引入clipboard
  15. var clipboard = new ClipboardJS('.clipbtn');//这里类似query,得有#
  16. //log一下
  17. clipboard.on('success', function (e) {
  18. console.log(e);
  19. });
  20.  
  21. clipboard.on('error', function (e) {
  22. console.log(e);
  23. });
  24.  
  25. //1.设定加载条件
  26. if (document.getElementById("browser-app") || document.getElementById("masthead")) { //这两个元素,有一个是true,就往下执行
  27. var sx = setInterval(function () { //间隔执行
  28. //console.log(">>>>>>>>>>> 【YoutubeTools】 interval开始 <<<<<<<<<<<");
  29. if (window.location.href.indexOf("watch?v=") < 0) { //如果网址不匹配
  30. return false; //就不执行 【这里只能匹配域名,然后筛,直接用watch的网址,从首页点进去会不触发】
  31. }else{
  32. if (document.getElementById("meta-contents") && document.getElementById("punisher") === null) { //网址匹配的话,punisher没有被添加
  33. StartJS(); //就执行函数,添加punisher
  34. console.log(">>>>>>>>>>> 【YoutubeTools】 已加载 <<<<<<<<<<<");
  35. }
  36. }
  37. }, 3000); //间隔时间,毫秒
  38. //return;
  39. }
  40.  
  41. //2.条件触发后加载
  42. function StartJS() {
  43. //2.1新增按钮的样式
  44. const btncss = `
  45. color: #F97D00;
  46. font-weight: bold;
  47. /*text-transform: uppercase;*/
  48. padding: 0px 3px;
  49. background-color: transparent;
  50. border-color: transparent;
  51. `
  52. //2.2开始添加按钮
  53. var buttonDiv = document.createElement("span");
  54. buttonDiv.id = "punisher";
  55. buttonDiv.style.width = "100%";
  56. buttonDiv.style.marginTop = "3px";
  57. buttonDiv.style.padding = "1px 0";
  58. var addButtonV = document.createElement("button");
  59. addButtonV.setAttribute('class','clipbtn');
  60. addButtonV.setAttribute('data-clipboard-action','cut');
  61. addButtonV.setAttribute('data-clipboard-target','#clipinput');
  62. var addButtonA = document.createElement("button");
  63. addButtonA.setAttribute('class','clipbtn');
  64. addButtonA.setAttribute('data-clipboard-action','cut');
  65. addButtonA.setAttribute('data-clipboard-target','#clipinput');
  66. var addButtonM = document.createElement("button");
  67. var aCover = document.createElement("a");
  68. var aTitle = document.createElement("a");
  69. var aSrt = document.createElement("a");
  70. addButtonV.appendChild(document.createTextNode("Vcode"));
  71. addButtonA.appendChild(document.createTextNode("Acode"));
  72. addButtonM.appendChild(document.createTextNode("Merge"));
  73. aCover.appendChild(document.createTextNode("Cover"));
  74. aTitle.appendChild(document.createTextNode("F-name"));
  75. aSrt.appendChild(document.createTextNode("Srt"));
  76. addButtonV.style.cssText = btncss;
  77. addButtonA.style.cssText = btncss;
  78. addButtonM.style.cssText = btncss;
  79. aCover.style.cssText = btncss;
  80. aTitle.style.cssText = btncss;
  81. aSrt.style.cssText = btncss;
  82. buttonDiv.appendChild(addButtonV);
  83. buttonDiv.appendChild(addButtonA);
  84. buttonDiv.appendChild(addButtonM);
  85. buttonDiv.appendChild(aCover);
  86. buttonDiv.appendChild(aTitle);
  87. buttonDiv.appendChild(aSrt);
  88.  
  89.  
  90. var targetElement = document.querySelectorAll("[id='info-text']"); //youtube故意的,很多元素id重复,这里够绝,直接全选中,然后按class筛,再加
  91. if (targetElement) {
  92. for (var i = 0; i < targetElement.length; i++) {
  93. if (targetElement[i].className.indexOf("style-scope ytd-video-primary-info-renderer") > -1) {
  94. targetElement[i].appendChild(buttonDiv);
  95. }
  96. }
  97. }
  98.  
  99. //3.创建一个input,但是不显示(通过移位),作为复制的中介
  100. var nMInput = document.createElement('input');
  101. nMInput.style.cssText = "position:absolute; top:-200px;"; //火狐实测隐藏的话不能选,oInput.style.display='none';
  102. document.body.appendChild(nMInput);
  103. nMInput.setAttribute('id','clipinput');
  104. //4.生成文件名
  105. var refreshvar = function(){//设置全局变量,随时准备刷新
  106. nMo = document.querySelector("#container h1 yt-formatted-string").innerText;//获取视频名称,下面再把不能作为文件名的符号替换
  107. /*油管应该是禁了,cmd方式调用IDM下载会报错,这里就没必要筛选韩文了
  108. if(nMo.match(/[\uac00-\ud7ff]/gi)){
  109. nMo = "【名称包含韩文,根据视频编号自行修改】" +window.location.href.split("watch?v=")[1];
  110. }
  111. */
  112. nM = nMo.replace(/[|\\|\/|\:|\*|\?|\"|\<|\>|\|]/g, function (a) {//每一个符号前面都加|\,/[]/g表示全文匹配
  113. switch (a) {//就是换成全角的标点,全角标点是用输入法找出来的
  114. case '\\':
  115. return '\';
  116. case '/':
  117. return '/';
  118. case ':':
  119. return ':';
  120. case '*':
  121. return '·';
  122. case '?':
  123. return ' ?';
  124. case '\"':
  125. return '"';
  126. case '<':
  127. return '〈';
  128. case '>':
  129. return '〉';
  130. case '|':
  131. return '|';
  132. }
  133. });
  134. nM_V = '"' + nM + ' - DASH_V' + '"' + '.mp4';
  135. nM_A = '"' + nM + ' - DASH_A' + '"' + '.m4a';
  136. //5.生成封面图的地址和名称
  137. src_J = document.querySelector("#container div.ytp-cued-thumbnail-overlay-image").style.cssText.slice(23, -3);
  138. nM_J = document.querySelector("#container h1 yt-formatted-string").innerText + src_J.split("default")[1];
  139. if(document.querySelector("div#info-strings #dot").nextSibling.innerText.split(" ")[1]){
  140. nM_dateXX = document.querySelector("div#info-strings #dot").nextSibling.innerText.split(" ")[1];//避免出现“首播开始于”
  141. }else{
  142. nM_dateXX = document.querySelector("div#info-strings #dot").nextSibling.innerText;
  143. }
  144. nM_date = nM_dateXX.split("年")[0] +"-"+ nM_dateXX.split("月")[0].split("年")[1].padStart(2,'0') +"-"+ nM_dateXX.split("日")[0].split("月")[1].padStart(2,'0');
  145. }
  146. aCover.target = "_blank";
  147. //6.IDM下载命令行所需
  148. const ds1 = `"D:\\Programs\\Internet Download Manager"\\idman.exe /n /d "`
  149. const ds2 = `"/p "D:\\下载\\IDM\\00.合并油管" /f `
  150. const mg1 = `"D:\\Programs\\视频编辑\\YouTube 音视频分离合并\\64 位\\ffmpeg" -i "D:\\下载\\IDM\\00.合并油管\\`
  151. const mg2 = ` - DASH_A".m4a -i "D:\\下载\\IDM\\00.合并油管\\`
  152. const mg3 = ` - DASH_V".mp4 -acodec copy -vcodec copy "D:\\下载\\IDM\\00.合并油管\\`
  153. const mg4 = `".mp4`
  154. //7.1视频
  155. addButtonV.onclick = function () {//按钮加event //shadowroot的mode必须是open,否则没有ShadowDOM
  156. refreshvar();//刷新全局变量
  157. var xroot = document.getElementById("hahahazijijiade");//找这里const shadowHost = $el('div'),然后加一句 shadowHost.setAttribute("id","hahahazijijiade"); <<<<<<<<<<<<<<<<<<<<<<<<<
  158. var linkss = xroot.shadowRoot.children[0].children[2].children[1].children[1];
  159. var ku = linkss.querySelectorAll("a");
  160. if (ku) {
  161. for (var i = 0; i < ku.length; i++) {
  162. if (linkss.children[i].innerText.indexOf("1080p") > -1 && linkss.children[i].innerText.indexOf("video/mp4") > -1 && linkss.children[i].innerText.indexOf("avc1.6") > -1) { //用=0就不行,用>-1就行...,如果没有,就是-1
  163. //nMInput.value = ds1 + linkss.children[i].href + ds2 + nM_V;
  164. var downlink = linkss.children[i].href;
  165. console.log(downlink.value);
  166. }
  167. }
  168. }
  169. /*油管应该是禁了,cmd方式调用IDM下载会报错
  170. nMInput.select(); // 选择对象
  171. document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动`
  172. */
  173. window.open(downlink,"_blank");
  174. nMInput.value = "D:\\下载\\IDM\\00.合并油管\\" + nM_date + '\ ' + nM + ' - DASH_V'+ '.mp4';
  175. //nMInput.select(); // 选择对象
  176. //document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动
  177. };
  178. //7.2音频
  179. addButtonA.onclick = function () {//按钮加event //shadowroot的mode必须是open,否则没有ShadowDOM // 找这里const shadow = shadowHost.attachShadow ? shadowHost.attachShadow({ mode: 'closed' }) <<<<<<<<<<<<<<<<<<<<<<<<<
  180. refreshvar();//刷新全局变量
  181. var xroot = document.getElementById("hahahazijijiade");
  182. var linkss = xroot.shadowRoot.children[0].children[2].children[1].children[1];
  183. var ku = linkss.querySelectorAll("a");
  184. if (ku) {
  185. for (var i = 0; i < ku.length; i++) {
  186. if (linkss.children[i].innerText.indexOf("audio/mp4") > -1) { //用=0就不行,用>-1就行...,如果没有,就是-1
  187. //nMInput.value = ds1 + linkss.children[i].href + ds2 + nM_A;
  188. var downlink = linkss.children[i].href;
  189. console.log(downlink.value);
  190. }
  191. }
  192. }
  193. /*油管应该是禁了,cmd方式调用IDM下载会报错
  194. nMInput.select(); // 选择对象
  195. document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动
  196. */
  197. window.open(downlink,"_blank");
  198. nMInput.value = "D:\\下载\\IDM\\00.合并油管\\" + nM_date + '\ ' + nM + ' - DASH_A'+ '.m4a';
  199. //nMInput.select(); // 选择对象
  200. //document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动
  201. };
  202. //7.3合并
  203. addButtonM.onclick = function () {//按钮加event //shadowroot的mode必须是open,否则没有ShadowDOM
  204. refreshvar();//刷新全局变量
  205. nMInput.value = mg1 + nM_date + '\ ' + nM + mg2 + nM_date + '\ ' + nM + mg3 + nM_date + '\ ' + nM + mg4;
  206. nMInput.select(); // 选择对象
  207. document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动
  208. };
  209. //7.4封面
  210. //网上找的点击下载图片的,原理是canvas重绘
  211. aCover.onclick = function(){
  212. refreshvar();//刷新全局变量
  213. function dIamge(xhref, name) {
  214. var image = new Image();
  215. image.setAttribute('crossOrigin', 'anonymous'); // 解决跨域 Canvas 污染问题
  216. image.src = xhref;
  217. image.onload = function () {
  218. var canvas = document.createElement('canvas');
  219. canvas.width = image.width;
  220. canvas.height = image.height;
  221. var context = canvas.getContext('2d');
  222. context.drawImage(image, 0, 0, image.width, image.height);
  223. var url = canvas.toDataURL('image/png');
  224. var a = document.createElement('a'); // 生成一个a元素
  225. var event = new MouseEvent('click'); // 创建一个单击事件
  226. a.download = name || '下载图片名称'; // 将a的download属性设置为我们想要下载的图片名称,若name不存在则使用‘下载图片名称’作为默认名称
  227. a.href = url; // 将生成的URL设置为a.href属性
  228. a.dispatchEvent(event); // 触发a的单击事件
  229. }
  230. };
  231. //dIamge(src_J, nM);
  232. dIamge(src_J, nM_date +"\ "+ nM + "-thumb");
  233. };
  234. //7.5名称
  235. aTitle.onclick = function () {//按钮加event //shadowroot的mode必须是open,否则没有ShadowDOM
  236. refreshvar();//刷新全局变量
  237. nMInput.value = nM_date +'\ '+ document.querySelector("#container h1 yt-formatted-string").innerText.replace(/[|\\|\/|\:|\*|\?|\"|\<|\>|\|]/g, function (a) {//每一个符号前面都加|\,/[]/g表示全文匹配
  238. switch (a) {
  239. case '\\':
  240. return ' ';
  241. case '/':
  242. return ' ';
  243. case ':':
  244. return ':';
  245. case '*':
  246. return '·';
  247. case '?':
  248. return ' ?';
  249. case '\"':
  250. return '\'';
  251. case '<':
  252. return '《';
  253. case '>':
  254. return '》';
  255. case '|':
  256. return '-';
  257. }
  258. });;
  259. nMInput.select(); // 选择对象
  260. document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动
  261. };
  262. //7.6字幕
  263. /*
  264. aSrt.href = window.location.href.split("www.")[0]+"www.subtitle.to/" + window.location.href.split("www.")[1]; //这样会有下划线
  265. aSrt.target = "_blank";
  266. */
  267. aSrt.onclick = function () {
  268. window.open(window.location.href.split("www.")[0]+"www.subtitle.to/" + window.location.href.split("www.")[1],"_blank");
  269. };
  270. if(!document.getElementById("hahahazijijiade")){
  271. addButtonV.prepend(document.createTextNode(">>需更新<< "));
  272. }
  273. }