// ==UserScript== // @name 网易云歌单 M4A 批量下载(自动播放拦截) // @namespace https://music.163.com/ // @version 2.7.6 // @description 歌单 m4a:media URL 来自 XHR/performance 拦截或 audio/video(任一先满足且未在 ignore 列表即采用);行内解析歌名;GM_download 直链;任务列表与统计;暂停/继续。 // @author getMusic // @match https://music.163.com/* // @match https://*.music.163.com/* // @match https://*.music.126.net/* // @match http://*.music.126.net/* // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_download // @grant unsafeWindow // @connect * // @run-at document-start // ==/UserScript== (function () { 'use strict'; var SCRIPT_VERSION = '2.7.6'; var MSG_DL_NAME = 'NETEASE_M4A_DL_NAME'; var MSG_DL_DONE = 'NETEASE_M4A_DL_DONE'; var POPUP_NAME_PREFIX = '__NETEASE_M4A_DL_NAME__:'; function encodePopupFilename(name) { try { return POPUP_NAME_PREFIX + encodeURIComponent(String(name || 'audio.m4a')); } catch (e) { return POPUP_NAME_PREFIX + 'audio.m4a'; } } function decodePopupFilename(raw) { if (!raw || typeof raw !== 'string') return ''; if (raw.indexOf(POPUP_NAME_PREFIX) !== 0) return ''; try { return decodeURIComponent(raw.slice(POPUP_NAME_PREFIX.length)); } catch (e) { return raw.slice(POPUP_NAME_PREFIX.length); } } /** 与 Blob 同源下载时,使用 NFC 规范化,减少中文乱码 */ function normalizeFilenameForDownload(name) { if (!name) return 'audio.m4a'; try { return String(name).normalize('NFC'); } catch (e) { return String(name); } } function triggerBlobDownload(blob, filename) { var fn = normalizeFilenameForDownload(filename); if (typeof GM_download === 'function') { var u = URL.createObjectURL(blob); try { GM_download({ url: u, name: fn, saveAs: false, onload: function () { setTimeout(function () { try { URL.revokeObjectURL(u); } catch (e) {} }, 500); }, onerror: function () { try { URL.revokeObjectURL(u); } catch (e2) {} triggerBlobDownloadAnchor(blob, fn); }, }); return; } catch (e) { try { URL.revokeObjectURL(u); } catch (e3) {} triggerBlobDownloadAnchor(blob, fn); return; } } triggerBlobDownloadAnchor(blob, fn); } function triggerBlobDownloadAnchor(blob, fn) { var u = URL.createObjectURL(blob); var a = document.createElement('a'); a.href = u; a.download = fn; a.rel = 'noopener'; a.style.display = 'none'; var parent = document.body || document.documentElement; if (!parent) return; parent.appendChild(a); a.click(); parent.removeChild(a); setTimeout(function () { try { URL.revokeObjectURL(u); } catch (e) {} }, 2500); } function gmFetchBlobAsBlob(url) { return new Promise(function (resolve, reject) { if (typeof GM_xmlhttpRequest === 'undefined') { reject(new Error('no-GM')); return; } GM_xmlhttpRequest({ method: 'GET', url: url, responseType: 'arraybuffer', timeout: 180000, onload: function (r) { if (r.status >= 200 && r.status < 300) { resolve(new Blob([r.response], { type: 'audio/mp4' })); } else { reject(new Error('HTTP ' + r.status)); } }, onerror: function () { reject(new Error('net')); }, ontimeout: function () { reject(new Error('timeout')); }, }); }); } /** * CDN 预览页(新标签打开 m4a 直链时出现