- /* 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("清除下载记录成功");
- });
-
-
- })();