Greasy Fork

Greasy Fork is available in English.

网易云音乐高音质支持

去除网页版网易云音乐仅可播放低音质(96Kbps)的限制,强制播放高音质版本

当前为 2015-12-02 提交的版本,查看 最新版本

// ==UserScript==
// @name         网易云音乐高音质支持
// @version      1.2
// @description  去除网页版网易云音乐仅可播放低音质(96Kbps)的限制,强制播放高音质版本
// @match        *://music.163.com/*
// @include      *://music.163.com/*
// @author       864907600cc
// @icon         https://secure.gravatar.com/avatar/147834caf9ccb0a66b2505c753747867
// @run-at       document-end
// @grant        none
// @noframes
// @namespace    http://ext.ccloli.com
// ==/UserScript==

// getTrackURL 源码来自 Chrome 扩展程序 网易云音乐增强器(Netease Music Enhancement) by [email protected]
// 菊苣这个加密算法你是怎么知道的 _(:3
var getTrackURL = function getTrackURL (dfsId) {
	var byte1 = '3' + 'g' + 'o' + '8' + '&' + '$' + '8' + '*' + '3' 
		+ '*' + '3' + 'h' + '0' + 'k' + '(' + '2' + ')' + '2';
	var byte1Length = byte1.length;
	var byte2 = dfsId + '';
	var byte2Length = byte2.length;
	var byte3 = [];
	for (var i = 0; i < byte2Length; i++) {
		byte3[i] = byte2.charCodeAt(i) ^ byte1.charCodeAt(i % byte1Length);
	};

	byte3 = byte3.map(function(i) {
		return String.fromCharCode(i)
	}).join('');

	results = CryptoJS.MD5(byte3).toString(CryptoJS.enc.Base64);
	results = results.replace(/\//g, '_').replace(/\+/g, '-');

	var url = 'http://m1.music.126.net/' + results + '/' + byte2 + '.mp3';
	return url;
}

// 由于黄易修改了相关变量, 网易云音乐增强器 里对播放器的 hack 基本失效,故关于切换音质的部分均重写,找变量找的我心好累 _(:3
var magicIt = function magicIt(){
	console.log('施放魔法!变变变!');
	if (window.NEJ_CONF) { // 网易云音乐主站
		// 黄易将播放列表存在了 localStorage 里,通过遍历 + getTrackURL 就可以将其的 mp3 地址替换为音质尽可能高的 mp3 地址
		var data = JSON.parse(localStorage.getItem('track-queue'));
		data.map(function(elem){
			// 部分音乐没有高音质
			elem.mp3Url = getTrackURL(elem.hMusic ? elem.hMusic.dfsId : elem.mMusic ? elem.mMusic.dfsId : elem.lMusic.dfsId);
		});
		localStorage.setItem('track-queue', JSON.stringify(data));

		// 由于直接改 localStorage 只能刷新后生效,看了两天的源码,终于找出了黄易将播放列表存在哪个变量里了 _(:3
		// 卧槽这些变量名只有两个字符,基本看不出什么端倪,我还差点以为我不会 js 了,结果找了好久终于试出来了,闲的无聊的可以看我 QQ 空间里这两天的吐槽 _(:3
		// 不过相比某度,感觉黄易的代码写得真(ta)是(ma)高(kan)大(bu)上(dong)啊 _(:3」∠∠∠∠∠∠∠∠∠∠)__________
		NEJ.P("nm.d").fW.bL().bA['track-queue'] = data;
	}
	else { // 网易云音乐外链框架
		ce[0].qT.map(function(elem){
			// 部分音乐没有高音质
			elem.mp3Url = getTrackURL(elem.hMusic ? elem.hMusic.dfsId : elem.mMusic ? elem.mMusic.dfsId : elem.lMusic.dfsId);
		});
		
	}
}

// 但是直接改了播放列表也不会对当前播放的曲目产生任何变化,player.seek() 只是重新播放而已,并没有重新向服务器发起请求 _(:3
// 尝试 hack 原函数,实现自动转换播放列表的音质,不过看来我小看了闭包的作用 _(:3
/*略*/

// 这个 bL.WG 可以实现强制重新载入播放列表,但是在随机播放模式下,其播放的第一曲始终是播放列表的第一曲,故废弃
// 话说黄易每次在歌单点击播放时都会 bL.WG(undefined, 0) 清空播放列表再 bL.WG(newPlaylist, 1) (伪代码)添加播放列表,感觉有点多此一举,还是因为我没理解 bL.WG 的含义 _(:3
/*
bL.WG(data, 1); 
*/

// 由于有反馈称更改播放列表时会出现当前播放的音乐重新载入的情况,由前,可监听播放列表数变为 0 的情况
var isReset = 0;

// 由于无法直接 hack 原函数,onstorage 事件不会在当前标签页触发,所以监听播放器的播放列表曲目数的变化来转换播放列表,我真是太机智了 _(:3
document.getElementsByClassName('icn-list')[0].addEventListener('DOMSubtreeModified', function(){
	console.log('播放列表发生了变化,当前曲目数为 ' + this.textContent);
	// 如前,黄易在播放一个新的歌单时会清空播放列表,此时其值为 0,/* 故此时不做处理 */ 可用于判断是否是播放新的列表还是更改原来的列表
	if (this.textContent == '0') return isReset = 1;
	// 莫名其妙地会出现播放最后一曲的情况,加个定时器去除冲突
	setTimeout(function(){
		magicIt();
		// 直接播放歌单内曲目虽然会被转换,但是播放时还是会使用原始音质,这里再判断一次
		if (isReset || document.querySelector('.add.f-pr > .j-flag.tip').textContent == '已开始播放') {
			// 对于当前曲目的处理方法……先播放上一曲再播放下一曲不就好了?我真是天(chun)才 _(:3
			document.getElementsByClassName('prv')[0].click();
			document.getElementsByClassName('nxt')[0].click();
			isReset = 0;
		}
	}, 10);
});