Greasy Fork is available in English.

网易云音乐列表导出

导出当前页网易云音乐列表为文本

  1. // ==UserScript==
  2. // @name 网易云音乐列表导出
  3. // @namespace undefined
  4. // @version 0.0.4
  5. // @description 导出当前页网易云音乐列表为文本
  6. // @author allen smith
  7. // @match *://music.163.com/*
  8. // @require https://cdn.bootcss.com/clipboard.js/1.7.1/clipboard.js
  9. // @require https://cdn.bootcdn.net/ajax/libs/Sortable/1.15.0/Sortable.min.js
  10. // @run-at document-end
  11. // @grant none
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. 'use strict';
  16.  
  17. // 检测页面
  18. let htm = document.getElementsByClassName('f-oh');
  19. if(htm.length === 0){
  20. return;
  21. }
  22.  
  23. // 创建dom节点
  24. function createDocument(txt) {
  25. const template = `<div class='childdom'>${txt}</div>`;
  26. let doc = new DOMParser().parseFromString(template, 'text/html');
  27. let div = doc.querySelector('.childdom');
  28. return div;
  29. }
  30.  
  31. // 检测文档变动
  32. let doc = document.getElementById('g_mymusic');
  33. let _body = document.body;
  34. let clipboard, btn, spli, interId, waitTimeoutId, wait ;
  35. let ckdiv;
  36. let check1, check2, check3, check4, check5;
  37. let sortdiv;
  38. doc.addEventListener('DOMSubtreeModified', function () {
  39.  
  40. //查找列表动画
  41. wait = document.getElementById('wait-animation');
  42. if(wait) _body.removeChild(wait);
  43. wait = document.createElement("span");
  44. wait.id = 'wait-animation';
  45. wait.setAttribute('style', 'display:inline-block;position:absolute;right:50px;top:100px;padding:3px 5px;border:1px solid lightgray;background-color:white;color:black;border-radius:5px;font-size:14px;');
  46. _body.appendChild(wait);
  47. wait.innerHTML = '导出:没有合适的列表';
  48.  
  49. //检测列表
  50. let list = document.getElementsByClassName('m-table')[0];
  51. if (!list) {
  52. btn = document.getElementById('export-btn');
  53. spli = document.getElementById('export-spli');
  54. if(btn) _body.removeChild(btn);
  55. if(spli) _body.removeChild(spli);
  56. return;
  57. }
  58. _body.removeChild(wait);
  59.  
  60. //创建按钮
  61. btn = null;
  62. spli = null;
  63. btn = document.getElementById('export-btn');
  64. spli = document.getElementById('export-spli');
  65. if (!spli) {
  66. spli = document.createElement("input");
  67. spli.id = 'export-spli';
  68. spli.className = 'export-spli';
  69. spli.setAttribute('placeholder','自定义分隔符(默认 -- )');
  70. spli.setAttribute('style', 'display:inline-block;position:absolute;right:50px;top:100px;padding:3px 5px;border:1px solid lightgray;background-color:white;color:black;border-radius:5px;font-size:14px;');
  71. _body.appendChild(spli);
  72. }
  73. if (!btn) {
  74. btn = document.createElement("button");
  75. btn.id = 'export-btn';
  76. btn.className = 'export-btn';
  77. btn.innerText = '导出列表';
  78. btn.setAttribute('style', 'display:inline-block;position:absolute;right:50px;top:229px;padding:3px 5px;border:1px solid lightgray;background-color:white;color:black;border-radius:5px;font-size:14px;');
  79. _body.appendChild(btn);
  80. }
  81. // 选择列
  82. if(!ckdiv){
  83. ckdiv = document.createElement("div");
  84. ckdiv.id = 'ckdiv';
  85. ckdiv.className = 'export-ck';
  86. ckdiv.setAttribute('style', 'display:inline-block;position:absolute;right:50px;top:128px;padding:3px 5px;border:1px solid lightgray;background-color:white;color:black;border-radius:5px;font-size:14px;');
  87. _body.appendChild(ckdiv);
  88. }
  89. // 排序
  90. if(!sortdiv){
  91. // sortdiv = document.createElement("div");
  92. // sortdiv.id = 'sortdiv';
  93. // sortdiv.className = 'sortdiv';
  94. // sortdiv.setAttribute('style', 'display:inline-block;position:absolute;right:50px;top:156px;padding:3px 5px;border:1px solid lightgray;background-color:white;color:black;border-radius:5px;font-size:14px;');
  95.  
  96. let divstr = `<div id="sortdivbox" style="display:inline-block;position:absolute;right:50px;top:159px;padding:3px 5px;border:1px solid lightgray;background-color:white;color:black;border-radius:5px;font-size:14px;"><div>拖动以排序</div><div id="sortdiv" style="margin:10px 0px;cursor:pointer;"><span style="margin:0 4px;padding:4px 8px;border-radius:3px;border:1px solid lightgray">歌名</span><span style="margin:0 4px;padding:4px 8px;border-radius:3px;border:1px solid lightgray">歌手</span><span style="margin:0 4px;padding:4px 8px;border-radius:3px;border:1px solid lightgray">专辑</span><span style="margin:0 4px;padding:4px 8px;border-radius:3px;border:1px solid lightgray">时长</span><span style="margin:0 4px;padding:4px 8px;border-radius:3px;border:1px solid lightgray">链接</span></div></div>`
  97. _body.appendChild(createDocument(divstr));
  98. sortdiv = new Sortable(document.querySelector('#sortdiv'))
  99. }
  100.  
  101. let ckbuilder = function(id, label, uncheck, readonly){
  102. let tmpid = 'ck_' + id;
  103. let ckbox = document.createElement("input");
  104. ckbox.id = tmpid
  105. ckbox.setAttribute('type', 'checkbox');
  106. ckbox.setAttribute('style', 'vertical-align: middle;margin-top: -2px;');
  107. if(!uncheck) ckbox.checked = true;
  108. if(!!readonly) ckbox.setAttribute("disabled", "disabled");
  109. ckdiv.appendChild(ckbox);
  110.  
  111. let ckspn = document.createElement("label");
  112. ckspn.setAttribute('for', tmpid);
  113. ckspn.innerHTML = ' ' + label;
  114. ckdiv.appendChild(ckspn);
  115. return ckbox;
  116. }
  117. if(!check1){
  118. check1 = ckbuilder("ck01","歌名 ", false, true);
  119. }
  120. if(!check2){
  121. check2 = ckbuilder("ck02","歌手 ");
  122. }
  123. if(!check3){
  124. check3 = ckbuilder("ck03","专辑 ", true);
  125. }
  126. if(!check4){
  127. check4 = ckbuilder("ck04","时长 ", true);
  128. }
  129. if(!check5){
  130. check5 = ckbuilder("ck05","链接", true);
  131. }
  132.  
  133.  
  134. //创建剪贴板
  135. if (clipboard) clipboard.destroy();
  136. clipboard = new Clipboard('.export-btn', {
  137. text: function (trigger) {
  138.  
  139. //导出列表
  140. btn.innerText = '正在导出 ...';
  141. let result = '';
  142. let listBody = list.getElementsByTagName('tbody')[0];
  143. let rows = listBody.getElementsByTagName('tr');
  144. for (let i = 0; i < rows.length; i++) {
  145. let ele = rows[i];
  146. let cells = ele.getElementsByTagName('td');
  147. let name = cells[1].getElementsByTagName('b')[0].getAttribute('title').replace(/<div class="soil">[\s\S\n]*?<\/div>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&");
  148. let link = `https://music.163.com/#${cells[1].getElementsByTagName('a')[0].getAttribute('href')}`
  149. let time = cells[2].querySelector('.u-dur').innerText;
  150. let artist = cells[3].getElementsByTagName('span')[0].getAttribute('title').replace(/<div class="soil">[\s\S\n]*?<\/div>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&");
  151. let album = cells[4].getElementsByTagName('a')[0].getAttribute('title').replace(/<div class="soil">[\s\S\n]*?<\/div>/g, "").replace(/&nbsp;/g, " ").replace(/&amp;/g, "&");
  152.  
  153. let spliChar = spli.value;
  154. if(!spliChar) spliChar = ' -- ';
  155.  
  156.  
  157. let isFirst = true;
  158. document.querySelectorAll('#sortdiv span').forEach(item=>{
  159. let type = item.innerText;
  160. let tempSplit;
  161. if(isFirst){
  162. tempSplit = ()=> {isFirst = false; return "";}
  163. }else {
  164. tempSplit = ()=> spliChar;
  165. }
  166. switch(type){
  167. case "歌名":
  168. result += tempSplit() + name;
  169. break;
  170. case "歌手":
  171. if(check2.checked){
  172. result += tempSplit() + artist;
  173. }
  174. break;
  175. case "专辑":
  176. if(check3.checked){
  177. result += tempSplit() + album;
  178. }
  179. break;
  180. case "时长":
  181. if(check4.checked){
  182. result += tempSplit() + time;
  183. }
  184. break;
  185. case "链接":
  186. if(check5.checked){
  187. result += tempSplit() + link;
  188. }
  189. break;
  190. }
  191. })
  192. result += '\r\n';
  193. }
  194.  
  195. //提示动画
  196. btn.innerText = '已复制到剪贴板 =';
  197. let count = 6;
  198. clearInterval(interId);
  199. interId = setInterval(function () {
  200. count--;
  201. if (count > 0){
  202. btn.innerText = '已复制到剪贴板 ' + waitAnimationChar(count);
  203. }
  204. else{
  205. btn.innerText = '导出列表';
  206. clearInterval(interId);
  207. }
  208. }, 300);
  209.  
  210. //输出到控制台
  211. console.log(result);
  212. //输出到剪贴板
  213. trigger.setAttribute('aria-label', result);
  214. return trigger.getAttribute('aria-label');
  215. }
  216. });
  217. });
  218. //字符动画
  219. let waitAnimationChar = function(n){
  220. let temp = n % 3;
  221. if(temp === 0) return '#';
  222. else if(temp == 1) return '$';
  223. else if(temp == 2) return '+';
  224. };
  225. })();