网易云音乐-MyFreeMP3扩展

利用MyFreeMP3扩展网易云音乐功能

目前为 2022-11-02 提交的版本。查看 最新版本

  1. /* eslint-disable no-multi-spaces */
  2. /* eslint-disable dot-notation */
  3.  
  4. // ==UserScript==
  5. // @name 网易云音乐-MyFreeMP3扩展
  6. // @name:zh-CN 网易云音乐-MyFreeMP3扩展
  7. // @name:en Netease Music - MyFreeMP3 Extender
  8. // @namespace 163Music-MyFreeMP3-Extender
  9. // @version 1.0
  10. // @description 利用MyFreeMP3扩展网易云音乐功能
  11. // @description:zh-CN 利用MyFreeMP3扩展网易云音乐功能
  12. // @description:en Extend netease music with MyFreeMP3
  13. // @author PY-DNG
  14. // @license GPL-v3
  15. // @match http*://music.163.com/*
  16. // @connect 59.110.45.28
  17. // @connect music.163.net
  18. // @connect music.126.net
  19. // @icon https://s1.music.126.net/style/favicon.ico
  20. // @grant GM_xmlhttpRequest
  21. // @grant GM_download
  22. // @run-at document-start
  23. // @noframes
  24. // ==/UserScript==
  25.  
  26. (function __MAIN__() {
  27. 'use strict';
  28. const CONST = {
  29. Text: {
  30. V5NOCANQU: '要听龙叔的话,V5不能屈,听完歌快去给你网易爸爸充VIP吧'
  31. },
  32. Number: {
  33. Interval_Fastest: 1,
  34. Interval_Fast: 50,
  35. Interval_Balanced: 500,
  36. MaxSearchPage: 3,
  37. }
  38. }
  39.  
  40. // Prepare
  41. const md5Script = document.createElement('script');
  42. md5Script.src = 'https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.18.0/js/md5.js';
  43. document.head.appendChild(md5Script);
  44.  
  45. // Arguments: level=LogLevel.Info, logContent, asObject=false
  46. // Needs one call "DoLog();" to get it initialized before using it!
  47. function DoLog() {
  48. const win = typeof unsafeWindow === 'object' ? unsafeWindow : window;
  49.  
  50. // Global log levels set
  51. win.LogLevel = {
  52. None: 0,
  53. Error: 1,
  54. Success: 2,
  55. Warning: 3,
  56. Info: 4,
  57. }
  58. win.LogLevelMap = {};
  59. win.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'}
  60. win.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'}
  61. win.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'}
  62. win.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'}
  63. win.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'}
  64. win.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'}
  65.  
  66. // Current log level
  67. DoLog.logLevel = (win.isPY_DNG && win.userscriptDebugging) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error
  68.  
  69. // Log counter
  70. DoLog.logCount === undefined && (DoLog.logCount = 0);
  71. if (++DoLog.logCount > 512) {
  72. console.clear();
  73. DoLog.logCount = 0;
  74. }
  75.  
  76. // Get args
  77. let level, logContent, asObject;
  78. switch (arguments.length) {
  79. case 1:
  80. level = LogLevel.Info;
  81. logContent = arguments[0];
  82. asObject = false;
  83. break;
  84. case 2:
  85. level = arguments[0];
  86. logContent = arguments[1];
  87. asObject = false;
  88. break;
  89. case 3:
  90. level = arguments[0];
  91. logContent = arguments[1];
  92. asObject = arguments[2];
  93. break;
  94. default:
  95. level = LogLevel.Info;
  96. logContent = 'DoLog initialized.';
  97. asObject = false;
  98. break;
  99. }
  100.  
  101. // Log when log level permits
  102. if (level <= DoLog.logLevel) {
  103. let msg = '%c' + LogLevelMap[level].prefix;
  104. let subst = LogLevelMap[level].color;
  105.  
  106. if (asObject) {
  107. msg += ' %o';
  108. } else {
  109. switch(typeof(logContent)) {
  110. case 'string': msg += ' %s'; break;
  111. case 'number': msg += ' %d'; break;
  112. case 'object': msg += ' %o'; break;
  113. }
  114. }
  115.  
  116. console.log(msg, subst, logContent);
  117. }
  118. }
  119. DoLog();
  120.  
  121. main();
  122. function main() {
  123. // Wait for document.body
  124. if (!document.body) {
  125. setTimeout(main, CONST.Number.Interval_Fast);
  126. return false;
  127. }
  128.  
  129. // Commons
  130. hookPlay();
  131.  
  132. // Page functions
  133. const ITM = new IntervalTaskManager();
  134. const pageChangeDetecter = (function(callback, emitOnInit=false) {
  135. let href = location.href;
  136. emitOnInit && callback(null, href);
  137. return function detecter() {
  138. const new_href = location.href;
  139. if (href !== new_href) {
  140. callback(href, new_href);
  141. href = new_href;
  142. }
  143. }
  144. }) (deliverPageFuncs, true);
  145. ITM.time = CONST.Number.Interval_Fast;
  146. ITM.addTask(pageChangeDetecter);
  147. ITM.start();
  148.  
  149. function deliverPageFuncs(href, new_href) {
  150. const pageFuncs = [{
  151. reg: /^https?:\/\/music\.163\.com\/#\/song\?.+$/,
  152. func: pageSong,
  153. checker: function() {
  154. const ifr = $('#g_iframe'); if (!ifr) {return false;}
  155. const oDoc = ifr.contentDocument; if (!oDoc) {return false;}
  156. const elm = !!$(oDoc, '.cnt>.m-info');
  157. return elm;
  158. }
  159. },{
  160. reg: /^https?:\/\/music\.163\.com\/#\/(artist|album|discover\/toplist)\?.+$/,
  161. func: replacePredata,
  162. sync: false
  163. },{
  164. reg: /^https?:\/\/music\.163\.com\//,
  165. func: listDownload,
  166. checker: function() {
  167. const ifr = $('#g_iframe'); if (!ifr) {return false;}
  168. const oDoc = ifr.contentDocument; if (!oDoc) {return false;}
  169. return !!oDoc.body;
  170. }
  171. },{
  172. reg: /^https?:\/\/music\.163\.com\//,
  173. func: playlistDownload
  174. },{
  175. reg: /^https?:\/\/music\.163\.com\/#\/album\?.+$/,
  176. func: pageAlbum,
  177. checker: function() {
  178. const ifr = $('#g_iframe'); if (!ifr) {return false;}
  179. const oDoc = ifr.contentDocument; if (!oDoc) {return false;}
  180. const elm = !!$(oDoc, '#content-operation');
  181. return elm;
  182. }
  183. }];
  184. for (const pageFunc of pageFuncs) {
  185. test_exec(pageFunc);
  186. }
  187.  
  188. function test_exec(pageFunc) {
  189. pageFunc.reg.test(location.href) && ((((pageFunc.sync || !pageFunc.hasOwnProperty('sync')) ? iframeDocSync() : true) && (pageFunc.checker ? ({
  190. 'string': () => ($(pageFunc.checker)),
  191. 'function': pageFunc.checker,
  192. })[typeof pageFunc.checker]() : true)) ? true : (setTimeout(test_exec.bind(null, pageFunc), CONST.Number.Interval_Balanced), DoLog('waiting: ' + location.href), false)) && pageFunc.func(href, new_href);
  193. }
  194. }
  195. }
  196.  
  197. function hookPlay() {
  198. // Play
  199. try {
  200. const RATES = {
  201. 'none': 0,
  202. 'standard': 128000,
  203. 'lossless': 999000,
  204. };
  205. const APIH = new APIHooker();
  206.  
  207. let dlLevel, dlRate, plLevel, plRate;
  208. APIH.hook(/^https?:\/\/music\.163\.com\/weapi\/v3\/song\/detail(\?[a-zA-Z0-9=_]+)?$/, function(xhr) {
  209. const json = JSON.parse(xhr.response);
  210. const privilege = json['privileges'][0];
  211. dlLevel = privilege['downloadMaxBrLevel'];
  212. dlRate = RATES[dlLevel];
  213. plLevel = privilege['playMaxBrLevel'];
  214. plRate = RATES[plLevel];
  215. privilege['dlLevel'] = dlLevel;
  216. privilege['dl'] = dlRate;
  217. privilege['plLevel'] = plLevel;
  218. privilege['pl'] = plRate;
  219. const response = JSON.stringify(json)
  220. const propDesc = {
  221. value: response,
  222. writable: false,
  223. configurable: false,
  224. enumerable: true
  225. }
  226. Object.defineProperties(xhr, {
  227. 'response': propDesc,
  228. 'responseText': propDesc
  229. })
  230. return true;
  231. });
  232. APIH.hook(/^\/weapi\/song\/enhance\/player\/url\/v1(\?[a-zA-Z0-9=_]+)?$/, function(xhr, _this, args, onreadystatechange) {
  233. const ifr = $('#g_iframe');
  234. const oDoc = ifr.contentDocument;
  235.  
  236. // Get data
  237. const json = JSON.parse(xhr.response);
  238. const data = json['data'][0];
  239. const name = $('.play a.name').innerText;
  240. const artist = $('.play .by>span').children[0].innerText;
  241. const fname = replaceText('{$NAME} - {$ARTIST}', {'{$NAME}': name, '{$ARTIST}': artist});
  242. const cover = $('.m-playbar .head img').src;
  243. const cpath = getUrlPath(cover);
  244.  
  245. // Only hook unplayable songs
  246. if (data['url']) {return true};
  247.  
  248. search({
  249. text: fname,
  250. callback: function(s_json) {
  251. const list = s_json.data.list;
  252. const song = list.find(function(song) {
  253. // Search result
  254. const qualities = [2000, 320, 128];
  255. const q = qualities.find((q) => (song.quality.includes(q)));
  256. const s_url = song[({
  257. 2000: 'url_flac',
  258. 320: 'url_320',
  259. 128: 'url_128'
  260. })[q]];
  261. const s_ftype = ({
  262. 2000: 'flac',
  263. 320: 'mp3',
  264. 128: 'mp3'
  265. })[q];
  266. const s_lrc = song.lrc;
  267. const s_cover = song.cover;
  268. const s_name = song.name;
  269. const s_artist = song.artist;
  270. const s_fname = name + ' - ' + artist;
  271. const s_cpath = getUrlPath(s_cover);
  272.  
  273. if (s_cpath === cpath) {
  274. // Song found, request final url
  275. song.url = s_url;
  276. return true;
  277. }
  278. }) || list[0];
  279. const abort = GM_xmlhttpRequest({
  280. method: 'GET',
  281. url: song.url,
  282. onprogress: function(e) {
  283. abort();
  284. // modify xhr and continue stack
  285. data['code'] = 200;
  286. data['br'] = plRate;
  287. data['level'] = plLevel;
  288. data['type'] = 'mp3';
  289. data['url'] = e.finalUrl;
  290. const response = JSON.stringify(json);
  291. const propDesc = {
  292. value: response,
  293. writable: false,
  294. configurable: true,
  295. enumerable: true
  296. };
  297. Object.defineProperties(xhr, {
  298. 'response': propDesc,
  299. 'responseText': propDesc
  300. });
  301. continueStack();
  302. }
  303. }).abort
  304. },
  305. });
  306.  
  307. // Suspend stack until search & find the song
  308. return false;
  309.  
  310. function continueStack() {
  311. onreadystatechange.apply(_this, args);;
  312. }
  313. });
  314. } catch (err) {
  315. console.error(err);
  316. DoLog(LogLevel.Error, 'hooking error');
  317. }
  318. }
  319.  
  320. function listDownload() {
  321. const iframe = $('#g_iframe');
  322. const oDoc = iframe.contentDocument;
  323. const body = oDoc.body;
  324. if (!body) {
  325. DoLog(LogLevel.Warning, 'listDownload: list not found');
  326. return false;
  327. }
  328.  
  329. const AEL = getPureAEL();
  330. AEL.call(body, 'click', function(e) {
  331. const elm = e.target;
  332. if (elm.getAttribute('data-res-action') === 'download') {
  333. e.stopPropagation();
  334. const elm_share = elm.previousElementSibling;
  335.  
  336. const name = elm_share.getAttribute('data-res-name');
  337. const artist = elm_share.getAttribute('data-res-author');
  338. const cover = elm_share.getAttribute('data-res-pic');
  339. downloadSong(name, artist, cover);
  340. }
  341. }, {capture: true});
  342. }
  343.  
  344. function playlistDownload() {
  345. const AEL = getPureAEL();
  346. AEL.call(document.body, 'click', function(e) {
  347. const elm = e.target;
  348. if (elm.getAttribute('data-action') === 'download') {
  349. e.stopPropagation();
  350. const li = elm.parentElement.parentElement.parentElement;
  351.  
  352. const name = $(li, '.col-2').innerText;
  353. const artist = $(li, '.col-4').innerText;
  354. downloadSong(name, artist);
  355. }
  356. }, {capture: true});
  357. }
  358.  
  359. function pageSong() {
  360. const ifr = $('#g_iframe');
  361. const oDoc = ifr.contentDocument;
  362. const name = $(oDoc, '.tit>em').innerText;
  363. const artist = $(oDoc, '.cnt>.des>span>a').innerText;
  364. const cover = $(oDoc, '.u-cover>img.j-img').src;
  365. const AEL = getPureAEL();
  366.  
  367. // GUI
  368. if ($(oDoc, '.vip-song')) {
  369. const content_operation = $(oDoc, '#content-operation');
  370. const vip_group = $(content_operation, '.u-vip-btn-group');
  371. const vip_play = $(vip_group, 'a[data-res-action="play"]');
  372. const vip_add = $(vip_group, 'a[data-res-action="addto"]');
  373.  
  374. // Style
  375. vip_play.classList.remove('u-btni-vipply');
  376. vip_play.classList.add('u-btni-addply');
  377. vip_add.classList.remove('u-btni-vipadd');
  378. vip_add.classList.add('u-btni-add');
  379. content_operation.insertAdjacentElement('afterbegin', vip_add);
  380. content_operation.insertAdjacentElement('afterbegin', vip_play);
  381. content_operation.removeChild(vip_group);
  382.  
  383. // Text
  384. vip_play.title = CONST.Text.V5NOCANQU;
  385. vip_play.children[0].childNodes[1].nodeValue = '播放';
  386. }
  387.  
  388. // Download
  389. const dlButton = $(oDoc, '#content-operation>a[data-res-action="download"]');
  390. AEL.call(dlButton, 'click', dlOnclick, {useCapture: true});
  391.  
  392. function dlOnclick(e) {
  393. e.stopPropagation();
  394. downloadSong(name, artist, cover);
  395. }
  396. }
  397.  
  398. function pageAlbum() {
  399. const iframe = $('#g_iframe');
  400. const oDoc = iframe.contentDocument;
  401. const oWin = iframe.contentWindow;
  402.  
  403. // GUI
  404. if ($(oDoc, '.vip-album')) {
  405. const content_operation = $(oDoc, '#content-operation');
  406. const vip_group = $(content_operation, '.u-vip-btn-group');
  407. const vip_play = $(vip_group, 'a[data-res-action="play"]');
  408. const vip_add = $(vip_group, 'a[data-res-action="addto"]');
  409.  
  410. // Style
  411. vip_play.classList.remove('u-btni-vipply');
  412. vip_play.classList.add('u-btni-addply');
  413. vip_add.classList.remove('u-btni-vipadd');
  414. vip_add.classList.add('u-btni-add');
  415. content_operation.insertAdjacentElement('afterbegin', vip_add);
  416. content_operation.insertAdjacentElement('afterbegin', vip_play);
  417. content_operation.removeChild(vip_group);
  418.  
  419. // Text
  420. vip_play.title = CONST.Text.V5NOCANQU;
  421. vip_play.children[0].childNodes[1].nodeValue = '播放';
  422. }
  423. }
  424.  
  425. function replacePredata() {
  426. const iframe = $('#g_iframe');
  427. const oDoc = iframe.contentDocument;
  428. const oWin = iframe.contentWindow;
  429. const envReady = oDoc && iframeDocSync();
  430. const elmData = oDoc && $(oDoc, '#song-list-pre-data');
  431. if (!elmData) {
  432. // No elmData found.
  433. if (envReady && $(oDoc, '#song-list-pre-cache table')) {
  434. // Too late. Data has already been dealed.
  435. DoLog(LogLevel.Error, 'Predata hook failed.');
  436. DoLog([$(oDoc, '#song-list-pre-cache table'), oDoc.URL, oWin.location.href]);
  437. } else {
  438. // Data has not been loaded!
  439. DoLog('No predata found');
  440. if (envReady) {
  441. // Hook Element.prototype.getElementsByTagName to make changeValue called.
  442. DoLog('Environment ready, hooking getElementsByTagName...');
  443. const hooker = new Hooker();
  444. const id = hooker.hook(oWin, 'Element.prototype.getElementsByTagName', false, false, {
  445. dealer: function(_this, args) {
  446. if (_this.id === 'song-list-pre-cache' && args[0] === 'textarea') {
  447. const elmData = $(_this, 'textarea');
  448. changeValue(elmData);
  449. hooker.unhook(id);
  450. DoLog('Value changed, getElementsByTagName unhooked...');
  451. }
  452. return [_this, args];
  453. }
  454. }).id;
  455. DoLog(LogLevel.Success, 'getElementsByTagName Hooked...');
  456. } else {
  457. // Environment not ready yet, wait for it
  458. DoLog('Environment not ready, waiting...');
  459. setTimeout(replacePredata, CONST.Number.Interval_Fastest);
  460. }
  461. }
  462. return false;
  463. } else {
  464. // elmData Found! Go change value directly.
  465. DoLog('Changing value directly');
  466. changeValue(elmData);
  467. }
  468.  
  469. function changeValue(elmData) {
  470. const RATES = {
  471. 'none': 0,
  472. 'standard': 128000,
  473. 'lossless': 999000,
  474. };
  475.  
  476. const list = JSON.parse(elmData.value);
  477. for (const song of list) {
  478. const privilege = song.privilege;
  479. const dlLevel = privilege.downloadMaxBrLevel;
  480. const dlRate = RATES[dlLevel];
  481. const plLevel = privilege.playMaxBrLevel;
  482. const plRate = RATES[plLevel];
  483. privilege.dlLevel = dlLevel;
  484. privilege.dl = dlRate;
  485. privilege.plLevel = plLevel;
  486. privilege.pl = plRate;
  487. }
  488. elmData.value = JSON.stringify(list);
  489.  
  490. DoLog(LogLevel.Success, 'Predata replaced');
  491. }
  492. }
  493.  
  494. function downloadSong(name, artist, cover) {
  495. // Check arguments
  496. if (!name || !artist) {
  497. DoLog(LogLevel.Error, 'downloadSong: name or artist missing');
  498. return false;
  499. }
  500. !cover && DoLog('downloadSong: cover not provided');
  501.  
  502. // Gather info
  503. const fname = replaceText('{$NAME} - {$ARTIST}', {'{$NAME}': name, '{$ARTIST}': artist});
  504. const cpath = getUrlPath(cover);
  505.  
  506. search_song();
  507.  
  508. function search_song(page=1) {
  509. search({
  510. text: fname,
  511. page: page,
  512. callback: onsearch,
  513. });
  514.  
  515. function onsearch(json) {
  516. const isLastPage = (page === CONST.Number.MaxSearchPage || json.data.more === '0');
  517. !get_song(json, isLastPage) && search_song(page+1);
  518. }
  519. }
  520.  
  521. function get_song(json, force=false) {
  522. const list = json.data.list;
  523. const song = choose(list, force);
  524. if (song) {
  525. dl_GM(song.url, song.fname + '.' + song.ftype);
  526. dl(song.lrc, song.fname + '.lrc');
  527. dl(song.cover, song.fname + song.cpath.match(/\.[a-zA-Z]+?$/)[0]);
  528. return true;
  529. } else {
  530. return false;
  531. }
  532.  
  533. function choose(list, force) {
  534. const my_list = list.map((song) => {
  535. const qualities = [2000, 320, 128];
  536. const q = qualities.find((q) => (song.quality.includes(q)));
  537. const s_url = song[({
  538. 2000: 'url_flac',
  539. 320: 'url_320',
  540. 128: 'url_128'
  541. })[q]];
  542. const s_ftype = ({
  543. 2000: 'flac',
  544. 320: 'mp3',
  545. 128: 'mp3'
  546. })[q];
  547. const s_lrc = song.lrc;
  548. const s_cover = song.cover;
  549. const s_name = song.name;
  550. const s_artist = song.artist;
  551. const s_fname = name + ' - ' + artist;
  552. const s_cpath = getUrlPath(s_cover);
  553.  
  554. return {
  555. ftype: s_ftype,
  556. url: s_url,
  557. lrc: s_lrc,
  558. cover: s_cover,
  559. artist: s_artist,
  560. fname: s_fname,
  561. cpath: s_cpath
  562. }
  563. })
  564. return my_list.find((song) => (song.cpath === cpath || !cpath)) || (force ? my_list[0] : null);
  565. }
  566. }
  567. }
  568.  
  569. function search(details, retry=3) {
  570. const text = details.text;
  571. const page = details.page || '1';
  572. const type = details.type || 'YQD';
  573. const callback = details.callback;
  574. if (!text || !callback) {
  575. throw new Error('Argument text or callback missing');
  576. }
  577.  
  578. const url = 'http://59.110.45.28/m/api/search';
  579. GM_xmlhttpRequest({
  580. method: 'POST',
  581. url: url,
  582. headers: {
  583. 'Content-Type': 'application/x-www-form-urlencoded',
  584. 'Referer': 'https://tools.liumingye.cn/music_old/'
  585. },
  586. data: encode('text='+text+'&page='+page+'&type='+type),
  587. onload: function(res) {
  588. let json;
  589. try {
  590. json = JSON.parse(res.responseText);
  591. if (json.code !== 200) {
  592. throw new Error('dataerror');
  593. } else {
  594. callback(json);
  595. }
  596. } catch(e) {
  597. --retry >= 0 && search(details, retry);
  598. return false;
  599. }
  600. }
  601. });
  602. }
  603.  
  604. function encode(plainText) {
  605. const now = new Date().getTime();
  606. const md5Data = md5('<G6sX,Lk~^2:Y%4Z');
  607. let left = md5(md5Data.substr(0, 16));
  608. let right = md5(md5Data.substr(16, 32));
  609. let nowMD5 = md5(now).substr(-4);
  610. let Var_10 = (left + md5((left + nowMD5)));
  611. let Var_11 = Var_10['length'];
  612. let Var_12 = ((((now / 1000 + 86400) >> 0) + md5((plainText + right)).substr(0, 16)) + plainText);
  613. let Var_13 = '';
  614. for (let i = 0, Var_15 = Var_12.length;
  615. (i < Var_15); i++) {
  616. let Var_16 = Var_12.charCodeAt(i);
  617. if ((Var_16 < 128)) {
  618. Var_13 += String['fromCharCode'](Var_16);
  619. } else if ((Var_16 > 127) && (Var_16 < 2048)) {
  620. Var_13 += String['fromCharCode'](((Var_16 >> 6) | 192));
  621. Var_13 += String['fromCharCode'](((Var_16 & 63) | 128));
  622. } else {
  623. Var_13 += String['fromCharCode'](((Var_16 >> 12) | 224));
  624. Var_13 += String['fromCharCode']((((Var_16 >> 6) & 63) | 128));
  625. Var_13 += String['fromCharCode'](((Var_16 & 63) | 128));
  626. }
  627. }
  628. let Var_17 = Var_13.length;
  629. let Var_18 = [];
  630. for (let i = 0; i <= 255; i++) {
  631. Var_18[i] = Var_10[(i % Var_11)].charCodeAt();
  632. }
  633. let Var_19 = [];
  634. for (let Var_04 = 0;
  635. (Var_04 < 256); Var_04++) {
  636. Var_19.push(Var_04);
  637. }
  638. for (let Var_20 = 0, Var_04 = 0;
  639. (Var_04 < 256); Var_04++) {
  640. Var_20 = (((Var_20 + Var_19[Var_04]) + Var_18[Var_04]) % 256);
  641. let Var_21 = Var_19[Var_04];
  642. Var_19[Var_04] = Var_19[Var_20];
  643. Var_19[Var_20] = Var_21;
  644. }
  645. let Var_22 = '';
  646. for (let Var_23 = 0, Var_20 = 0, Var_04 = 0;
  647. (Var_04 < Var_17); Var_04++) {
  648. let Var_24 = '0|2|4|3|5|1'.split('|'),
  649. Var_25 = 0;
  650. while (true) {
  651. switch (Var_24[Var_25++]) {
  652. case '0':
  653. Var_23 = ((Var_23 + 1) % 256);
  654. continue;
  655. case '1':
  656. Var_22 += String.fromCharCode(Var_13[Var_04].charCodeAt() ^ Var_19[((Var_19[Var_23] + Var_19[Var_20]) % 256)]);
  657. continue;
  658. case '2':
  659. Var_20 = ((Var_20 + Var_19[Var_23]) % 256);
  660. continue;
  661. case '3':
  662. Var_19[Var_23] = Var_19[Var_20];
  663. continue;
  664. case '4':
  665. var Var_21 = Var_19[Var_23];
  666. continue;
  667. case '5':
  668. Var_19[Var_20] = Var_21;
  669. continue;
  670. }
  671. break;
  672. }
  673. }
  674. let Var_26 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  675. for (var Var_27, Var_28, Var_29 = 0, Var_30 = Var_26, Var_31 = ''; Var_22.charAt((Var_29 | 0)) || (Var_30 = '=', (Var_29 % 1)); Var_31 += Var_30.charAt((63 & (Var_27 >> (8 - ((Var_29 % 1) * 8)))))) {
  676. Var_28 = Var_22['charCodeAt'](Var_29 += 0.75);
  677. Var_27 = ((Var_27 << 8) | Var_28);
  678. }
  679. Var_22 = (nowMD5 + Var_31.replace(/=/g, '')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '.');
  680. return (('data=' + Var_22) + '&v=2');
  681. }
  682.  
  683. function dl(url, name) {
  684. GM_xmlhttpRequest({
  685. method: 'GET',
  686. url: url,
  687. responseType: 'blob',
  688. onload: function(res) {
  689. const ourl = URL.createObjectURL(res.response);
  690. const a = document.createElement('a');
  691. a.download = name;
  692. a.href = ourl;
  693. a.click();
  694. setTimeout(function() {
  695. URL.revokeObjectURL(ourl);
  696. }, 0);
  697. }
  698. });
  699. }
  700.  
  701. function dl_browser(url, name) {
  702. const a = $CrE('a');
  703. a.href = url;
  704. a.download = name;
  705. a.click();
  706. }
  707.  
  708. function dl_GM(url, name) {
  709. GM_download(url, name);
  710. }
  711.  
  712. // Basic functions
  713. // querySelector
  714. function $() {
  715. switch(arguments.length) {
  716. case 2:
  717. return arguments[0].querySelector(arguments[1]);
  718. break;
  719. default:
  720. return document.querySelector(arguments[0]);
  721. }
  722. }
  723. // querySelectorAll
  724. function $All() {
  725. switch(arguments.length) {
  726. case 2:
  727. return arguments[0].querySelectorAll(arguments[1]);
  728. break;
  729. default:
  730. return document.querySelectorAll(arguments[0]);
  731. }
  732. }
  733. // createElement
  734. function $CrE() {
  735. switch(arguments.length) {
  736. case 2:
  737. return arguments[0].createElement(arguments[1]);
  738. break;
  739. default:
  740. return document.createElement(arguments[0]);
  741. }
  742. }
  743.  
  744. // Get the pathname of a given url
  745. function getUrlPath(url) {
  746. if (typeof url === 'string') {
  747. const a = $CrE('a');
  748. a.href = url;
  749. return a.pathname;
  750. } else {
  751. return null;
  752. }
  753. }
  754.  
  755. // Replace model text with no mismatching of replacing replaced text
  756. // e.g. replaceText('aaaabbbbccccdddd', {'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e'}) === 'bbbbccccddddeeee'
  757. // replaceText('abcdAABBAA', {'BB': 'AA', 'AAAAAA': 'This is a trap!'}) === 'abcdAAAAAA'
  758. // replaceText('abcd{AAAA}BB}', {'{AAAA}': '{BB', '{BBBB}': 'This is a trap!'}) === 'abcd{BBBB}'
  759. // replaceText('abcd', {}) === 'abcd'
  760. /* Note:
  761. replaceText will replace in sort of replacer's iterating sort
  762. e.g. currently replaceText('abcdAABBAA', {'BBAA': 'TEXT', 'AABB': 'TEXT'}) === 'abcdAATEXT'
  763. but remember: (As MDN Web Doc said,) Although the keys of an ordinary Object are ordered now, this was
  764. not always the case, and the order is complex. As a result, it's best not to rely on property order.
  765. So, don't expect replaceText will treat replacer key-values in any specific sort. Use replaceText to
  766. replace irrelevance replacer keys only.
  767. */
  768. function replaceText(text, replacer) {
  769. if (Object.entries(replacer).length === 0) {return text;}
  770. const [models, targets] = Object.entries(replacer);
  771. const len = models.length;
  772. let text_arr = [{text: text, replacable: true}];
  773. for (const [model, target] of Object.entries(replacer)) {
  774. text_arr = replace(text_arr, model, target);
  775. }
  776. return text_arr.map((text_obj) => (text_obj.text)).join('');
  777.  
  778. function replace(text_arr, model, target) {
  779. const result_arr = [];
  780. for (const text_obj of text_arr) {
  781. if (text_obj.replacable) {
  782. const splited = text_obj.text.split(model);
  783. for (const part of splited) {
  784. result_arr.push({text: part, replacable: true});
  785. result_arr.push({text: target, replacable: false});
  786. }
  787. result_arr.pop();
  788. } else {
  789. result_arr.push(text_obj);
  790. }
  791. }
  792. return result_arr;
  793. }
  794. }
  795.  
  796. function iframeDocSync() {
  797. const iframe = $('#g_iframe');
  798. const oDoc = iframe && iframe.contentDocument;
  799. if (oDoc) {
  800. const top_path = document.URL.replace(/^https?:\/\/music\.163\.com\/#\//, '').replace(/^my\/m\//, '');
  801. const ifr_path = oDoc.URL.replace(/^https?:\/\/music\.163\.com\//, '').replace(/^my\/#\//, '');
  802. return top_path === ifr_path;
  803. } else {
  804. return false;
  805. }
  806. }
  807.  
  808. // Get unpolluted addEventListener
  809. function getPureAEL(parentDocument=document) {
  810. const ifr = makeIfr(parentDocument);
  811.  
  812. const oWin = ifr.contentWindow;
  813. const oDoc = ifr.contentDocument;
  814.  
  815. const AEL = oWin.XMLHttpRequest.prototype.addEventListener;
  816. return AEL;
  817. }
  818.  
  819. // Get unpolluted addEventListener
  820. function getPureREL(parentDocument=document) {
  821. const ifr = makeIfr(parentDocument);
  822.  
  823. const oWin = ifr.contentWindow;
  824. const oDoc = ifr.contentDocument;
  825.  
  826. const REL = oWin.XMLHttpRequest.prototype.removeEventListener;
  827. return REL;
  828. }
  829.  
  830. function makeIfr(parentDocument=document) {
  831. const ifr = $CrE(parentDocument, 'iframe');
  832. ifr.srcdoc = '<html></html>';
  833. ifr.style.width = ifr.style.height = ifr.style.border = ifr.style.padding = ifr.style.margin = '0';
  834. parentDocument.body.appendChild(ifr);
  835. return ifr;
  836. }
  837.  
  838. function APIHooker() {
  839. const AH = this;
  840. const hooker = new Hooker();
  841. const hooker_hooks = [];
  842. const hooks = [];
  843. const addEventListener = (function() {
  844. const AEL = getPureAEL();
  845. return function() {
  846. const args = Array.from(arguments);
  847. const _this = args.shift();
  848. AEL.apply(_this, args);
  849. }
  850. }) ();
  851. const removeEventListener = (function() {
  852. const REL = getPureREL();
  853. return function() {
  854. const args = Array.from(arguments);
  855. const _this = args.shift();
  856. REL.apply(_this, args);
  857. }
  858. }) ();
  859.  
  860. AH.hook = hook;
  861. AH.unhook = unhook;
  862. AH.pageOnchange = recover;
  863.  
  864. inject();
  865. setInterval(inject, CONST.Number.Interval_Balanced);
  866.  
  867. function hook(urlMatcher, xhrDealer) {
  868. return hooks.push({
  869. id: hooks.length,
  870. matcher: urlMatcher,
  871. dealer: xhrDealer,
  872. xhrs: []
  873. }) - 1;
  874. }
  875.  
  876. function unhook(id) {
  877. hooks.splice(id, 1);
  878. }
  879.  
  880. function inject() {
  881. const iframe = $('#g_iframe');
  882. const oWin = iframe ? iframe.contentWindow : null;
  883.  
  884. const hook_dealers = {
  885. open: function(_this, args) {
  886. const xhr = _this;
  887. for (const hook of hooks) {
  888. matchUrl(args[1], hook.matcher) && hook.xhrs.push(xhr);
  889. }
  890. return [_this, args];
  891. },
  892. send: function(_this, args) {
  893. const xhr = _this;
  894. for (const hook of hooks) {
  895. if (hook.xhrs.includes(xhr)) {
  896. // After first readystatechange event, change onreadystatechange to our onProgress function
  897. let onreadystatechange;
  898. addEventListener(xhr, 'readystatechange', function(e) {
  899. onreadystatechange = xhr.onreadystatechange;
  900. xhr.onreadystatechange = onProgress;
  901. }, {
  902. capture: false,
  903. passive: true,
  904. once: true
  905. });
  906.  
  907. // Recieves last 3 readystatechange event, apply dealer function, and continue onreadystatechange stack
  908. function onProgress(e) {
  909. let args = Array.from(arguments);
  910.  
  911. // When onload, apply xhr dealer
  912. let continueStack = true;
  913. if (xhr.status === 200 && xhr.readyState === 4) {
  914. continueStack = hook.dealer(xhr, this, args, onreadystatechange);
  915. }
  916.  
  917. continueStack && typeof onreadystatechange === 'function' && onreadystatechange.apply(this, args);
  918. }
  919. }
  920. }
  921. return [_this, args];
  922. },
  923. }
  924. let do_inject = false;
  925.  
  926. // Hook open: filter all xhr that should be hooked
  927. try {
  928. if (window.XMLHttpRequest.prototype.open.name !== 'hooker') {
  929. hooker_hooks.push(hooker.hook(window, 'XMLHttpRequest.prototype.open', false, false, {
  930. dealer: hook_dealers.open
  931. }));
  932. do_inject = true;
  933. }
  934. if (oWin && oWin.XMLHttpRequest.prototype.open.name !== 'hooker') {
  935. hooker_hooks.push(hooker.hook(oWin, 'XMLHttpRequest.prototype.open', false, false, {
  936. dealer: hook_dealers.open
  937. }));
  938. do_inject = true;
  939. }
  940.  
  941. // Hook send: change eventListeners for each hooked xhr, and apply xhr dealer
  942. if (window.XMLHttpRequest.prototype.send.name !== 'hooker') {
  943. hooker_hooks.push(hooker.hook(window, 'XMLHttpRequest.prototype.send', false, false, {
  944. dealer: hook_dealers.send
  945. }));
  946. do_inject = true;
  947. }
  948. if (oWin && oWin.XMLHttpRequest.prototype.send.name !== 'hooker') {
  949. hooker_hooks.push(hooker.hook(oWin, 'XMLHttpRequest.prototype.send', false, false, {
  950. dealer: hook_dealers.send
  951. }));
  952. do_inject = true;
  953. }
  954. } catch(err) {}
  955.  
  956. do_inject && DoLog(LogLevel.Success, 'Hooker injected');
  957. }
  958.  
  959. function recover() {
  960. hooker_hooks.forEach((hook) => (hooker.unhook(hook.id)));
  961.  
  962. DoLog(LogLevel.Success, 'Hooker removed');
  963. }
  964.  
  965. function matchUrl(url, matcher) {
  966. if (matcher instanceof RegExp) {
  967. return !!url.match(matcher);
  968. }
  969. if (typeof matcher === 'function') {
  970. return matcher(url);
  971. }
  972. }
  973.  
  974. function idmaker() {
  975. let i = 0;
  976. return function() {
  977. return i++;
  978. }
  979. }
  980. }
  981.  
  982. function Hooker() {
  983. const H = this;
  984. const makeid = idmaker();
  985. const map = H.map = {};
  986. H.hook = hook;
  987. H.unhook = unhook;
  988.  
  989. function hook(base, path, log=false, apply_debugger=false, hook_return=false) {
  990. // target
  991. path = arrPath(path);
  992. let parent = base;
  993. for (let i = 0; i < path.length - 1; i++) {
  994. const prop = path[i];
  995. parent = parent[prop];
  996. }
  997. const prop = path[path.length-1];
  998. const target = parent[prop];
  999.  
  1000. // Only hook functions
  1001. if (typeof target !== 'function') {
  1002. throw new TypeError('hooker.hook: Hook functions only');
  1003. }
  1004. // Check args valid
  1005. if (hook_return) {
  1006. if (typeof hook_return !== 'object' || hook_return === null) {
  1007. throw new TypeError('hooker.hook: Argument hook_return should be false or an object');
  1008. }
  1009. if (!hook_return.hasOwnProperty('value') && typeof hook_return.dealer !== 'function') {
  1010. throw new TypeError('hooker.hook: Argument hook_return should contain one of following properties: value, dealer');
  1011. }
  1012. if (hook_return.hasOwnProperty('value') && typeof hook_return.dealer === 'function') {
  1013. throw new TypeError('hooker.hook: Argument hook_return should not contain both of following properties: value, dealer');
  1014. }
  1015. }
  1016.  
  1017. // hooker function
  1018. const hooker = function hooker() {
  1019. let _this = this === H ? null : this;
  1020. let args = Array.from(arguments);
  1021. const config = map[id].config;
  1022. const hook_return = config.hook_return;
  1023.  
  1024. // hook functions
  1025. config.log && console.log([base, path.join('.')], _this, args);
  1026. if (config.apply_debugger) {debugger;}
  1027. if (hook_return && typeof hook_return.dealer === 'function') {
  1028. [_this, args] = hook_return.dealer(_this, args);
  1029. }
  1030.  
  1031. // continue stack
  1032. return hook_return && hook_return.hasOwnProperty('value') ? hook_return.value : target.apply(_this, args);
  1033. }
  1034. parent[prop] = hooker;
  1035.  
  1036. // Id
  1037. const id = makeid();
  1038. map[id] = {
  1039. id: id,
  1040. prop: prop,
  1041. parent: parent,
  1042. target: target,
  1043. hooker: hooker,
  1044. config: {
  1045. log: log,
  1046. apply_debugger: apply_debugger,
  1047. hook_return: hook_return
  1048. }
  1049. };
  1050.  
  1051. return map[id];
  1052. }
  1053.  
  1054. function unhook(id) {
  1055. // unhook
  1056. try {
  1057. const hookObj = map[id];
  1058. hookObj.parent[hookObj.prop] = hookObj.target;
  1059. delete map[id];
  1060. } catch(err) {
  1061. console.error(err);
  1062. DoLog(LogLevel.Error, 'unhook error');
  1063. }
  1064. }
  1065.  
  1066. function arrPath(path) {
  1067. return Array.isArray(path) ? path : path.split('.')
  1068. }
  1069.  
  1070. function idmaker() {
  1071. let i = 0;
  1072. return function() {
  1073. return i++;
  1074. }
  1075. }
  1076. }
  1077.  
  1078. function IntervalTaskManager() {
  1079. const tasks = this.tasks = [];
  1080. this.time = 500;
  1081. this.interval = -1;
  1082. defineProperty(this, 'working', {
  1083. get: () => (this.interval >= 0)
  1084. });
  1085.  
  1086. this.addTask = function(fn) {
  1087. tasks.push(fn);
  1088. }
  1089.  
  1090. this.removeTask = function(fn_idx) {
  1091. const idx = typeof fn_idx === 'number' ? fn_idx : tasks.indexOf(fn_idx)
  1092. tasks.splice(idx, 1)
  1093. }
  1094.  
  1095. this.clearTasks = function() {
  1096. tasks.splice(0, Infinity)
  1097. }
  1098.  
  1099. this.start = function() {
  1100. if (!this.working) {
  1101. this.interval = setInterval(this.do, this.time);
  1102. return true;
  1103. } else {
  1104. return false;
  1105. }
  1106. }
  1107.  
  1108. this.stop = function() {
  1109. if (this.working) {
  1110. clearInterval(this.interval);
  1111. this.interval = -1;
  1112. return true;
  1113. } else {
  1114. return false;
  1115. }
  1116. }
  1117.  
  1118. this.do = function() {
  1119. for (const task of tasks) {
  1120. task();
  1121. }
  1122. }
  1123. }
  1124.  
  1125. function defineProperty(obj, prop, desc) {
  1126. desc.configurable = false;
  1127. desc.enumerable = true;
  1128. Object.defineProperty(obj, prop, desc);
  1129. }
  1130. })();

QingJ © 2025

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