/* jshint esversion: 8 */
// ==UserScript==
// @name 网页音乐播放自动下载音乐文件
// @namespace javyliu
// @version 1.2
// @description 在酷狗音乐播放页面下载所听歌曲到本地,仅在chrome下测试通过,当第一次打开播放界面时,如果仅播放一首歌,那么是通过hash变化触发下载,也就是在列表页再次点击新的一首歌时会触发下载,试听音乐不下载,不会重复下载
// @author javy_liu
// @include *://*.kugou.com/*
// @include *://*.xiami.com/*
// @include *://music.163.com/*
// @include *://y.qq.com/*
// @require https://cdn.jsdelivr.net/npm/jquery@3.2.1/dist/jquery.min.js
// @grant GM_download
// @grant GM_openInTab
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_xmlhttpRequest
// @grant unsafeWindow
// @grant GM_setClipboard
// @grant GM_getResourceURL
// @grant GM_getResourceText
// @grant GM_notification
// @grant GM_deleteValue
// @license GPL License
// @connect *
// ==/UserScript==
// kugou.com 音乐下载
(function() {
'use strict';
if(unsafeWindow.jQuery){
$ = unsafeWindow.jQuery;
}
//从url中提取指定key的值
function getHashParams(key) {
var arr = location.hash.replace("#", "").split("&"), keyValue = "";
for (var i = 0;i<arr.length;i++) {
if (arr[i].split("=")[0] == key) {
keyValue = arr[i].split("=")[1];
break;
}
}
return keyValue;
}
//通过文档对像得到react组件对像,虾米用
function get_react_com(dom, traverseUp = 0) {
const key = Object.keys(dom).find(key=>key.startsWith("__reactInternalInstance$"));
const domFiber = dom[key];
if (domFiber == null) return null;
// react <16
if (domFiber._currentElement) {
let compFiber = domFiber._currentElement._owner;
for (let i = 0; i < traverseUp; i++) {
compFiber = compFiber._currentElement._owner;
}
return compFiber._instance;
}
// react 16+
const GetCompFiber = fiber=>{
//return fiber._debugOwner; // this also works, but is __DEV__ only
let parentFiber = fiber.return;
while (typeof parentFiber.type == "string") {
parentFiber = parentFiber.return;
}
return parentFiber;
};
let compFiber = GetCompFiber(domFiber);
for (let i = 0; i < traverseUp; i++) {
compFiber = GetCompFiber(compFiber);
}
return compFiber.stateNode;
}
//封装通知
function notify(txt,title="通知"){
GM_notification({
title: txt,
text: title,
// highlight: true,
timeout: 5000,
ondone: function(){
console.log("关闭了通知");
}
});
}
//下载url指定的资源,并指定文件名
function promise_download(res_url, file_name) {
return new Promise(function (resolve, reject) {
GM_download({
url: res_url,
name: file_name,
onload: function () {
resolve(`${file_name} 下载完成`);
},
onerror: function (error) {
reject(`${file_name} 下载失败 ${error.error}`);
}
});
});
}
//for kugou url fetch
function promise_fetch(req_url){
return new Promise(function(resolve, reject){
GM_xmlhttpRequest({
method: "GET",
url: req_url,
headers: { "Content-Type": "application/x-www-form-urlencoded" },
responseType: "json",
onload: function(r){
// console.log(r);
if(r.readyState == 4){
var res = r.response.data;
console.log("mp3地址", res.play_url);
resolve(res);
}
},
onerror: function(err){
console.error("请求地址失败", err);
reject("请求地址失败");
}
});
});
}
//is_free_part 为1时为试听, 不下载试听
//传入一个对像数组[{hash:xxx, encode_album_audio_id:xxx}]
//for of 内部 break, return 会跳出循环.
let list = GM_getValue("download_list") || {};
let base_url = location.href;
const KuGou = 'kugou.com';
const XiaMi = 'xiami.com';
const Netease = '163.com';
const Qq = 'y.qq.com';
let reg = new RegExp(`${KuGou}|${XiaMi}|${Netease}|${Qq}`.replace(/\./g, "\\."));
let match_domain = base_url.match(reg)[0];
let exec_in_kugou = function(){
let play_list = JSON.parse($.jStorage.get("k_play_list"));
//播放页面第一次打开为列表时,批量下载列表,否则通过监听hash地址变化触发下载
if(play_list && play_list.length > 1){
console.log("有列表:", play_list);
download_kugou(play_list).then(function(){
console.log("列表中的已下载完,增加单曲监听");
window.addEventListener("hashchange", function(ev){
download_kugou([{'Hash': ev.target.Hash, 'encode_album_audio_id': ev.target.encode_album_audio_id}]);
});
});
}else{
window.addEventListener("hashchange", function(ev){
download_kugou([{'Hash': ev.target.Hash, 'encode_album_audio_id': ev.target.encode_album_audio_id}])
.then(function(_return){
//单曲时如果列表不只一首,则播放下一首
if(_return == 1 && (JSON.parse($.jStorage.get("k_play_list"))).length > 1){
console.log("跳过--------------");
$("#next").trigger("click");//不播放试听音乐
}
});
});
}
};
let download_kugou = async function(ary_obj){
for (var obj of ary_obj) {
let _hash = obj.Hash;
let _album_id = obj.encode_album_audio_id;
let req_url = "https://wwwapi.kugou.com/yy/index.php?r=play/getdata&hash=" + _hash + "&encode_album_audio_id=" + _album_id + "&dfid=&mid=&platid=4";
// https://wwwapi.kugou.com/yy/index.php?r=play/getdata&dfid=2mSxKE1FMBgg4N2TLT4Y3yic&appid=1014&mid=6bc5c6880bc2ce18c248fb6b88eb0ccc&platid=4&encode_album_audio_id=6eliwgeb&_=1677045356215
console.log("请求地址:", req_url);
try {
//已下载的不下载,也不提醒
if (list[_hash]) {
console.log("已下载",obj);
continue;
}
var res = await promise_fetch(req_url);
//试听音乐也加到已加载列表
if (res.is_free_part) {
list[res.hash] = 1;
var txt = `${res.audio_name}为试听音乐`;
console.log(txt);
notify(txt);
//如果是试听音乐且是单曲播放时,则返回1,利于后期处理
if(ary_obj.length == 1){
return 1;
}
continue;
}
var extname = res.play_url.match(/(\.[\w]+?$)/)[1];
await promise_download(res.play_url, res.audio_name + extname);
list[res.hash] = 1;
GM_setValue("download_list", list);
console.log(res);
notify(res);
} catch (error) {
console.error(error);
notify(error);
}
}
};
let exec_in_xiami = function(){
let play_box_com = get_react_com($(".player")[0]);
let inject_methods = ['play','playMusic','playNext','playPrev'];
let ori_mtds = [];
//注入方法
for (let i=0,len=inject_methods.length; i<len; i++) {
ori_mtds[i] = play_box_com[inject_methods[i]];
play_box_com[inject_methods[i]] = function() {
ori_mtds[i].apply(this,arguments);
xiami_download(this);
}.bind(play_box_com);
}
};
let xiami_download = function(cobj){
console.count("--------xiami_download called----");
try {
let song_id = cobj.audio.currentSrc.match(/fn=(\d+)_/)[1];
if (!song_id) {
throw new Error("歌曲id不存在");
}
if(list[`xm_${song_id}`]){
console.log(`${song_id} 已下载`);
return;
}
let song_item = cobj.props.activePlayList.find((item) => {
return item.id == song_id;
});
if (!song_item) {
throw new Error("歌曲不存在");
}
console.log(song_item.detail.songName);
let play_info = song_item.playInfo.find((item)=>{
return item.listenFile && item.listenFile.length > 0;
});
console.log(play_info);
let song_name = `${song_item.detail.songName.replace(/\s+/g,"_")}_${song_item.detail.artistName}.${play_info.format}`;
promise_download(play_info.listenFile, song_name).then(res => {
list[`xm_${song_id}`] = 1;
GM_setValue("download_list", list);
notify(`${song_name} 下载完成!` );
});
} catch (error) {
console.error(error);
}
/*
// k1=$0.__reactInternalInstance$twjwzf1adie.return.stateNode;
// p1=k1.play;
// k1.play=function(){p1.apply(this,arguments);console.log("------play",arguments)}.bind(k1);
// p2=k1.playMusic;
// k1.playMusic=function(){p2.apply(this,arguments);console.log("------playMusic",arguments)}.bind(k1);
// p3=k1.playNext ;
// k1.playNext=function(){p3.apply(this,arguments);console.log("------playNext",arguments)}.bind(k1);
// p4=k1.playPrev;
// k1.playPrev=function(){p4.apply(this,arguments);console.log("------playPrev",arguments)}.bind(k1);
// p5=k1.props.playMusic;
// k1.props.playMusic=function(){p5.apply(this,arguments);console.log("------k1.props.playMusic",arguments)}.bind(k1.props);
// p6=k1.props.setPlayListPlayInfo;
// k1.props.setPlayListPlayInfo=function(){p6.apply(this,arguments);console.log("------k1.props.setPlayListPlayInfo",arguments)}.bind(k1.props);
// p7=k1.props.setPlayListDetail;
// k1.props.setPlayListDetail=function(){p7.apply(this,arguments);console.log("------k1.props.setPlayListDetail",arguments)}.bind(k1.props);
*/
};
let exec_in_netease = function(){
let ori_change = unsafeWindow.onplaychange;
unsafeWindow.onplaychange = function(){
ori_change.apply(this,arguments);
console.log("----------onplaychange");
// setTimeout(() => {
netease_download();
// }, 1000);
}.bind(unsafeWindow);
};
let netease_download = function(){
try {
if(!unsafeWindow.player) return 0;
let url = unsafeWindow.cAi5n;
let playinfo = unsafeWindow.player.getPlaying();
console.log("--------播放信息:",playinfo);
if(list[`netease_${playinfo.track.id}`]){
console.log(`${playinfo.track.id} 已下载`);
return;
}
let extname = url.match(/\.([\w]+?$)/)[1];
let articles = playinfo.track.artists.map((item) => item.name).join("_");
let song_name = `${playinfo.track.name}_${articles}.${extname}`;
promise_download(url, song_name).then(res => {
list[`netease_${playinfo.track.id}`] = 1;
GM_setValue("download_list", list);
notify(`${song_name} 下载完成!` );
})
.catch(error => {
console.error(error);
});
} catch (error) {
console.error(error);
}
};
//注入的getSongUrl方法会被调用多次,所以需要限制下,如果下载失败需刷新后才会再次触发下载
let pre_download_list = {};
let exec_in_qq = function(){
let ori_mtd = unsafeWindow.MUSIC.util.getSongUrl;
unsafeWindow.MUSIC.util.getSongUrl = function(arg){
let res = ori_mtd.call(this,arg);
setTimeout(() => void qq_download(arg), 1000 );
return res;
};
};
let qq_download = function(playinfo){
try {
if( pre_download_list[playinfo.songid]==1) return 0;
if(!playinfo.songid) return 0;
if(list[`qq_${playinfo.songid}`]){
console.log(`${playinfo.songid} 已下载`);
return 0;
}
//预下载标记
pre_download_list[playinfo.songid]=1;
let song_url = playinfo.playUrl || $("audio")[0].src;
// console.log("-------------需下载对像地址----------------");
// console.log("1:",playinfo.playUrl);
// console.log("2:",song_url);
let extname = song_url.match(/\.(\w+)\?/)[1];
let song_name = `${playinfo.songname}_${playinfo.singername}.${extname}`;
promise_download(song_url, song_name).then(res => {
list[`qq_${playinfo.songid}`] = 1;
pre_download_list[playinfo.songid] = 0;
GM_setValue("download_list", list);
notify(`${song_name} 下载完成!` );
})
.catch(error => {
pre_download_list[playinfo.songid] = 0;
console.error(error);
});
} catch (error) {
pre_download_list[playinfo.songid] = 0;
console.error(error);
}
};
console.log("----------------------",match_domain);
switch (match_domain) {
case KuGou:
exec_in_kugou();
break;
case XiaMi:
exec_in_xiami();
break;
case Netease:
exec_in_netease();
break;
case Qq:
setTimeout(()=>{
exec_in_qq();
},2000);
break;
default:
break;
}
/*
music_title[0]="163.com"
music_title[1]= "y.qq.com"
music_title[2]= "kugou.com"
music_title[3]= "kuwo.cn"
music_title[4]= "xiami.com"
music_title[5]= "taihe.com"
music_title[6]= "1ting.com"
music_title[7]= "migu.cn"
music_title[8]= "qingting.fm"
music_title[9]= "lizhi.fm"
music_title[10]= "ximalaya.com"
*/
//头部添加清除下载记录按钮
$("body").prepend(`<button id='clear_download_list' style="position:absolute;left:0px;top:0;z-index:1000;background-color: green;padding:10px;color:white;opacity: 0.7;">clear download list</button>`);
$("#clear_download_list").on("click", function(){
GM_deleteValue("download_list");
console.log("list:",GM_getValue("download_list"));
notify("清除下载记录成功");
});
})();