返回首頁 

My Free MP3+

解锁MyFreeMP3的QQ音乐、#狗音乐、#我音乐,过广告拦截器检测,所有下载全部转为页面内直链下载


Install this script?
/* eslint-disable no-multi-spaces *//* eslint-disable no-return-assign */// ==UserScript==// @name               My Free MP3+// @namespace          http://tampermonkey.net/My Free MP3 Plus// @version            0.2.6.2// @description        解锁MyFreeMP3的QQ音乐、#狗音乐、#我音乐,过广告拦截器检测,所有下载全部转为页面内直链下载// @author             PY-DNG// @license            GPL-3.0-or-later// @require            https://greasyfork.org/scripts/456034-basic-functions-for-userscripts/code/script.js?version=1226884// @require            https://fastly.jsdelivr.net/npm/[email protected]/dist/mp3tag.min.js// @require            https://update.greasyfork.org/scripts/482519/1297737/buffer.js// @require            https://update.greasyfork.org/scripts/482520/1298549/metaflacjs.js// @match              http*://tool.liumingye.cn/music_old/*// @match              http*://tools.liumingye.cn/music_old/*// @match              http*://tool.liumingye.cn/music/*// @match              http*://tools.liumingye.cn/music/*// @connect            kugou.com// @connect            *// @grant              GM_xmlhttpRequest// @grant              GM_download// @grant              GM_registerMenuCommand// @grant              GM_unregisterMenuCommand// @grant              GM_getValue// @grant              GM_setValue// @icon               data:image/png;base64,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###C6hm8BgraBjMFHpc9x54cSDEP2DRcilzyPRscCjjz+Gz332U8yZt4ggknDfHfjCYJ5+HvXaG4jWHEoq1Llnk739ZvRXL0Ot3dijn4V+4XdermHxPgBQ42YGLZJSf6MzjQxi5CBYOBZzUL+iFXCIxN8TVnzWMSCMRryzEX3eRFSYiMOrrYOlEFRlFC17vFioKCoQRRFKuQihYoqJqjThsvvwd+xCj7sWaYrXK5ctSP1C60Oh1/rrUinRKZHZux0nPeAKpdw7ECLT6ecS1DVnEY7/PCgrdMkK1gJYayiwUWrWYtRDv0LG5UOxAChm685ht5NkNvR+6yLy4y5Djh2Pfmdz3NV1bRhNFDV4HTt+EkWeLaffsxVp12pUQ68jE9VVT0kh/6MLfKhOoO6/nPC44SDLmrc0shZQcUOvX3kTZ86jRBsa4ial2+rSY9jv5dDBeA8vInpoKWrJE0hhqddDJRp4q/zczmUQ2UrUFnMNXd9gYaed1MBZWjuTYttXlDpWk2p4PWbxOBjUD1MGEfuBioGgXNQf3kZefCvCSJIJl759atm8ZSdKSs48/STWvL2Ohp12SgJyUH+ipXdTWPsWcuosnPagR+DGmJyfb3kg9Nt+C2y2dZBtLbt3ZLEnVh+aqKp9Rkp1eGcN2t5VwKcPQ0z5EmbYwApLaAxFEGZLI4lbl8If18WNDcZ2VsWIoqQtiUKEdpGnn0zwvYkEO7Yip89Bbt6FLE0yulouDLw3vY4d90P0QUn4bT01NHHjZa2gkvUXOE7mPiFEspipShNmy2ctEUcPQNx6IeaIobHWY0cuUYlQQDZE/WktYtJCyBWTnV2xFVMuYvw3yV12Abz0AmLuYlRzfu9goFvoNGZPIbvzTqL8ulInZi0QN/Y9NfX2mu0H+rmZwfOkVBcKGyqKr9/rWLFj9vr71OG758KZxxLV1WKUHZEUgcROjcZ5+AXMj5Yj3ARUp1FnnYL3nTH4boS8617EYy+gw+6cL4G1L/T9QtuTodfyqm1gSvRpKM2HvH0B###IaV13gk5V/1gIeWJ3byxqk6SC4wbDaSMQJ4/AHDIYk0pjtFsMrU3t6De3Qq8+RH1q8bc0IFa+gVzxBmLDjg8dtgfntlk39HMr/XzjsxDZoZaljz2Xx4w9jlXKhNFADbrmxESq7j4pxNDONviQEvF1V0JdEvpXw2H9iAb3hZoqIj9CtOXg/R2YdVuRjVlkWwHbfcYzpp4iVUlbYeit8bK7fgqBFdxq3Z53lcbu8aC3JwuUlW2tkIhBqJoTEunaBQJxSLwv0JM5Kq4V435UHCnGw6tSTLfxfv+/tWOQMIr8tV7Hdtu82/GJncZZp7XnttIuzkeOFsuvsUS2Tlyr3NpTtFN1tZDqNFGeZO1HmG4RbL/ix7TMh37HKj/f8jIElvNWaKt9C8SO2W0zsrfK259C7PdlEHWQPFSlar7u6MS3hJDdt5d64nGPA9ied2tMFDX5hbZnQn/PXyCyVGksCW8/Vwq/NzvtD0DZH8ogbJVaL51eZziJzHgp5aHGYDc9Pvo5PQCrCKsRmFwYeKv9XPPz4FmN21bMCl8GYWlT1nynKcKBAKgEYcOr1Xwv0IO0W32q0snThFRHI+RAYWvnA9ocjanimyjYEob+u1HQsToKsutLWraatsLbSGOb4myJ85Y23Z5+oADKIKw32qFQCqgC6kAPUEofZFRiuNapE4TUhxnEQClE0lqmTBZbSRrImijYFoX+e1HQsTaK/B1EnhXUjh/sXpgV3mrfnu01uxFus+A+t1z/EQCV0clSylqjDKQ6jlZF66SL4JxeSFWDlC5R4BOZdvDLAtkQaPeD86UxuRXW0sSCsBq399nvu+2JdY0b/wyASmuUgdhwa6OVFd6Csn/bw1rL3lNurawmLRWsVi2nLQArrD3sZ3uUd+w/lo3uSgWUwVvh7GETnxXYnq117NkeFkD5XssoC8Bq1gpaeZT/Z+KABC8L8n94op5YdeBzVgAAAABJRU5ErkJggg==// @run-at             document-start// ==/UserScript==/* global LogLevel DoLog Err $ $All $CrE $AEL $$CrE addStyle detectDom destroyEvent copyProp copyProps parseArgs escJsStr replaceText getUrlArgv dl_browser dl_GM AsyncManager *//* global pop MP3Tag BufferExport Metaflac */(async function() {'use strict';const CONST = {Text: {DownloadError: '下载遇到错误,请重试',MergeMetadata: ['[ ]下载时自动合成歌名、艺术家、封面和歌词到歌曲文件里', '[✔]下载时自动合成歌名、艺术家、封面和歌词到歌曲文件里']}};const FileType = await import('https://fastly.jsdelivr.net/npm/[email protected]/+esm');// Main loadermain();function main() {// Collect all funcs from page objsconst pages = [music, music_old, setting].map(f => f());const func_immediate = [], func_load = [];for (const page of pages) {page.regurl.test(location.href) &&page.funcs.forEach(funcobj => (funcobj.onload ? func_load : func_immediate).push(funcobj.func));}// Execconst exec = funcs => funcs.forEach(func => func());exec(func_immediate);document.readyState !== 'complete' ? $AEL(window, 'load', exec.bind(null, func_load)) : exec(func_load);}// 新版页面function music() {return {regurl: /^https?:\/\/tools?\.liumingye\.cn\/music\//,funcs: [{func: downloadInPage,onload: false}]}function downloadInPage() {const hooker = new Hooker();const xhrs = [];const hookedURLs = ['https://api.liumingye.cn/m/api/search', 'https://api.liumingye.cn/m/api/home/recommend', 'https://api.liumingye.cn/m/api/top/song'];const openHooerId = hooker.hook(XMLHttpRequest.prototype, 'open', false, false, {dealer(_this, args) {if (hookedURLs.some(url => args[1].includes(url))) {xhrs.push(_this);}return [_this, args];}});const sendHooerId = hooker.hook(XMLHttpRequest.prototype, 'send', false, false, {dealer(_this, args) {if (xhrs.includes(_this)) {const callbackName = 'onloadend' in _this ? 'onloadend' : 'onreadystatechange';const callback = _this[callbackName];_this[callbackName] = function() {const json = JSON.parse(this.response);json.data.list.forEach(song => song.quality.forEach((q, i) => typeof q !== 'number' && (song.quality[i] = parseInt(q.name, 10))));rewriteResponse(this, json);callback.apply(this, arguments);}xhrs.splice(xhrs.indexOf(_this), 1);}return [_this, args];}});}}// 旧版页面function music_old() {return {regurl: /^https?:\/\/tools?\.liumingye\.cn\/music_old\//,funcs: [{func: unlockTencent,onload: true}, {func: downloadInPage,onload: true}, {func: bypassAdkillerDetector,onload: false}]};// 解锁QQ音乐、#狗音乐、#我音乐函数function unlockTencent() {// 模拟双击const search_title = $('#search .home-title');const eDblclick = new Event('dblclick');search_title.dispatchEvent(eDblclick);// 去除双击事件const p = search_title.parentElement;const new_search_title = $CrE('div');new_search_title.className = search_title.className;new_search_title.innerHTML = search_title.innerHTML;p.removeChild(search_title);p.insertBefore(new_search_title, p.children[0]);}// Hook掉下载按钮实现全部下载均采用页面内下载方式(重写下载逻辑)function downloadInPage() {$AEL(document.body, 'click', onclick, {capture: true});function onclick(e) {const elm = e.target;const parent = elm ? elm.parentElement : null;match(elm);match(parent);function match(elm) {const tag = elm.tagName.toUpperCase();const clList = [...elm.classList];if (tag === 'A' && clList.includes('download') || clList.includes('pic_download')) {e.stopPropagation();e.preventDefault();;download(elm);}}}function download(a) {const elm_data = a.parentElement.previousElementSibling;const url = elm_data.value;const name = $("#name").value;const objPop = pop.download(name, 'download');GM_xmlhttpRequest({method: 'GET',url: url,responseType: 'blob',onprogress: function(e) {e.lengthComputable /*&& c*/ && (pop.size(objPop, bytesToSize(e.loaded) + " / " + bytesToSize(e.total)),pop.percent(objPop, 100 * (e.loaded / e.total) >> 0))},onerror: function(e) {console.log(e);window.open(url);},onload: async function(response) {let blob = response.response;const filetype = await FileType.fileTypeFromBuffer(await readAsArrayBuffer(blob));const ext = filetype?.ext || getExtname(elm_data.id, blob.type.split(';')[0]);try {GM_getValue('merge-metadata', false) && filetype?.ext === 'mp3' && (blob = await tagMP3(blob, getCurDlTag()));GM_getValue('merge-metadata', false) && filetype?.ext === 'flac' && (blob = await tagFLAC(blob, getCurDlTag()));} catch(err) {pop.text(objPop, CONST.Text.DownloadError);setTimeout(() => pop.close(objPop), 3000);DoLog(LogLevel.Error, err, 'error');throw err;}saveFile(blob, `${name}.${ext}`, filetype?.mime);pop.finished(objPop);setTimeout(pop.close.bind(pop, objPop), 2000);}});function getExtname(...args) {const map = {url_dsd: "flac",url_flac: "flac",url_ape: "ape",url_320: "mp3",url_128: "mp3",url_m4a: "m4a",url_lrc: "lrc",'image/png': 'png','image/jpg': 'jpg','image/gif': 'gif','image/bmp': 'bmp','image/jpeg': 'jpeg','image/webp': 'webp','image/tiff': 'tiff','image/vnd.microsoft.icon': 'ico',};return map[args.find(a => map[a])];}function bytesToSize(a) {if (0 === a) {return "0 B";}var b = ####, c = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"], d = Math.floor(Math.log(a) / Math.log(b));return (a / Math.pow(b, d)).toFixed(2) + " " + c[d]}}function getCurDlTag() {const tag = {cover: $('#pic').value,lyric: $('#url_lrc').value};const dlname = JSON.parse(localStorage.configure).data.dlname.split(' - ');const filename = $('#name').value.split(' - ');const name_singer = [0, 1].reduce((o, i) => ((o[dlname[i]] = filename[i], o)), {});tag.name = name_singer['{name}'];tag.artist = name_singer['{singer}'];return tag;}}// 过广告拦截器检测function bypassAdkillerDetector() {/*// 拦截广告拦截检测器的setTimeout延迟启动器// 优点:不用考虑#music_tool是否存在,不用反复执行;缺点:需要在setTimeout启动器注册前执行,如果脚本加载缓慢,就来不及了const setTimeout = unsafeWindow.setTimeout;unsafeWindow.setTimeout = function(func, time) {if (func && func.toString().includes('$("#music_tool").html()')) {func = function() {};}setTimeout.call(this, func, time);}*//*// 拦截广告拦截检测器的innerHTML检测// 优点:对浏览器API没有影响,对DOM影响极小,在检测前执行即可;缺点:需要#music_tool存在,需要反复检测执行,影响性能,稳定性差const bypasser = () => {const elm = $('#music_tool');elm && Object.defineProperty($('#music_tool'), 'innerHTML', {get: () => '<iframe></iframe>'});};setTimeout(bypasser, 2000);bypasser();*/// 在页面添加干扰元素// 优点:对浏览器API没有影响,对DOM几乎没有影响,在检测前执行即可,不用考虑#music_tool是否存在,不用反复执行;缺点:可能影响广告功能(乐document.body.firstChild.insertAdjacentHTML('beforebegin', '<ins id="music_tool" style="display: none !important;">sometext</ins>');}}function setting() {return {regurl: /^https?:\/\/tools?\.liumingye\.cn\/music(_old)?\//,funcs: [{func: makeSettings,onload: false}]};function makeSettings() {makeBooleanSettings([{text: CONST.Text.MergeMetadata,key: 'merge-metadata',defaultValue: false,}]);}}// Write MP3 tagsfunction tagMP3(blob, tag) {return new Promise(async (resolve, reject) => {try {const buffer = await readAsArrayBuffer(blob);// MP3Tag Usageconst mp3tag = new MP3Tag(buffer);mp3tag.read();mp3tag.tags.v2.TIT2 = tag.name || '';mp3tag.tags.v2.TPE1 = tag.artist || '';const AM = new AsyncManager();AM.onfinish = () => resolve(new Blob([mp3tag.save()], { type: blob.type }));// LyricAM.add();GM_xmlhttpRequest({method: 'GET',url: tag.lyric,timeout: 5 * 1000,onload: res => {const lyric = res.responseText;//.split(/[\r\n\t ]+/g).filter(line => /^\[\d+:\d+.\d+\][^\[\]]*$/.test(line)).join('\n');mp3tag.tags.v2.USLT = [{language: 'eng',descriptor: '',text: lyric}];AM.finish();},ontimeout: err => reject(err),onerror: err => reject(err)});// CoverAM.add();GM_xmlhttpRequest({method: 'GET',url: tag.cover,responseType: 'blob',timeout: 5 * 1000,onload: async res => {const blob = res.response;const imagebuffer = await readAsArrayBuffer(blob);const imageBytes = new Uint8Array(imagebuffer);mp3tag.tags.v2.APIC = [{format: blob.type,type: 3,description: '',data: imageBytes}]AM.finish();},ontimeout: err => reject(err),onerror: err => reject(err)});AM.finishEvent = true;} catch (err) {reject(err);}});}function tagFLAC(blob, tag) {return new Promise(async (resolve, reject) => {try {const buf = BufferExport.Buffer.from(await readAsArrayBuffer(blob));const flac = new Metaflac(buf);flac.removeTag('TITLE');flac.removeTag('ARTIST');flac.setTag(`TITLE=${tag.name}`);flac.setTag(`ARTIST=${tag.artist}`);const AM = new AsyncManager();AM.onfinish = () => resolve(new Blob([flac.save()], { type: blob.type }));// LyricAM.add();GM_xmlhttpRequest({method: 'GET',url: tag.lyric,timeout: 5 * 1000,onload: res => {const lyric = res.responseText;//.split(/[\r\n\t ]+/g).filter(line => /^\[\d+:\d+.\d+\][^\[\]]*$/.test(line)).join('\n');flac.removeTag('LYRICS');flac.setTag(`LYRICS=${lyric}`);AM.finish();},ontimeout: err => reject(err),onerror: err => reject(err)});// CoverAM.add();GM_xmlhttpRequest({method: 'GET',url: tag.cover,responseType: 'blob',timeout: 5 * 1000,onload: async res => {const blob = res.response;const arraybuffer = await readAsArrayBuffer(blob);const imagebuffer = BufferExport.Buffer.from(arraybuffer);await flac.importPictureFromBuffer(imagebuffer);AM.finish();},ontimeout: err => reject(err),onerror: err => reject(err)});AM.finishEvent = true;} catch(err) {reject(err);}});}function readAsArrayBuffer(file) {return new Promise(function (resolve, reject) {const reader = new FileReader();reader.onload = () => {resolve(reader.r###lt);};reader.onerror = reject;reader.readAsArrayBuffer(file);});}// Save url/Blob/File to filefunction saveFile(dataURLorBlob, filename, mimeType=null) {let url = dataURLorBlob, isObjURL = false;if (typeof url !== 'string') {const mimedBlob = new Blob([dataURLorBlob], { type: mimeType || dataURLorBlob.type });url = URL.createObjectURL(mimedBlob);isObjURL = true;}if (GM_info.scriptHandler === 'Tampermonkey' && GM_info.downloadMode !== 'disabled') {GM_download({ name: filename, url, onload: revoke });} else {const a = $CrE('a');a.href = url;a.download = filename;a.click();revoke();}function revoke() {isObjURL && setTimeout(() => URL.revokeObjectURL(url));}}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) {// targetpath = 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 functionsif (typeof target !== 'function') {throw new TypeError('hooker.hook: Hook functions only');}// Check args validif (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 functionconst 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 functionsconfig.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 stackreturn hook_return && hook_return.hasOwnProperty('value') ? hook_return.value : target.apply(_this, args);}parent[prop] = hooker;// Idconst 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) {// unhooktry {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 makeBooleanSettings(settings) {for (const setting of settings) {makeBooleanMenu(setting.text, setting.key, setting.defaultValue, setting.callback, setting.initCallback);}function makeBooleanMenu(texts, key, defaultValue=false, callback=null, initCallback=false) {const initialVal = GM_getValue(key, defaultValue);const initialText = texts[initialVal + 0];let id = GM_registerMenuCommand(initialText, onClick/*, {autoClose: false}*/);initCallback && callback(key, initialVal);function onClick() {const newValue = !GM_getValue(key, defaultValue);const newText = texts[newValue + 0];GM_setValue(key, newValue);GM_unregisterMenuCommand(id);id = GM_registerMenuCommand(newText, onClick/*, {autoClose: false}*/);typeof callback === 'function' && callback(key, newValue);}}}function rewriteResponse(xhr, json) {const response = JSON.stringify(json);const propDesc = {value: response,writable: false,configurable: true,enumerable: true};Object.defineProperties(xhr, {'response': propDesc,'responseText': propDesc});}})();