Skip to content

Instantly share code, notes, and snippets.

@xymopen
Forked from ccloli/hook_xhr.js
Last active December 22, 2015 14:24
Show Gist options
  • Save xymopen/d28a23d625bebc9d4aa2 to your computer and use it in GitHub Desktop.
Save xymopen/d28a23d625bebc9d4aa2 to your computer and use it in GitHub Desktop.
Netease Music HQ Support (Refactor)
function process( input ) {
console.log( input );
return input;
};
function getPrototypeChain( END_OF_PROTOTYPE_CHAIN ) {
var returns = [ ], currentProto = END_OF_PROTOTYPE_CHAIN;
do {
returns.push( currentProto );
currentProto = Object.getPrototypeOf( currentProto );
// The end of a prototype chain is null;
} while ( currentProto !== null );
return returns;
};
if ( Object.hasOwnProperty.call( new XMLHttpRequest(), "responseText" ) ) {
self.XMLHttpRequest = ( function( XMLHttpRequest ) {
var xhrProto = XMLHttpRequest.prototype,
fakeProto = getPrototypeChain( xhrProto ).reduce( function( fakeProto, proto ) {
Object.keys( proto ).reduce( function( fakeProto, property ) {
var descriptor = Object.getOwnPropertyDescriptor( proto, property );
if ( // native method from prototype chain can only be called on the extriact instance
// so need to correct this reference.
typeof descriptor.value === "function" &&
// only process native method
Function.prototype.toString.call( descriptor.value ).indexOf( "[native code]" ) >= 0 ) {
descriptor.value = function() {
debugger;
return xhrProto[ property ].apply( Object.getPrototypeOf( this ), arguments );
};
fakeProto[ property ] = descriptor;
}
return fakeProto;
}, fakeProto );
return fakeProto;
}, { } );
fakeProto.responseText = ( function() {
var responseTextDesc = Object.getOwnPropertyDescriptor( new XMLHttpRequest(), "responseText" );
delete responseTextDesc.value;
delete responseTextDesc.writable;
responseTextDesc.get = function() {
var xhr = Object.getPrototypeOf( this );
return xhr._NEED_HANDLE ?
process( xhr.responseText ) : xhr.responseText;
};
responseTextDesc.set = function() {
Object.getPrototypeOf( this ).responseText = newValue;
return newValue;
};
return responseTextDesc;
} )();
function fakeXMLHttpRequest() {
return Object.create( new XMLHttpRequest, fakeProto );
};
fakeXMLHttpRequest.prototype = XMLHttpRequest.prototype;
return fakeXMLHttpRequest;
} )( self.XMLHttpRequest );
}
Hooks.method( XMLHttpRequest.prototype, "open", function ( original, argv ) {
"use strict";
var url = argv[ 1 ];
if ( url.indexOf( "/weapi/song/enhance/player/url" ) >= 0 ) {
this._NEED_HANDLE = true;
argv[ 1 ] = url.replace('/enhance/player/url', '/detail' );
}
return original.apply( this, argv );
} );
function process( input ) {
console.log( input );
return input;
};
if ( Object.hasOwnProperty.call( XMLHttpRequest.prototype, "responseText" ) ) {
Hooks.getter( XMLHttpRequest.prototype, "responseText", function( original, argv ) {
"use strict";
return this._NEED_HANDLE ?
process( original.apply( this, argv ) ) : original.apply( this, argv );
} );
}
Hooks.method( XMLHttpRequest.prototype, "open", function ( original, argv ) {
"use strict";
var url = argv[ 1 ];
if ( url.indexOf( "/weapi/song/enhance/player/url" ) >= 0 ) {
this._NEED_HANDLE = true;
argv[ 1 ] = url.replace('/enhance/player/url', '/detail' );
}
return original.apply( this, argv );
} );
var Hooks = {
"fn": function fn( $fn, onInvoke ) { // parameter fn refers to function fn( $fn, onInvoke ) itself
"use strict";
return function() {
return onInvoke.call( this, $fn, arguments );
};
},
"property": function property( object, propertyName, onGet, onSet ) {
"use strict";
var descriptor, oldValue;
if ( Object.prototype.hasOwnProperty.call( object, propertyName ) ) {
descriptor = Object.getOwnPropertyDescriptor( object, propertyName );
if ( Object.prototype.hasOwnProperty.call( descriptor, "value" ) ) {
oldValue = descriptor.value;
delete descriptor.value;
delete descriptor.writable;
} else if ( Object.prototype.hasOwnProperty.call( descriptor, "get" ) ) {
oldValue = descriptor.get.call( object );
} else {
oldValue = undefined;
}
descriptor.get = function get() {
return onGet.call( this, oldValue );
};
descriptor.set = function set( newValue ) {
oldValue = onSet.call( this, oldValue, newValue );
return oldValue;
};
Object.defineProperty( object, propertyName, descriptor );
} else {
throw new Error( "ERR_PROPERTY_NOT_DEFINED" );
}
},
"getter": function getter( object, propertyName, onGet ) {
"use strict";
var descriptor, $getter; // variable getter refers to function getter( object, propertyName, onGet ) itself
if ( Object.prototype.hasOwnProperty.call( object, propertyName ) ) {
descriptor = Object.getOwnPropertyDescriptor( object, propertyName );
$getter = descriptor.get;
if ( Object.prototype.hasOwnProperty.call( descriptor, "get" ) &&
typeof $getter === "function" ) {
descriptor.get = Hooks.fn( $getter, onGet );
} else {
throw new Error( "ERR_NOT_A_GETTER" );
}
Object.defineProperty( object, propertyName, descriptor );
} else {
throw new Error( "ERR_PROPERTY_NOT_DEFINED" );
}
},
"setter": function setter( object, propertyName, onSet ) {
"use strict";
var descriptor, $setter; // variable setter refers to function setter( object, propertyName, onSet ) itself
if ( Object.prototype.hasOwnProperty.call( object, propertyName ) ) {
descriptor = Object.getOwnPropertyDescriptor( object, propertyName );
$setter = descriptor.set;
if ( Object.prototype.hasOwnProperty.call( descriptor, "set" ) &&
typeof $setter === "function" ) {
descriptor.set = Hooks.fn( $setter, onSet );
} else {
throw new Error( "ERR_NOT_A_SETTER" );
}
Object.defineProperty( object, propertyName, descriptor );
} else {
throw new Error( "ERR_PROPERTY_NOT_DEFINED" );
}
},
"method": function method( object, methodName, onInvoke ) {
"use strict";
var $method; // variable method refers to function method( object, methodName, onInvoke ) itself
if ( Object.prototype.hasOwnProperty.call( object, methodName ) ) {
$method = object[ methodName ];
if ( typeof $method === "function" ) {
object[ methodName ] = Hooks.fn( $method, onInvoke );
} else {
throw new Error( "ERR_NOT_A_METHOD" );
}
} else {
throw new Error( "ERR_PROPERTY_NOT_DEFINED" );
}
}
};
// ==UserScript==
// @name 网易云音乐高音质支持
// @namespace http://ext.ccloli.com
// @version 2.3
// @description 去除网页版网易云音乐仅可播放低音质(96Kbps)的限制,强制播放高音质版本
// @match *://music.163.com/*
// @include *://music.163.com/*
// @author 864907600cc
// @refactor xymopen
// @icon https://secure.gravatar.com/avatar/147834caf9ccb0a66b2505c753747867
// @run-at document-start
// @grant none
// ==/UserScript==
( function() {
"use strict";
// ==Debug==
var SI_PREFIXES = [
{ "multiple": 1e24, "symbol": "Y" },
{ "multiple": 1e21, "symbol": "Z" },
{ "multiple": 1e18, "symbol": "E" },
{ "multiple": 1e15, "symbol": "P" },
{ "multiple": 1e12, "symbol": "T" },
{ "multiple": 1e9, "symbol": "G" },
{ "multiple": 1e6, "symbol": "M" },
{ "multiple": 1e3, "symbol": "k" },
{ "multiple": 1e2, "symbol": "h" },
{ "multiple": 1e1, "symbol": "da" },
{ "multiple": 1, "symbol": "" },
{ "multiple": 1e-1, "symbol": "d" },
{ "multiple": 1e-2, "symbol": "c" },
{ "multiple": 1e-3, "symbol": "m" },
{ "multiple": 1e-6, "symbol": "μ" },
{ "multiple": 1e-9, "symbol": "n" },
{ "multiple": 1e-12, "symbol": "p" },
{ "multiple": 1e-15, "symbol": "f" },
{ "multiple": 1e-18, "symbol": "a" },
{ "multiple": 1e-21, "symbol": "z" },
{ "multiple": 1e-24, "symbol": "y" }
];
var BINARY_PREFIXES = [
{ "multiple": Math.pow( 2, 80 ), "symbol": "Yi" },
{ "multiple": Math.pow( 2, 70 ), "symbol": "Zi" },
{ "multiple": Math.pow( 2, 60 ), "symbol": "Ei" },
{ "multiple": Math.pow( 2, 50 ), "symbol": "Pi" },
{ "multiple": Math.pow( 2, 40 ), "symbol": "Ti" },
{ "multiple": Math.pow( 2, 30 ), "symbol": "Gi" },
{ "multiple": Math.pow( 2, 20 ), "symbol": "Mi" },
{ "multiple": Math.pow( 2, 10 ), "symbol": "Ki" }
];
var addPrefix = ( function() {
var SCALE = 1, FIXED = 2;
function toTrimmedFixed( f, n ) {
return f.toFixed( n ).replace( /0+$/, "" ).replace( /\.$/, "" );
};
function addPrefix( n, prefixes ) {
"use strict";
var i, prefix, multiple;
for ( i = 0; i < prefixes.length; i += 1 ) {
prefix = prefixes[ i ];
multiple = prefix.multiple;
if ( n >= SCALE * multiple ) {
return toTrimmedFixed( n / multiple, FIXED ) + prefix.symbol;
}
}
return n.toString();
};
return addPrefix;
} )();
function binaryPrefix( n ) {
return addPrefix( n, BINARY_PREFIXES );
};
function metricPrefix( n ) {
return addPrefix( n, SI_PREFIXES.slice( 0, 8 ) );
};
var TimeTag = {
"parse": function parse( tag ) {
if ( /(\d{1,}:)?\d{1,2}:\d{1,2}(\.\d{1,3})?/.test( tag ) ) {
// split time into [ s.mm, m, h ]
return tag.split( ":" ).reverse().reduce( function( time, i, index ) {
return time + parseFloat( i ) * Math.pow( 60, index );
}, 0 );
} else {
return NaN;
}
},
"stringify": function stringify( time ) {
var i, times;
function complete( i ) {
return ( i < 10 ) ? "0" + i : i.toString();
};
if ( time < 0 ) {
throw new TypeError( "ERR_NEGATIVE_TIME" );
} else {
times = [ ];
for ( i = 0; i < 3; i += 1 ) {
times.push( time % 60 );
time = Math.floor( time / 60 );
}
return complete( times[ 2 ] ) + ":" + complete( times[ 1 ] ) + ":" + complete( times[ 0 ].toFixed( 3 ) );
}
}
};
// ==/Debug==
var Hooks = {
"fn": function fn( $fn, onInvoke ) { // parameter fn refers to function fn( $fn, onInvoke ) itself
return function() {
return onInvoke.call( this, $fn, arguments );
};
},
"property": function property( object, propertyName, onGet, onSet ) {
var descriptor, oldValue;
if ( Object.prototype.hasOwnProperty.call( object, propertyName ) ) {
descriptor = Object.getOwnPropertyDescriptor( object, propertyName );
if ( Object.prototype.hasOwnProperty.call( descriptor, "value" ) ) {
oldValue = descriptor.value;
delete descriptor.value;
delete descriptor.writable;
} else if ( Object.prototype.hasOwnProperty.call( descriptor, "get" ) ) {
oldValue = descriptor.get.call( object );
} else {
oldValue = undefined;
}
descriptor.get = function get() {
return onGet.call( this, oldValue );
};
descriptor.set = function set( newValue ) {
oldValue = onSet.call( this, oldValue, newValue );
return oldValue;
};
Object.defineProperty( object, propertyName, descriptor );
} else {
throw new Error( "ERR_PROPERTY_NOT_DEFINED" );
}
},
"getter": function getter( object, propertyName, onGet ) {
var descriptor, $getter; // variable getter refers to function getter( object, propertyName, onGet ) itself
if ( Object.prototype.hasOwnProperty.call( object, propertyName ) ) {
descriptor = Object.getOwnPropertyDescriptor( object, propertyName );
$getter = descriptor.get;
if ( Object.prototype.hasOwnProperty.call( descriptor, "get" ) &&
typeof $getter === "function" ) {
descriptor.get = Hooks.fn( $getter, onGet );
} else {
throw new Error( "ERR_NOT_A_GETTER" );
}
Object.defineProperty( object, propertyName, descriptor );
} else {
throw new Error( "ERR_PROPERTY_NOT_DEFINED" );
}
},
"setter": function setter( object, propertyName, onSet ) {
var descriptor, $setter; // variable setter refers to function setter( object, propertyName, onSet ) itself
if ( Object.prototype.hasOwnProperty.call( object, propertyName ) ) {
descriptor = Object.getOwnPropertyDescriptor( object, propertyName );
$setter = descriptor.set;
if ( Object.prototype.hasOwnProperty.call( descriptor, "set" ) &&
typeof $setter === "function" ) {
descriptor.set = Hooks.fn( $setter, onSet );
} else {
throw new Error( "ERR_NOT_A_SETTER" );
}
Object.defineProperty( object, propertyName, descriptor );
} else {
throw new Error( "ERR_PROPERTY_NOT_DEFINED" );
}
},
"method": function method( object, methodName, onInvoke ) {
var $method; // variable method refers to function method( object, methodName, onInvoke ) itself
if ( Object.prototype.hasOwnProperty.call( object, methodName ) ) {
$method = object[ methodName ];
if ( typeof $method === "function" ) {
object[ methodName ] = Hooks.fn( $method, onInvoke );
} else {
throw new Error( "ERR_NOT_A_METHOD" );
}
} else {
throw new Error( "ERR_PROPERTY_NOT_DEFINED" );
}
}
};
function getPrototypeChain( END_OF_PROTOTYPE_CHAIN ) {
var returns = [ ], currentProto = END_OF_PROTOTYPE_CHAIN;
do {
returns.push( currentProto );
currentProto = Object.getPrototypeOf( currentProto );
// The end of a prototype chain is null;
} while ( currentProto !== null );
return returns;
};
// modified from Chrome Extension 网易云音乐增强器(Netease Music Enhancement) by wanmingtom@gmail.com
var getTrackURL = ( function() {
"use strict";
var dict = "3go8&$8*3*3h0k(2)2",
dictLength = dict.length;
return function getTrackURL( dfsId, ext ) {
var plain = String( dfsId ),
cipher = plain.split( "" ).map( function( character, index ) {
return String.fromCharCode( character.charCodeAt( 0 ) ^ dict.charCodeAt( index % dictLength ) );
} ).join( "" ),
results = CryptoJS.MD5( cipher ).toString( CryptoJS.enc.Base64 ).replace( /\//g, "_" ).replace( /\+/g, "-" );
// 使用 p1 cdn 解决境外用户无法播放的问题
return "http://p1.music.126.net/" + results + "/" + plain + "." + ext;
};
} )();
/* function modifyURL( data, parentKey ) {
"use strict";
return data.map( function ( elem ) {
// 部分音乐没有高音质
var target = parentKey ? elem[ parentKey ] : elem;
target.mp3Url = getTrackURL( target.hMusic.dfsId || target.mMusic.dfsId || target.lMusic.dfsId );
return elem;
} );
}; */
function onGetResponseText( responseText ) {
try {
var output = { }, input = JSON.parse( responseText );
output.code = input.code;
output.data = input.songs.map( function( song ) {
var source = song.hMusic || song.mMusic || song.lMusic || song.bMusic;
// ==Debug==
console.group( song.name );
console.log( song.artists.map( function( artist ) {
return artist.name;
} ).join( ", " ) );
console.log( song.album.name );
console.log( "ID %i", song.id );
function detail( $detail, quality ) {
if ( $detail ) {
console.group( quality );
console.log( TimeTag.stringify( $detail.playTime / 1000 ) );
console.log( binaryPrefix( $detail.size ) + "B" );
console.log( metricPrefix( $detail.bitrate ) + "bps" );
console.log( metricPrefix( $detail.sr ) + "Hz" );
console.log( getTrackURL( $detail.dfsId, $detail.extension ) );
console.groupEnd( quality );
}
}
detail( song.hMusic, "HQ" );
detail( song.mMusic, "SQ" );
detail( song.lMusic, "Real Time" );
detail( song.bMusic, "Bad" );
console.groupEnd( song.name );
if ( song.hMusic ) {
console.log( "已选定 HQ 音源" );
} else if ( song.mMusic ) {
console.log( "已选定 SQ 音源 无更高品质可选" );
} else if ( song.lMusic ) {
console.log( "已选定实时音源 无更高品质可选" );
} else if ( song.hMusic ) {
console.log( "已选定最低品质音源 无更高品质可选" );
}
console.log( "已解密地址 %s", getTrackURL( source.dfsId, source.extension ) );
// ==/Debug==
return {
"id" : song.id,
"url" : getTrackURL( source.dfsId, source.extension ),
"br" : source.bitrate,
"size" : source.size,
// "md5" : "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"code" : input.code,
"expi" : 1200, // ???
"type" : source.extension,
"gain" : source.volumeDelta,
"fee" : song.fee,
"uf" : null, // ???
"canExtend" : false // ???
};
} );
return JSON.stringify( output );
/* function processRes( fn ) {
try {
var res = JSON.parse( responseText );
fn( res );
return JSON.stringify( res );
} catch ( error ) {
return responseText;
}
};
switch ( action[ 0 ] ) {
case 'album':
return processRes( function( res ) {
modifyURL( res.album.songs );
} );
case 'song':
return action[ 1 ] === 'detail' ? processRes( function( res ) {
modifyURL( res.album.songs );
} ) : responseText;
case 'playlist':
return action[ 1 ] === 'detail' ? processRes( function( res ) {
modifyURL( res.result.tracks );
} ) : responseText;
case 'dj':
return action[ 2 ] === 'byradio' ? processRes( function( res ) {
modifyURL( res.programs, 'mainSong' );
} ) : action[ 2 ] === 'detail' ? processRes( function( res ) {
res.program = modifyURL( [ res.program ], 'mainSong' )[0];
} ) : responseText;
default:
return responseText;
} */
} catch ( error ) {
return responseText;
}
};
if ( Object.hasOwnProperty.call( XMLHttpRequest.prototype, "responseText" ) ) {
// Chrome 43+
Hooks.getter( XMLHttpRequest.prototype, "responseText", function( original, argv ) {
return this._NEED_HANDLE ?
onGetResponseText( original.apply( this, argv ) ) : original.apply( this, argv );
} );
} else {
console.log( "正在伪造 XHR" );
// Chrome 6+
self.XMLHttpRequest = ( function( XMLHttpRequest ) {
var xhrProto = XMLHttpRequest.prototype,
fakeProto = getPrototypeChain( xhrProto ).reduce( function( fakeProto, proto ) {
Object.keys( proto ).reduce( function( fakeProto, property ) {
var descriptor = Object.getOwnPropertyDescriptor( proto, property );
if ( // native method from prototype chain can only be called on the extriact instance
// so need to correct this reference.
typeof descriptor.value === "function" &&
// only process native method
Function.prototype.toString.call( descriptor.value ).indexOf( "[native code]" ) >= 0 ) {
descriptor.value = function() {
return xhrProto[ property ].apply( Object.getPrototypeOf( this ), arguments );
};
fakeProto[ property ] = descriptor;
}
return fakeProto;
}, fakeProto );
return fakeProto;
}, { } );
fakeProto.responseText = ( function() {
var responseTextDesc = Object.getOwnPropertyDescriptor( new XMLHttpRequest(), "responseText" );
delete responseTextDesc.value;
delete responseTextDesc.writable;
responseTextDesc.get = function() {
var xhr = Object.getPrototypeOf( this );
return xhr._NEED_HANDLE ?
onGetResponseText( xhr.responseText ) : xhr.responseText;
};
responseTextDesc.set = function() {
Object.getPrototypeOf( this ).responseText = newValue;
return newValue;
};
return responseTextDesc;
} )();
function fakeXMLHttpRequest() {
return Object.create( new XMLHttpRequest, fakeProto );
};
fakeXMLHttpRequest.prototype = XMLHttpRequest.prototype;
return fakeXMLHttpRequest;
} )( self.XMLHttpRequest );
}
Hooks.method( XMLHttpRequest.prototype, "open", function ( original, argv ) {
var url = argv[ 1 ];
if ( url.indexOf( "/weapi/song/enhance/player/url" ) >= 0 ) {
// ==Debug==
console.log( "匹配 XHR %s", url );
// ==/Debug==
this._NEED_HANDLE = true;
argv[ 1 ] = url.replace( "/enhance/player/url", "/detail" );
// ==Debug==
console.log( "已重定向到 %s", url.replace( "/enhance/player/url", "/detail" ) );
// ==/Debug==
}
return original.apply( this, argv );
} );
} )();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment