Greasy Fork is available in English.

【自用】YoutubeTools

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

  1. // ==UserScript==
  2.  
  3. // @icon https://github.com/favicon.ico
  4. // @name 【自用】YoutubeTools
  5. // @namespace Violentmonkey Scripts
  6. // @match *://*.youtube.com/*
  7. // @grant none
  8. // @version 2024.06.02-01(with LYD v0.9.55)
  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. //找这里const shadowHost = $el('div'),然后加一句 shadowHost.setAttribute("id","hahahazijijiade");
  14. // 找这里const shadow = shadowHost.attachShadow ? shadowHost.attachShadow({ mode: 'closed' })
  15.  
  16. //1.设定加载条件
  17. if (document.getElementById("browser-app") || document.getElementById("masthead")) { //这两个元素,有一个是true,就往下执行
  18. var sx = setInterval(function () { //间隔执行
  19. //console.log(">>>>>>>>>>> 【YoutubeTools】 interval开始 <<<<<<<<<<<");
  20. if (window.location.href.indexOf("watch?v=") < 0) { //如果网址不匹配
  21. return false; //就不执行 【这里只能匹配域名,然后筛,直接用watch的网址,从首页点进去会不触发】
  22. } else {
  23. if (document.getElementById("meta-contents") && document.getElementById("punisher") === null) { //网址匹配的话,punisher没有被添加
  24. StartJS(); //就执行函数,添加punisher
  25. console.log(">>>>>>>>>>> 【YoutubeTools】 已加载 <<<<<<<<<<<");
  26. }
  27. }
  28. }, 3000); //间隔时间,毫秒
  29. //return;
  30. }
  31.  
  32. //2.条件触发后加载
  33. function StartJS() {
  34. //2.1新增按钮的样式
  35. const spancss = `
  36. justify-content:left;
  37. display:flex;
  38. width:100%;
  39. margintop:3px;
  40. padding:1px 0;
  41. `
  42. const btncss = `
  43. color: #F97D00;
  44. font-weight: bold;
  45. /*text-transform: uppercase;*/
  46. font-size:14px;
  47. padding: 0px 8px;
  48. background-color: transparent;
  49. border-color: transparent;
  50. font-family: sans-serif !important;//这个得加,要不字体不一样
  51. `
  52. //2.2开始添加按钮
  53. var buttonDiv = document.createElement("span");
  54. buttonDiv.id = "punisher";
  55. buttonDiv.style.cssText = spancss;
  56. var [addButtonV, addButtonV720,addButtonA, addButtonM] = [document.createElement("button"), document.createElement("button"), document.createElement("button"), document.createElement("button")];
  57. var [aCover, aTitle, apName, aSrt] = [document.createElement("a"), document.createElement("a"), document.createElement("a"), document.createElement("a")];
  58. addButtonV.appendChild(document.createTextNode("V1080p"));
  59. addButtonV720.appendChild(document.createTextNode("V720p"));
  60. addButtonA.appendChild(document.createTextNode("Acode"));
  61. addButtonM.appendChild(document.createTextNode("Merge"));
  62. aCover.appendChild(document.createTextNode("Cover"));
  63. aTitle.appendChild(document.createTextNode("F-name"));
  64. apName.appendChild(document.createTextNode("Path&Name"));
  65. aSrt.appendChild(document.createTextNode("Srt"));
  66. addButtonV.style.cssText = btncss;
  67. addButtonV720.style.cssText = btncss;
  68. addButtonA.style.cssText = btncss;
  69. addButtonM.style.cssText = btncss;
  70. aCover.style.cssText = btncss;
  71. aTitle.style.cssText = btncss;
  72. apName.style.cssText = btncss;
  73. apName.style.cssText = btncss;
  74. aSrt.style.cssText = btncss;
  75. buttonDiv.appendChild(addButtonV);
  76. buttonDiv.appendChild(addButtonV720);
  77. buttonDiv.appendChild(addButtonA);
  78. buttonDiv.appendChild(addButtonM);
  79. buttonDiv.appendChild(aCover);
  80. buttonDiv.appendChild(aTitle);
  81. buttonDiv.appendChild(apName);
  82. buttonDiv.appendChild(aSrt);
  83.  
  84.  
  85. var targetElement = document.querySelectorAll("[id='title']"); //youtube故意的,很多元素id重复,这里够绝,直接全选中,然后按class筛,再加
  86. if (targetElement) {
  87. for (var i = 0; i < targetElement.length; i++) {
  88. if (targetElement[i].className.indexOf("style-scope ytd-watch-metadata") > -1) {
  89. targetElement[i].appendChild(buttonDiv);
  90. }
  91. }
  92. }
  93.  
  94. //3.创建一个input,但是不显示(通过移位),作为复制的中介
  95. var nMInput = document.createElement('input');
  96. nMInput.style.cssText = "position:absolute; top:-200px;"; //火狐实测隐藏的话不能选,oInput.style.display='none';
  97. document.body.appendChild(nMInput);
  98.  
  99.  
  100. //4.生成文件名
  101. var refreshvar = function () {//设置全局变量,随时准备刷新
  102. nMo = document.querySelector("#container h1 yt-formatted-string").innerText;//获取视频名称,下面再把不能作为文件名的符号替换
  103. /*油管应该是禁了,cmd方式调用IDM下载会报错,这里就没必要筛选韩文了
  104. if(nMo.match(/[\uac00-\ud7ff]/gi)){
  105. nMo = "【名称包含韩文,根据视频编号自行修改】" +window.location.href.split("watch?v=")[1];
  106. }
  107. */
  108. nM = nMo.replace(/[|\\|\/|\:|\*|\?|\"|\<|\>|\|]/g, function (a) {//每一个符号前面都加|\,/[]/g表示全文匹配
  109. switch (a) {//就是换成全角的标点,全角标点是用输入法找出来的
  110. case '\\':
  111. return '\';
  112. case '/':
  113. return '/';
  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. }
  129. });
  130. nM_V = '"' + nM + ' - DASH_V' + '"' + '.mp4';
  131. nM_A = '"' + nM + ' - DASH_A' + '"' + '.m4a';
  132. //5.生成封面图的地址和名称
  133. src_J = document.querySelector("#container div.ytp-cued-thumbnail-overlay-image").style.cssText.slice(23, -3);
  134. nM_J = document.querySelector("#container h1 yt-formatted-string").innerText + src_J.split("default")[1];
  135. if (document.querySelector("div#info-strings #dot").nextSibling.innerText.split(" ")[1]) {
  136. nM_dateXX = document.querySelector("div#info-strings #dot").nextSibling.innerText.split(" ")[1];//避免出现“首播开始于”
  137. } else {
  138. nM_dateXX = document.querySelector("div#info-strings #dot").nextSibling.innerText;
  139. }
  140. nM_date = nM_dateXX.split("年")[0] + "-" + nM_dateXX.split("月")[0].split("年")[1].padStart(2, '0') + "-" + nM_dateXX.split("日")[0].split("月")[1].padStart(2, '0');
  141. }
  142. aCover.target = "_blank";
  143. //6.IDM下载命令行所需
  144. const ds1 = `"E:\\Programs\\Internet Download Manager"\\idman.exe / n / d "`
  145. const ds2 = `"/p "E:\\下载\\IDM\\00.合并油管" /f `
  146. const mg1 = `"E:\\Programs\\视频编辑\\YouTube 音视频分离合并\\64 位\\ffmpeg" -i "E:\\下载\\IDM\\00.合并油管\\`
  147. const mg2 = ` - DASH_A".m4a -i "E:\\下载\\IDM\\00.合并油管\\`
  148. const mg3 = ` - DASH_V".mp4 -acodec copy -vcodec copy "E:\\下载\\IDM\\00.合并油管\\`
  149. const mg4 = `".mp4`
  150.  
  151. //7.1视频
  152. addButtonV.onclick = function () { //按钮加event //shadowroot的mode必须是open,否则没有ShadowDOM
  153. refreshvar(); //刷新全局变量
  154. nMInput.value = "E:\\下载\\IDM\\00.合并油管\\" + nM_date + '\ ' + nM + ' - DASH_V' + '.mp4';
  155. nMInput.select(); // 选择对象
  156. document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动
  157. //只能先复制,要不打开新窗口容易失去焦点,然后没法复制,V99开始
  158. var xroot = document.getElementById("hahahazijijiade"); //找这里const shadowHost = $el('div'),然后加一句 shadowHost.setAttribute("id","hahahazijijiade"); <<<<<<<<<<<<<<<<<<<<<<<<<
  159. var linkss = xroot.shadowRoot.children[0].children[2].children[1].children[1];
  160. var ku = linkss.querySelectorAll("a");
  161.  
  162. if (ku) {
  163. for (var i = 0; i < ku.length; i++) {
  164. if (linkss.innerHTML.indexOf("1080p") > -1) { //先看看1080p有没有,不能用linkss.children[i].innerText.indexOf("1080p") > -1作为条件判断
  165. if (linkss.children[i].innerText.indexOf("1080p") > -1 && linkss.children[i].innerText.indexOf("video/mp4") > -1 && linkss.children[i].innerText.indexOf("avc1.") > -1) { //用=0就不行,用>-1就行...,如果没有,就是-1,
  166. //nMInput.value = ds1 + linkss.children[i].href + ds2 + nM_V;
  167. var downlink = linkss.children[i].href;
  168. console.log('下面是1080p的链接');
  169. console.log(downlink);
  170. }
  171. } else if (linkss.children[i].innerText.indexOf("720p") > -1 && linkss.children[i].innerText.indexOf("video/mp4") > -1 && linkss.children[i].innerText.indexOf("avc1.") > -1) { //用=0就不行,用>-1就行...,如果没有,就是-1,没有再下720p
  172. //nMInput.value = ds1 + linkss.children[i].href + ds2 + nM_V;
  173. var downlink = linkss.children[i].href;
  174. console.log('下面是720p的链接');
  175. console.log(downlink);
  176. }
  177. }
  178. }
  179.  
  180. window.open(downlink, "_blank");
  181. };
  182.  
  183. addButtonV720.onclick = function () { //按钮加event //shadowroot的mode必须是open,否则没有ShadowDOM
  184. refreshvar(); //刷新全局变量
  185. nMInput.value = "E:\\下载\\IDM\\00.合并油管\\" + nM_date + '\ ' + nM + ' - DASH_V' + '.mp4';
  186. nMInput.select(); // 选择对象
  187. document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动
  188. //只能先复制,要不打开新窗口容易失去焦点,然后没法复制,V99开始
  189. var xroot = document.getElementById("hahahazijijiade"); //找这里const shadowHost = $el('div'),然后加一句 shadowHost.setAttribute("id","hahahazijijiade"); <<<<<<<<<<<<<<<<<<<<<<<<<
  190. var linkss = xroot.shadowRoot.children[0].children[2].children[1].children[1];
  191. var ku = linkss.querySelectorAll("a");
  192.  
  193. if (ku) {
  194. for (var i = 0; i < ku.length; i++) {
  195. if (linkss.innerHTML.indexOf("720p") > -1) { //先看看1080p有没有,不能用linkss.children[i].innerText.indexOf("1080p") > -1作为条件判断
  196. if (linkss.children[i].innerText.indexOf("720p") > -1 && linkss.children[i].innerText.indexOf("video/mp4") > -1 && linkss.children[i].innerText.indexOf("avc1.") > -1) { //用=0就不行,用>-1就行...,如果没有,就是-1,
  197. //nMInput.value = ds1 + linkss.children[i].href + ds2 + nM_V;
  198. var downlink = linkss.children[i].href;
  199. console.log('下面是720p的链接');
  200. console.log(downlink);
  201. }
  202. }
  203. }
  204. }
  205. window.open(downlink, "_blank");
  206. };
  207.  
  208. //7.2音频
  209. addButtonA.onclick = function () {//按钮加event //shadowroot的mode必须是open,否则没有ShadowDOM // 找这里const shadow = shadowHost.attachShadow ? shadowHost.attachShadow({ mode: 'closed' }) <<<<<<<<<<<<<<<<<<<<<<<<<
  210. refreshvar();//刷新全局变量
  211. nMInput.value = "E:\\下载\\IDM\\00.合并油管\\" + nM_date + '\ ' + nM + ' - DASH_A' + '.m4a';
  212. nMInput.select(); // 选择对象
  213. document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动
  214. //只能先复制,要不打开新窗口容易失去焦点,然后没法复制,V99开始
  215. var xroot = document.getElementById("hahahazijijiade");
  216. var linkss = xroot.shadowRoot.children[0].children[2].children[1].children[1];
  217. var ku = linkss.querySelectorAll("a");
  218. if (ku) {
  219. for (var i = 0; i < ku.length; i++) {
  220. //alert(linkss.children[5].innerText.indexOf("audio"));
  221. if (ku[i].innerText.indexOf("audio/mp4")> -1) { //用=0就不行,用>-1就行...,如果没有,就是-1
  222. var downlink = ku[i].href;//用linkss.children[i]会出问题,具体情况是audio只有一个元素时
  223. console.log(downlink);
  224. }
  225. }
  226. }
  227. window.open(downlink, "_blank");
  228.  
  229. };
  230. //7.3合并
  231. addButtonM.onclick = function () {//按钮加event //shadowroot的mode必须是open,否则没有ShadowDOM
  232. refreshvar();//刷新全局变量
  233. nMInput.value = mg1 + nM_date + '\ ' + nM + mg2 + nM_date + '\ ' + nM + mg3 + nM_date + '\ ' + nM + mg4;
  234. nMInput.select(); // 选择对象
  235. document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动
  236. };
  237. //7.4封面
  238. //网上找的点击下载图片的,原理是canvas重绘
  239. aCover.onclick = function () {
  240. refreshvar();//刷新全局变量
  241. function dIamge(xhref, name) {
  242. var image = new Image();
  243. image.setAttribute('crossOrigin', 'anonymous'); // 解决跨域 Canvas 污染问题
  244. image.src = xhref;
  245. image.onload = function () {
  246. var canvas = document.createElement('canvas');
  247. canvas.width = image.width;
  248. canvas.height = image.height;
  249. var context = canvas.getContext('2d');
  250. context.drawImage(image, 0, 0, image.width, image.height);
  251. var url = canvas.toDataURL('image/png');
  252. var a = document.createElement('a'); // 生成一个a元素
  253. var event = new MouseEvent('click'); // 创建一个单击事件
  254. a.download = name || '下载图片名称'; // 将a的download属性设置为我们想要下载的图片名称,若name不存在则使用‘下载图片名称’作为默认名称
  255. a.href = url; // 将生成的URL设置为a.href属性
  256. a.dispatchEvent(event); // 触发a的单击事件
  257. }
  258. };
  259. nMInput.value = nM_date + '\ ' + document.querySelector("#container h1 yt-formatted-string").innerText.replace(/[|\\|\/|\:|\*|\?|\"|\<|\>|\|]/g, function (a) {//每一个符号前面都加|\,/[]/g表示全文匹配
  260. switch (a) {
  261. case '\\':
  262. return ' ';
  263. case '/':
  264. return ' ';
  265. case ':':
  266. return ':';
  267. case '*':
  268. return '·';
  269. case '?':
  270. return ' ?';
  271. case '\"':
  272. return '\'';
  273. case '<':
  274. return '《';
  275. case '>':
  276. return '》';
  277. case '|':
  278. return '-';
  279. }
  280. });;
  281. //dIamge(src_J, nM);
  282. var thumbJ = nMInput.value + '-thumb';
  283. //alert(thumbJ);
  284. dIamge(src_J, thumbJ);
  285. };
  286. //7.5名称
  287. aTitle.onclick = function () {//按钮加event //shadowroot的mode必须是open,否则没有ShadowDOM
  288. refreshvar();//刷新全局变量
  289. nMInput.value = nM_date + '\ ' + document.querySelector("#container h1 yt-formatted-string").innerText.replace(/[|\\|\/|\:|\*|\?|\"|\<|\>|\|]/g, function (a) {//每一个符号前面都加|\,/[]/g表示全文匹配
  290. switch (a) {
  291. case '\\':
  292. return ' ';
  293. case '/':
  294. return ' ';
  295. case ':':
  296. return ':';
  297. case '*':
  298. return '·';
  299. case '?':
  300. return ' ?';
  301. case '\"':
  302. return '\'';
  303. case '<':
  304. return '《';
  305. case '>':
  306. return '》';
  307. case '|':
  308. return '-';
  309. }
  310. });;
  311. nMInput.select(); // 选择对象
  312. document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动
  313. };
  314. apName.onclick = function () {//按钮加event //shadowroot的mode必须是open,否则没有ShadowDOM
  315. refreshvar();//刷新全局变量
  316. nMInput.value = "E:\\下载\\IDM\\00.合并油管\\" + nM_date + '\ ' + document.querySelector("#container h1 yt-formatted-string").innerText.replace(/[|\\|\/|\:|\*|\?|\"|\<|\>|\|]/g, function (a) {//每一个符号前面都加|\,/[]/g表示全文匹配
  317. switch (a) {
  318. case '\\':
  319. return ' ';
  320. case '/':
  321. return ' ';
  322. case ':':
  323. return ':';
  324. case '*':
  325. return '·';
  326. case '?':
  327. return ' ?';
  328. case '\"':
  329. return '\'';
  330. case '<':
  331. return '《';
  332. case '>':
  333. return '》';
  334. case '|':
  335. return '-';
  336. }
  337. });;
  338. nMInput.select(); // 选择对象
  339. document.execCommand("Copy"); // 执行浏览器复制命令,火狐里面这个command只能是用户触发,不能自动
  340. };
  341. //7.6字幕
  342. /*
  343. aSrt.href = window.location.href.split("www.")[0]+"www.subtitle.to/" + window.location.href.split("www.")[1]; //这样会有下划线
  344. aSrt.target = "_blank";
  345. */
  346. aSrt.onclick = function () {
  347. window.open(window.location.href.split("www.")[0] + "www.subtitle.to/" + window.location.href.split("www.")[1], "_blank");
  348. };
  349.  
  350.  
  351. if (!document.getElementById("hahahazijijiade")) {
  352. addButtonV.prepend(document.createTextNode(">>需更新<< "));
  353. }
  354. }