您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
利用MyFreeMP3扩展网易云音乐功能
当前为
/* eslint-disable no-multi-spaces */ /* eslint-disable dot-notation */ // ==UserScript== // @name 网易云音乐-MyFreeMP3扩展 // @name:zh-CN 网易云音乐-MyFreeMP3扩展 // @name:en Netease Music - MyFreeMP3 Extender // @namespace 163Music-MyFreeMP3-Extender // @version 1.2.2 // @description 利用MyFreeMP3扩展网易云音乐功能 // @description:zh-CN 利用MyFreeMP3扩展网易云音乐功能 // @description:en Extend netease music with MyFreeMP3 // @author PY-DNG // @license GPL-v3 // @match http*://music.163.com/* // @connect 59.110.45.28 // @connect music.163.net // @connect music.126.net // @icon https://s1.music.126.net/style/favicon.ico // @grant GM_xmlhttpRequest // @grant GM_download // @run-at document-start // @noframes // ==/UserScript== (function __MAIN__() { 'use strict'; const CONST = { Text: { V5NOCANQU: '要听龙叔的话,V5不能屈,听完歌快去给你网易爸爸充VIP吧' }, Number: { Interval_Fastest: 1, Interval_Fast: 50, Interval_Balanced: 500, MaxSearchPage: 3, } } // Prepare const md5Script = document.createElement('script'); md5Script.src = 'https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.18.0/js/md5.js'; document.head.appendChild(md5Script); // Arguments: level=LogLevel.Info, logContent, asObject=false // Needs one call "DoLog();" to get it initialized before using it! function DoLog() { const win = typeof unsafeWindow === 'object' ? unsafeWindow : window; // Global log levels set win.LogLevel = { None: 0, Error: 1, Success: 2, Warning: 3, Info: 4, } win.LogLevelMap = {}; win.LogLevelMap[LogLevel.None] = {prefix: '' , color: 'color:#ffffff'} win.LogLevelMap[LogLevel.Error] = {prefix: '[Error]' , color: 'color:#ff0000'} win.LogLevelMap[LogLevel.Success] = {prefix: '[Success]' , color: 'color:#00aa00'} win.LogLevelMap[LogLevel.Warning] = {prefix: '[Warning]' , color: 'color:#ffa500'} win.LogLevelMap[LogLevel.Info] = {prefix: '[Info]' , color: 'color:#888888'} win.LogLevelMap[LogLevel.Elements] = {prefix: '[Elements]', color: 'color:#000000'} // Current log level DoLog.logLevel = (win.isPY_DNG && win.userscriptDebugging) ? LogLevel.Info : LogLevel.Warning; // Info Warning Success Error // Log counter DoLog.logCount === undefined && (DoLog.logCount = 0); if (++DoLog.logCount > 512) { console.clear(); DoLog.logCount = 0; } // Get args let level, logContent, asObject; switch (arguments.length) { case 1: level = LogLevel.Info; logContent = arguments[0]; asObject = false; break; case 2: level = arguments[0]; logContent = arguments[1]; asObject = false; break; case 3: level = arguments[0]; logContent = arguments[1]; asObject = arguments[2]; break; default: level = LogLevel.Info; logContent = 'DoLog initialized.'; asObject = false; break; } // Log when log level permits if (level <= DoLog.logLevel) { let msg = '%c' + LogLevelMap[level].prefix; let subst = LogLevelMap[level].color; if (asObject) { msg += ' %o'; } else { switch(typeof(logContent)) { case 'string': msg += ' %s'; break; case 'number': msg += ' %d'; break; case 'object': msg += ' %o'; break; } } console.log(msg, subst, logContent); } } DoLog(); main(); function main() { // Wait for document.body if (!document.body) { setTimeout(main, CONST.Number.Interval_Fast); return false; } // Commons hookPlay(); playlistDownload(); // Page functions const ITM = new IntervalTaskManager(); const pageChangeDetecter = (function(callback, emitOnInit=false) { let href = location.href; emitOnInit && callback(null, href); return function detecter() { const new_href = location.href; if (href !== new_href) { callback(href, new_href); href = new_href; } } }) (deliverPageFuncs, true); ITM.time = CONST.Number.Interval_Fast; ITM.addTask(pageChangeDetecter); ITM.start(); function deliverPageFuncs(href, new_href) { const pageFuncs = [{ reg: /^https?:\/\/music\.163\.com\/#\/song\?.+$/, func: pageSong, checker: function() { const ifr = $('#g_iframe'); if (!ifr) {return false;} const oDoc = ifr.contentDocument; if (!oDoc) {return false;} const elm = !!$(oDoc, '.cnt>.m-info'); return elm; } },{ reg: /^https?:\/\/music\.163\.com\/#\/(artist|album|discover\/toplist)\?.+$/, func: replacePredata, sync: false },{ reg: /^https?:\/\/music\.163\.com\//, func: listDownload, checker: function() { const ifr = $('#g_iframe'); if (!ifr) {return false;} const oDoc = ifr.contentDocument; if (!oDoc) {return false;} return !!oDoc.body; } },{ reg: /^https?:\/\/music\.163\.com\/#\/album\?.+$/, func: pageAlbum, checker: function() { const ifr = $('#g_iframe'); if (!ifr) {return false;} const oDoc = ifr.contentDocument; if (!oDoc) {return false;} const elm = !!$(oDoc, '#content-operation'); return elm; } }]; for (const pageFunc of pageFuncs) { test_exec(pageFunc); } function test_exec(pageFunc) { pageFunc.reg.test(location.href) && ((((pageFunc.sync || !pageFunc.hasOwnProperty('sync')) ? iframeDocSync() : true) && (pageFunc.checker ? ({ 'string': () => ($(pageFunc.checker)), 'function': pageFunc.checker, })[typeof pageFunc.checker]() : true)) ? true : (setTimeout(test_exec.bind(null, pageFunc), CONST.Number.Interval_Balanced), DoLog('waiting: ' + location.href), false)) && (DoLog('Exec ' + pageFunc.func.name), pageFunc.func(href, new_href)); } } } function hookPlay() { // Access Checker: core_fbc43dc690327907cf6fdad6d52f7c31.js?:formatted:8988('l6f.tt2x = function(bi7b, action) {') // Play try { const RATES = { 'none': 0, 'standard': 128000, 'lossless': 999000, }; const APIH = new APIHooker(); let dlLevel, dlRate, plLevel, plRate; APIH.hook(/^https?:\/\/music\.163\.com\/weapi\/v3\/song\/detail(\?[a-zA-Z0-9=_]+)?$/, function(xhr) { const json = JSON.parse(xhr.response); const privilege = json['privileges'][0]; dlLevel = privilege['downloadMaxBrLevel']; dlRate = RATES[dlLevel]; plLevel = privilege['playMaxBrLevel']; plRate = RATES[plLevel]; privilege['dlLevel'] = dlLevel; // Download privilege['dl'] = dlRate; // Download privilege['plLevel'] = plLevel; // Play privilege['pl'] = plRate; // Play privilege['st'] = 0; // Copyright DoLog(json); const response = JSON.stringify(json) const propDesc = { value: response, writable: false, configurable: false, enumerable: true } Object.defineProperties(xhr, { 'response': propDesc, 'responseText': propDesc }) return true; }); APIH.hook(/^\/weapi\/song\/enhance\/player\/url\/v1(\?[a-zA-Z0-9=_]+)?$/, function(xhr, _this, args, onreadystatechange) { const ifr = $('#g_iframe'); const oDoc = ifr.contentDocument; // Get data const json = JSON.parse(xhr.response); const data = json['data'][0]; const name = $('.play a.name').innerText; const artist = $('.play .by>span').children[0].innerText; const fname = replaceText('{$NAME} - {$ARTIST}', {'{$NAME}': name, '{$ARTIST}': artist}); const cover = $('.m-playbar .head img').src; const cpath = getUrlPath(cover); // Only hook unplayable songs if (data['url']) {return true}; search({ text: fname, callback: function(s_json) { const list = s_json.data.list; const my_list = dealList(list); reqSong(choose(list, 0)); function dealList(list) { return list.map((song) => { const qualities = [2000, 320, 128]; const q = qualities.find((q) => (song.quality.includes(q))); const s_url = song[({ 2000: 'url_flac', 320: 'url_320', 128: 'url_128' })[q]]; const s_ftype = ({ 2000: 'flac', 320: 'mp3', 128: 'mp3' })[q]; const s_lrc = song.lrc; const s_cover = song.cover; const s_name = song.name; const s_artist = song.artist; const s_fname = name + ' - ' + artist; const s_cpath = getUrlPath(s_cover); return { ftype: s_ftype, url: s_url, lrc: s_lrc, cover: s_cover, artist: s_artist, fname: s_fname, cpath: s_cpath } }); } function choose(list, start=0) { return my_list.find((song, i) => (i >= start && (song.cpath === cpath || !cpath))) || my_list[start]; } function reqSong(song) { const abort = GM_xmlhttpRequest({ method: 'GET', url: song.url, onprogress: load, onload: load }).abort; function load(e) { // Abort request first abort(); // Check if finalUrl differ from original url if (song.url === e.finalUrl) { // Same url means not available, try another song. const nextSong = choose(list, my_list.indexOf(song)+1); nextSong ? reqSong(nextSong) : continueStack(); return; } // modify xhr and continue stack data['code'] = 200; data['br'] = plRate; data['level'] = plLevel; data['type'] = 'mp3'; data['url'] = e.finalUrl; const response = JSON.stringify(json); const propDesc = { value: response, writable: false, configurable: true, enumerable: true }; Object.defineProperties(xhr, { 'response': propDesc, 'responseText': propDesc }); continueStack(); } } }, }); // Suspend stack until search & find the song return false; function continueStack() { onreadystatechange.apply(_this, args);; } }); } catch (err) { console.error(err); DoLog(LogLevel.Error, 'hooking error'); } } function listDownload() { const iframe = $('#g_iframe'); const oDoc = iframe.contentDocument; const body = oDoc.body; if (!body) { DoLog(LogLevel.Warning, 'listDownload: list not found'); return false; } const AEL = getPureAEL(); AEL.call(body, 'click', function(e) { const elm = e.target; if (elm.getAttribute('data-res-action') === 'download') { e.stopPropagation(); const elm_share = elm.previousElementSibling; const container = elm.parentElement.parentElement.parentElement; const name = elm_share.getAttribute('data-res-name') || $(container, '.w0 div.text>*').innerText; const artist = elm_share.getAttribute('data-res-author') || $(container, '.w1 div.text>*').innerText; const cover = elm_share.getAttribute('data-res-pic'); downloadSong(name, artist, cover); } }, {capture: true}); } function playlistDownload() { const AEL = getPureAEL(); AEL.call(document.body, 'click', function(e) { const elm = e.target; if (elm.getAttribute('data-action') === 'download') { e.stopPropagation(); const li = elm.parentElement.parentElement.parentElement; const name = $(li, '.col-2').innerText; const artist = $(li, '.col-4').innerText; downloadSong(name, artist); } }, {capture: true}); } function pageSong() { const ifr = $('#g_iframe'); const oDoc = ifr.contentDocument; const name = $(oDoc, '.tit>em').innerText; const artist = $(oDoc, '.cnt>.des>span>a').innerText; const cover = $(oDoc, '.u-cover>img.j-img').src; const AEL = getPureAEL(); // GUI if ($(oDoc, '.vip-song')) { // vip song const content_operation = $(oDoc, '#content-operation'); const vip_group = $(content_operation, '.u-vip-btn-group'); const vip_play = $(vip_group || content_operation, 'a[data-res-action="play"]'); const vip_add = $(vip_group || content_operation, 'a[data-res-action="addto"]'); // Style vip_play.classList.remove('u-btni-vipply'); vip_play.classList.remove('u-btni-openvipply'); vip_play.classList.add('u-btni-addply'); vip_add && vip_add.classList.remove('u-btni-vipadd'); vip_add && vip_add.classList.add('u-btni-add'); if (vip_group) { vip_add && content_operation.insertAdjacentElement('afterbegin', vip_add); content_operation.insertAdjacentElement('afterbegin', vip_play); content_operation.removeChild(vip_group); } // Text vip_play.title = CONST.Text.V5NOCANQU; vip_play.children[0].childNodes[1].nodeValue = '播放'; } if ($(oDoc, '.u-btni-play-dis')) { // Copyright song // Data const cpr_play = $(oDoc, '.u-btni-play-dis'); const cpr_fav = cpr_play.nextElementSibling; cpr_play.setAttribute('data-res-id', cpr_fav.getAttribute('data-res-id')); cpr_play.setAttribute('data-res-type', cpr_fav.getAttribute('data-res-type')); cpr_play.setAttribute('data-res-action', 'play'); // Style cpr_play.classList.remove('u-btni-play-dis'); } // Download const dlButton = $(oDoc, '#content-operation>a[data-res-action="download"]'); AEL.call(dlButton, 'click', dlOnclick, {useCapture: true}); function dlOnclick(e) { e.stopPropagation(); downloadSong(name, artist, cover); } } function pageAlbum() { const iframe = $('#g_iframe'); const oDoc = iframe.contentDocument; const oWin = iframe.contentWindow; // GUI if ($(oDoc, '.vip-album')) { const content_operation = $(oDoc, '#content-operation'); const vip_group = $(content_operation, '.u-vip-btn-group'); const vip_play = $(vip_group || content_operation, 'a[data-res-action="play"]'); const vip_add = $(vip_group || content_operation, 'a[data-res-action="addto"]'); // Style vip_play.classList.remove('u-btni-vipply'); vip_play.classList.remove('u-btni-openvipply'); vip_play.classList.add('u-btni-addply'); vip_add && vip_add.classList.remove('u-btni-vipadd'); vip_add && vip_add.classList.add('u-btni-add'); if (vip_group) { vip_add && content_operation.insertAdjacentElement('afterbegin', vip_add); content_operation.insertAdjacentElement('afterbegin', vip_play); content_operation.removeChild(vip_group); } // Text vip_play.title = CONST.Text.V5NOCANQU; vip_play.children[0].childNodes[1].nodeValue = '播放'; } } function replacePredata() { const iframe = $('#g_iframe'); const oDoc = iframe.contentDocument; const oWin = iframe.contentWindow; const envReady = oDoc && iframeDocSync(); const elmData = oDoc && $(oDoc, '#song-list-pre-data'); if (!elmData) { // No elmData found. if (envReady && $(oDoc, '#song-list-pre-cache table')) { // Too late. Data has already been dealed. DoLog(LogLevel.Error, 'Predata hook failed.'); DoLog([$(oDoc, '#song-list-pre-cache table'), oDoc.URL, oWin.location.href]); } else { // Data has not been loaded! DoLog('No predata found'); if (envReady) { // Hook Element.prototype.getElementsByTagName to make changeValue called. DoLog('Environment ready, hooking getElementsByTagName...'); const hooker = new Hooker(); const id = hooker.hook(oWin, 'Element.prototype.getElementsByTagName', false, false, { dealer: function(_this, args) { if (_this.id === 'song-list-pre-cache' && args[0] === 'textarea') { const elmData = $(_this, 'textarea'); changeValue(elmData); hooker.unhook(id); DoLog('Value changed, getElementsByTagName unhooked...'); } return [_this, args]; } }).id; DoLog(LogLevel.Success, 'getElementsByTagName Hooked...'); } else { // Environment not ready yet, wait for it DoLog('Environment not ready, waiting...'); setTimeout(replacePredata, CONST.Number.Interval_Fastest); } } return false; } else { // elmData Found! Go change value directly. DoLog('Changing value directly'); changeValue(elmData); } function changeValue(elmData) { const RATES = { 'none': 0, 'standard': 128000, 'lossless': 999000, }; const list = JSON.parse(elmData.value); for (const song of list) { const privilege = song.privilege; const dlLevel = privilege.downloadMaxBrLevel; const dlRate = RATES[dlLevel]; const plLevel = privilege.playMaxBrLevel; const plRate = RATES[plLevel]; privilege.dlLevel = dlLevel; privilege.dl = dlRate; privilege.plLevel = plLevel; privilege.pl = plRate; } elmData.value = JSON.stringify(list); DoLog(LogLevel.Success, 'Predata replaced'); } } function downloadSong(name, artist, cover) { // Check arguments if (!name || !artist) { DoLog(LogLevel.Error, 'downloadSong: name or artist missing'); return false; } !cover && DoLog('downloadSong: cover not provided'); // Gather info const fname = replaceText('{$NAME} - {$ARTIST}', {'{$NAME}': name, '{$ARTIST}': artist}); const cpath = getUrlPath(cover); search_song(); function search_song(page=1) { search({ text: fname, page: page, callback: onsearch, }); function onsearch(json) { const isLastPage = (page === CONST.Number.MaxSearchPage || json.data.more === '0'); !get_song(json, isLastPage) && search_song(page+1); } } function get_song(json, force=false) { const list = json.data.list; const song = choose(list, force); if (song) { dl_GM(song.url, song.fname + '.' + song.ftype, false); dl_GM(song.lrc, song.fname + '.lrc', false); dl_GM(song.cover, song.fname + song.cpath.match(/\.[a-zA-Z]+?$/)[0], false); return true; } else { return false; } function choose(list, force) { const my_list = list.map((song) => { const qualities = [2000, 320, 128]; const q = qualities.find((q) => (song.quality.includes(q))); const s_url = song[({ 2000: 'url_flac', 320: 'url_320', 128: 'url_128' })[q]]; const s_ftype = ({ 2000: 'flac', 320: 'mp3', 128: 'mp3' })[q]; const s_lrc = song.lrc; const s_cover = song.cover; const s_name = song.name; const s_artist = song.artist; const s_fname = name + ' - ' + artist; const s_cpath = getUrlPath(s_cover); return { ftype: s_ftype, url: s_url, lrc: s_lrc, cover: s_cover, artist: s_artist, fname: s_fname, cpath: s_cpath } }) return my_list.find((song) => (song.cpath === cpath || !cpath)) || (force ? my_list[0] : null); } } } function search(details, retry=3) { const text = details.text; const page = details.page || '1'; const type = details.type || 'YQD'; const callback = details.callback; if (!text || !callback) { throw new Error('Argument text or callback missing'); } const url = 'http://59.110.45.28/m/api/search'; GM_xmlhttpRequest({ method: 'POST', url: url, headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Referer': 'https://tools.liumingye.cn/music_old/' }, data: encode('text='+text+'&page='+page+'&type='+type), onload: function(res) { let json; try { json = JSON.parse(res.responseText); if (json.code !== 200) { throw new Error('dataerror'); } else { callback(json); } } catch(e) { --retry >= 0 && search(details, retry); return false; } } }); } function encode(plainText) { const now = new Date().getTime(); const md5Data = md5('<G6sX,Lk~^2:Y%4Z'); let left = md5(md5Data.substr(0, 16)); let right = md5(md5Data.substr(16, 32)); let nowMD5 = md5(now).substr(-4); let Var_10 = (left + md5((left + nowMD5))); let Var_11 = Var_10['length']; let Var_12 = ((((now / 1000 + 86400) >> 0) + md5((plainText + right)).substr(0, 16)) + plainText); let Var_13 = ''; for (let i = 0, Var_15 = Var_12.length; (i < Var_15); i++) { let Var_16 = Var_12.charCodeAt(i); if ((Var_16 < 128)) { Var_13 += String['fromCharCode'](Var_16); } else if ((Var_16 > 127) && (Var_16 < 2048)) { Var_13 += String['fromCharCode'](((Var_16 >> 6) | 192)); Var_13 += String['fromCharCode'](((Var_16 & 63) | 128)); } else { Var_13 += String['fromCharCode'](((Var_16 >> 12) | 224)); Var_13 += String['fromCharCode']((((Var_16 >> 6) & 63) | 128)); Var_13 += String['fromCharCode'](((Var_16 & 63) | 128)); } } let Var_17 = Var_13.length; let Var_18 = []; for (let i = 0; i <= 255; i++) { Var_18[i] = Var_10[(i % Var_11)].charCodeAt(); } let Var_19 = []; for (let Var_04 = 0; (Var_04 < 256); Var_04++) { Var_19.push(Var_04); } for (let Var_20 = 0, Var_04 = 0; (Var_04 < 256); Var_04++) { Var_20 = (((Var_20 + Var_19[Var_04]) + Var_18[Var_04]) % 256); let Var_21 = Var_19[Var_04]; Var_19[Var_04] = Var_19[Var_20]; Var_19[Var_20] = Var_21; } let Var_22 = ''; for (let Var_23 = 0, Var_20 = 0, Var_04 = 0; (Var_04 < Var_17); Var_04++) { let Var_24 = '0|2|4|3|5|1'.split('|'), Var_25 = 0; while (true) { switch (Var_24[Var_25++]) { case '0': Var_23 = ((Var_23 + 1) % 256); continue; case '1': Var_22 += String.fromCharCode(Var_13[Var_04].charCodeAt() ^ Var_19[((Var_19[Var_23] + Var_19[Var_20]) % 256)]); continue; case '2': Var_20 = ((Var_20 + Var_19[Var_23]) % 256); continue; case '3': Var_19[Var_23] = Var_19[Var_20]; continue; case '4': var Var_21 = Var_19[Var_23]; continue; case '5': Var_19[Var_20] = Var_21; continue; } break; } } let Var_26 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; 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)))))) { Var_28 = Var_22['charCodeAt'](Var_29 += 0.75); Var_27 = ((Var_27 << 8) | Var_28); } Var_22 = (nowMD5 + Var_31.replace(/=/g, '')).replace(/\+/g, '-').replace(/\//g, '_').replace(/=/g, '.'); return (('data=' + Var_22) + '&v=2'); } function dl(url, name) { GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'blob', onload: function(res) { const ourl = URL.createObjectURL(res.response); const a = document.createElement('a'); a.download = name; a.href = ourl; a.click(); setTimeout(function() { URL.revokeObjectURL(ourl); }, 0); } }); } function dl_browser(url, name) { const a = $CrE('a'); a.href = url; a.download = name; a.click(); } function dl_GM(url, name, path=true) { name = path ? name : replaceOSSep(name); GM_download(url, name); } function replaceOSSep(text) { const sep = getOSSep(); const rpl = ({'\\': '\', '/': '/'})[sep]; return text.replaceAll(sep, rpl); } function getOSSep() { return ({ 'Windows': '\\', 'Mac': '/', 'Linux': '/', 'Null': '-' })[getOS()]; } function getOS() { const info = (navigator.platform || navigator.userAgent).toLowerCase(); const test = (s) => (info.includes(s)); const map = { 'Windows': ['window', 'win32', 'win64', 'win86'], 'Mac': ['mac', 'os x'], 'Linux': ['linux'] } for (const [sys, strs] of Object.entries(map)) { if (strs.some(test)) { return sys; } } return 'Null'; } // Basic functions // querySelector function $() { switch(arguments.length) { case 2: return arguments[0].querySelector(arguments[1]); break; default: return document.querySelector(arguments[0]); } } // querySelectorAll function $All() { switch(arguments.length) { case 2: return arguments[0].querySelectorAll(arguments[1]); break; default: return document.querySelectorAll(arguments[0]); } } // createElement function $CrE() { switch(arguments.length) { case 2: return arguments[0].createElement(arguments[1]); break; default: return document.createElement(arguments[0]); } } // Get the pathname of a given url function getUrlPath(url) { if (typeof url === 'string') { const a = $CrE('a'); a.href = url; return a.pathname; } else { return null; } } // Get a url argument from lacation.href // also recieve a function to deal the matched string // returns defaultValue if name not found // Args: {url=location.href, name, dealFunc=((a)=>{return a;}), defaultValue=null} or 'name' function getUrlArgv(details) { typeof(details) === 'string' && (details = {name: details}); typeof(details) === 'undefined' && (details = {}); if (!details.name) {return null;}; const url = details.url ? details.url : location.href; const name = details.name ? details.name : ''; const dealFunc = details.dealFunc ? details.dealFunc : ((a)=>{return a;}); const defaultValue = details.defaultValue ? details.defaultValue : null; const matcher = new RegExp('[\\?&]' + name + '=([^&#]+)'); const result = url.match(matcher); const argv = result ? dealFunc(result[1]) : defaultValue; return argv; } // Replace model text with no mismatching of replacing replaced text // e.g. replaceText('aaaabbbbccccdddd', {'a': 'b', 'b': 'c', 'c': 'd', 'd': 'e'}) === 'bbbbccccddddeeee' // replaceText('abcdAABBAA', {'BB': 'AA', 'AAAAAA': 'This is a trap!'}) === 'abcdAAAAAA' // replaceText('abcd{AAAA}BB}', {'{AAAA}': '{BB', '{BBBB}': 'This is a trap!'}) === 'abcd{BBBB}' // replaceText('abcd', {}) === 'abcd' /* Note: replaceText will replace in sort of replacer's iterating sort e.g. currently replaceText('abcdAABBAA', {'BBAA': 'TEXT', 'AABB': 'TEXT'}) === 'abcdAATEXT' but remember: (As MDN Web Doc said,) Although the keys of an ordinary Object are ordered now, this was not always the case, and the order is complex. As a result, it's best not to rely on property order. So, don't expect replaceText will treat replacer key-values in any specific sort. Use replaceText to replace irrelevance replacer keys only. */ function replaceText(text, replacer) { if (Object.entries(replacer).length === 0) {return text;} const [models, targets] = Object.entries(replacer); const len = models.length; let text_arr = [{text: text, replacable: true}]; for (const [model, target] of Object.entries(replacer)) { text_arr = replace(text_arr, model, target); } return text_arr.map((text_obj) => (text_obj.text)).join(''); function replace(text_arr, model, target) { const result_arr = []; for (const text_obj of text_arr) { if (text_obj.replacable) { const splited = text_obj.text.split(model); for (const part of splited) { result_arr.push({text: part, replacable: true}); result_arr.push({text: target, replacable: false}); } result_arr.pop(); } else { result_arr.push(text_obj); } } return result_arr; } } function iframeDocSync() { const iframe = $('#g_iframe'); const oDoc = iframe && iframe.contentDocument; if (oDoc) { const top_path = document.URL.replace(/^https?:\/\/music\.163\.com\/#\//, '').replace(/^my\/m\//, '').replace('/m/', '/').replace('/#/', '/'); const ifr_path = oDoc.URL.replace(/^https?:\/\/music\.163\.com\//, '').replace(/^my\/#\//, '').replace('/m/', '/').replace('/#/', '/'); return top_path === ifr_path; } else { return false; } } // Get unpolluted addEventListener function getPureAEL(parentDocument=document) { const ifr = makeIfr(parentDocument); const oWin = ifr.contentWindow; const oDoc = ifr.contentDocument; const AEL = oWin.XMLHttpRequest.prototype.addEventListener; return AEL; } // Get unpolluted addEventListener function getPureREL(parentDocument=document) { const ifr = makeIfr(parentDocument); const oWin = ifr.contentWindow; const oDoc = ifr.contentDocument; const REL = oWin.XMLHttpRequest.prototype.removeEventListener; return REL; } function makeIfr(parentDocument=document) { const ifr = $CrE(parentDocument, 'iframe'); ifr.srcdoc = '<html></html>'; ifr.style.width = ifr.style.height = ifr.style.border = ifr.style.padding = ifr.style.margin = '0'; parentDocument.body.appendChild(ifr); return ifr; } function APIHooker() { const AH = this; const hooker = new Hooker(); const hooker_hooks = []; const hooks = []; const addEventListener = (function() { const AEL = getPureAEL(); return function() { const args = Array.from(arguments); const _this = args.shift(); AEL.apply(_this, args); } }) (); const removeEventListener = (function() { const REL = getPureREL(); return function() { const args = Array.from(arguments); const _this = args.shift(); REL.apply(_this, args); } }) (); AH.hook = hook; AH.unhook = unhook; AH.pageOnchange = recover; inject(); setInterval(inject, CONST.Number.Interval_Balanced); function hook(urlMatcher, xhrDealer) { return hooks.push({ id: hooks.length, matcher: urlMatcher, dealer: xhrDealer, xhrs: [] }) - 1; } function unhook(id) { hooks.splice(id, 1); } function inject() { const iframe = $('#g_iframe'); const oWin = iframe ? iframe.contentWindow : null; const hook_dealers = { open: function(_this, args) { const xhr = _this; for (const hook of hooks) { matchUrl(args[1], hook.matcher) && hook.xhrs.push(xhr); } return [_this, args]; }, send: function(_this, args) { const xhr = _this; for (const hook of hooks) { if (hook.xhrs.includes(xhr)) { // After first readystatechange event, change onreadystatechange to our onProgress function let onreadystatechange; addEventListener(xhr, 'readystatechange', function(e) { onreadystatechange = xhr.onreadystatechange; xhr.onreadystatechange = onProgress; }, { capture: false, passive: true, once: true }); // Recieves last 3 readystatechange event, apply dealer function, and continue onreadystatechange stack function onProgress(e) { let args = Array.from(arguments); // When onload, apply xhr dealer let continueStack = true; if (xhr.status === 200 && xhr.readyState === 4) { continueStack = hook.dealer(xhr, this, args, onreadystatechange); } continueStack && typeof onreadystatechange === 'function' && onreadystatechange.apply(this, args); } } } return [_this, args]; }, } let do_inject = false; // Hook open: filter all xhr that should be hooked try { if (window.XMLHttpRequest.prototype.open.name !== 'hooker') { hooker_hooks.push(hooker.hook(window, 'XMLHttpRequest.prototype.open', false, false, { dealer: hook_dealers.open })); do_inject = true; } if (oWin && oWin.XMLHttpRequest.prototype.open.name !== 'hooker') { hooker_hooks.push(hooker.hook(oWin, 'XMLHttpRequest.prototype.open', false, false, { dealer: hook_dealers.open })); do_inject = true; } // Hook send: change eventListeners for each hooked xhr, and apply xhr dealer if (window.XMLHttpRequest.prototype.send.name !== 'hooker') { hooker_hooks.push(hooker.hook(window, 'XMLHttpRequest.prototype.send', false, false, { dealer: hook_dealers.send })); do_inject = true; } if (oWin && oWin.XMLHttpRequest.prototype.send.name !== 'hooker') { hooker_hooks.push(hooker.hook(oWin, 'XMLHttpRequest.prototype.send', false, false, { dealer: hook_dealers.send })); do_inject = true; } } catch(err) {} do_inject && DoLog(LogLevel.Success, 'Hooker injected'); } function recover() { hooker_hooks.forEach((hook) => (hooker.unhook(hook.id))); DoLog(LogLevel.Success, 'Hooker removed'); } function matchUrl(url, matcher) { if (matcher instanceof RegExp) { return !!url.match(matcher); } if (typeof matcher === 'function') { return matcher(url); } } function idmaker() { let i = 0; return function() { return i++; } } } function Hooker() { const H = this; const makeid = idmaker(); const map = H.map = {}; H.hook = hook; H.unhook = unhook; function hook(base, path, log=false, apply_debugger=false, hook_return=false) { // target path = arrPath(path); let parent = base; for (let i = 0; i < path.length - 1; i++) { const prop = path[i]; parent = parent[prop]; } const prop = path[path.length-1]; const target = parent[prop]; // Only hook functions if (typeof target !== 'function') { throw new TypeError('hooker.hook: Hook functions only'); } // Check args valid if (hook_return) { if (typeof hook_return !== 'object' || hook_return === null) { throw new TypeError('hooker.hook: Argument hook_return should be false or an object'); } if (!hook_return.hasOwnProperty('value') && typeof hook_return.dealer !== 'function') { throw new TypeError('hooker.hook: Argument hook_return should contain one of following properties: value, dealer'); } if (hook_return.hasOwnProperty('value') && typeof hook_return.dealer === 'function') { throw new TypeError('hooker.hook: Argument hook_return should not contain both of following properties: value, dealer'); } } // hooker function const hooker = function hooker() { let _this = this === H ? null : this; let args = Array.from(arguments); const config = map[id].config; const hook_return = config.hook_return; // hook functions config.log && console.log([base, path.join('.')], _this, args); if (config.apply_debugger) {debugger;} if (hook_return && typeof hook_return.dealer === 'function') { [_this, args] = hook_return.dealer(_this, args); } // continue stack return hook_return && hook_return.hasOwnProperty('value') ? hook_return.value : target.apply(_this, args); } parent[prop] = hooker; // Id const id = makeid(); map[id] = { id: id, prop: prop, parent: parent, target: target, hooker: hooker, config: { log: log, apply_debugger: apply_debugger, hook_return: hook_return } }; return map[id]; } function unhook(id) { // unhook try { const hookObj = map[id]; hookObj.parent[hookObj.prop] = hookObj.target; delete map[id]; } catch(err) { console.error(err); DoLog(LogLevel.Error, 'unhook error'); } } function arrPath(path) { return Array.isArray(path) ? path : path.split('.') } function idmaker() { let i = 0; return function() { return i++; } } } function IntervalTaskManager() { const tasks = this.tasks = []; this.time = 500; this.interval = -1; defineProperty(this, 'working', { get: () => (this.interval >= 0) }); this.addTask = function(fn) { tasks.push(fn); } this.removeTask = function(fn_idx) { const idx = typeof fn_idx === 'number' ? fn_idx : tasks.indexOf(fn_idx) tasks.splice(idx, 1) } this.clearTasks = function() { tasks.splice(0, Infinity) } this.start = function() { if (!this.working) { this.interval = setInterval(this.do, this.time); return true; } else { return false; } } this.stop = function() { if (this.working) { clearInterval(this.interval); this.interval = -1; return true; } else { return false; } } this.do = function() { for (const task of tasks) { task(); } } } function defineProperty(obj, prop, desc) { desc.configurable = false; desc.enumerable = true; Object.defineProperty(obj, prop, desc); } })();