Greasy Fork

来自缓存

Greasy Fork is available in English.

网页音乐播放自动下载音乐文件

在酷狗音乐播放页面下载所听歌曲到本地,仅在chrome下测试通过,当第一次打开播放界面时,如果仅播放一首歌,那么是通过hash变化触发下载,也就是在列表页再次点击新的一首歌时会触发下载,试听音乐不下载,不会重复下载

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

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

})();