// ==UserScript== // @name _喜馬拉雅批量下載器! // @version 1.1.7 // @description 喜馬拉雅批量下載器,可以批量下載喜馬拉雅的專輯! // @author See7di@Gmail.com // @match *://www.ximalaya.com/* // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @grant GM_setClipboard // @grant GM_download // @icon https://www.ximalaya.com/favicon.ico // @require https://unpkg.com/vue@2 // @require https://unpkg.com/sweetalert@2.1.2/dist/sweetalert.min.js // @require https://unpkg.com/jquery@3.2.1/dist/jquery.min.js // @require https://greasyfork.org/scripts/435476-priatelib/code/PriateLib.js?version=1021495 // @require https://unpkg.com/ajax-hook@2.0.3/dist/ajaxhook.min.js // @homepageURL https://greasyfork.org/zh-CN/scripts/456764-%E5%96%9C%E9%A6%AC%E6%8B%89%E9%9B%85%E6%89%B9%E9%87%8F%E4%B8%8B%E8%BC%89%E5%99%A8 // @contributionURL https://www.youtube.com/@read-book // @license MIT // @namespace https://www.youtube.com/@read-book // @downloadURL none // ==/UserScript== (function() { 'use strict'; const global_setting = { number: true, offset: 0, export: 'url', aria2_wsurl: "ws://127.0.0.1:6800/jsonrpc", aria2_secret: "", } function initSetting() { //document.querySelectorAll('.xm-player-case').$remove(1); var setting; if (!GM_getValue('priate_script_xmly_data')) { GM_setValue('priate_script_xmly_data', { multithreading: true, left: 20, top: 100, manualMusicURL: null, quality: 1 }) } setting = GM_getValue('priate_script_xmly_data') if (setting.quality !== 0) setting.quality = setting.quality || 1; GM_setValue('priate_script_xmly_data', setting) } // 手动获取音频地址功能 function manualGetMusicURL() { let windowID = getRandStr("1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM", 100) function getRandStr(chs, len) { let str = ""; while (len--) { str += chs[parseInt(Math.random() * chs.length)]; } return str; } (function() { let playOriginal = HTMLAudioElement.prototype.play; function play() { let link = this.src; window.top.postMessage(Array("audioVideoCapturer", link, windowID, "link"), "*"); return playOriginal.call(this); } HTMLAudioElement.prototype.play = play; HTMLAudioElement.prototype.play.toString = HTMLAudioElement.prototype.play.toString.bind(playOriginal); })(); if (window.top == window) { window.addEventListener("message", function(event) { if (event.data[0] == "audioVideoCapturer") { var setting = GM_getValue('priate_script_xmly_data') setting.manualMusicURL = event.data[1] GM_setValue('priate_script_xmly_data', setting) } }); } } manualGetMusicURL() function injectDiv() { var priate_script_div = document.createElement("div") priate_script_div.innerHTML = `

批量下載器!  音質 : {{qualityStr}}


操作標題
下載 等待中 {{item.progress}} OK 失敗 | 網址 {{item.title}}
` GM_addStyle(` #priate_script_div{font-size:12px;background-color:#fff;color:#666;text-align:center;padding:5px;border-radius:5px;border:2px solid #5d718f;box-shadow:2px 2px 2px #bbb;z-index:9999;position:fixed;} /*#priate_script_div:hover{box-shadow:3px 3px 3px #333;transition:box-shadow 0.3s;}*/ .priate_script_hide{padding:0 !important;border:none !important;} a{cursor:pointer;text-decoration:none;} #priate_script_div table{text-align:center;border:0;margin:5px auto;padding:2px;border-collapse:collapse;display:block;height:400px;overflow-y:scroll;} #priate_script_div td{border:1px solid #ddd;padding:6px 6px 4px;max-width:300px;word-wrap:break-word;} #priate_script_div th{border:1px solid #ddd;padding:3px 6px 4px;} #priate_script_div button{font-size:12px;display:inline-block;box-shadow:2px 2px 2px #bbb;border-radius:4px;border:1px solid #31435e;background-color:#5d718f;color:#fff;text-decoration:none;padding:5px 10px;margin:5px 10px;} #priate_script_div button:hover{cursor:pointer;box-shadow:0px 0px 0px #bbb;transition:box-shadow 0.2s;} #priate_script_div .hide-button{z-index:2147483647;width:32px;height:32px;cursor:pointer;position:fixed;left:0px;bottom:0px;color:#660000;text-align:center;line-height:32px;margin:10px;border-width:1px;border-style:solid;border-color:#ccc;border-image:initial;border-radius:100%;} #priate_script_div .hide-button:hover{background-color:rgba(240, 223, 175, 0.9);} #priate_script_div textarea{height:50px;width:200px;background-color:#fff;border:1px solid #000000;padding:4px;} .swal-button--low{background-color:#FFFAEB !important;color:#946C00;} .swal-button--high{background-color:#ebfffc !important;color:#00947e;} .swal-button--mid{background-color:#ECF6FD !important;color:#55ACEE;} .checkMusicBox{transform:scale(1.5,1.5);cursor:pointer;} `); document.querySelector("html").appendChild(priate_script_div) var setting = GM_getValue('priate_script_xmly_data') document.getElementById("priate_script_div").style.left = (setting.left || 20) + "px"; document.getElementById("priate_script_div").style.top = (setting.top || 100) + "px"; } function dragFunc(id) { var Drag = document.getElementById(id); var setting = GM_getValue('priate_script_xmly_data') Drag.onmousedown = function(event) { var ev = event || window.event; event.stopPropagation(); var disX = ev.clientX - Drag.offsetLeft; var disY = ev.clientY - Drag.offsetTop; document.onmousemove = function(event) { var ev = event || window.event; setting.left = ev.clientX - disX Drag.style.left = setting.left + "px"; setting.top = ev.clientY - disY Drag.style.top = setting.top + "px"; Drag.style.cursor = "move"; GM_setValue('priate_script_xmly_data', setting) }; }; Drag.onmouseup = function() { document.onmousemove = null; this.style.cursor = "default"; }; }; function initQuality() { //音质修改 ah.proxy({ onRequest: (config, handler) => { handler.next(config); }, onError: (err, handler) => { handler.next(err) }, onResponse: (response, handler) => { const setting = GM_getValue('priate_script_xmly_data') if (response.config.url.indexOf("mobile.ximalaya.com/mobile-playpage/track/v3/baseInfo") != -1) { const setting = GM_getValue('priate_script_xmly_data') const data = JSON.parse(response.response) const playUrlList = data.trackInfo.playUrlList var replaceUrl; for (var num = 0; num < playUrlList.length; num++) { var item = playUrlList[num] if (item.qualityLevel == setting.quality) { replaceUrl = item.url break } } replaceUrl && playUrlList.forEach((item) => { item.url = replaceUrl }) response.response = JSON.stringify(data) } if (setting.quality == 2 && response.config.url.indexOf("www.ximalaya.com/revision/play/v1/audio") != -1) { const setting = GM_getValue('priate_script_xmly_data') var resp = JSON.parse(response.response) var data = resp.data delete data.src response.response = JSON.stringify(resp) } handler.next(response) } }) unsafeWindow.XMLHttpRequest = XMLHttpRequest } initSetting() //初始化脚本设置 injectDiv() //注入脚本div initQuality() //初始化音质修改 async function getSimpleMusicURL1(item) { //第一种获取musicURL的方式,任意用户均可获得,不可获得VIP音频 var res = null if (item.url) { res = item.url } else { const timestamp = Date.parse(new Date()); var url = `https://mobwsa.ximalaya.com/mobile-playpage/playpage/tabs/${item.id}/${timestamp}` $.ajax({ type: 'get',url: url,async: false,dataType: "json", success: function(resp) { if (resp.ret === 0) { const setting = GM_getValue('priate_script_xmly_data') const trackInfo = resp.data.playpage.trackInfo; if (setting.quality == 0) { res = trackInfo.playUrl32 } else if (setting.quality == 1) { res = trackInfo.playUrl64 } // res = res || trackInfo.downloadUrl } } }); } return res } async function getSimpleMusicURL2(item) { //第二种获取musicURL的方式,任意用户均可获得,不可获得VIP音频 var res = null if (item.url) { res = item.url } else { var url = `https://www.ximalaya.com/revision/play/v1/audio?id=${item.id}&ptype=1` $.ajax({ type: 'get',url: url,async: false,dataType: "json", success: function(resp) { if (resp.ret == 200) res = resp.data.src; } }); } return res } async function getAllMusicURL1(item) { //获取任意音频方法 var res = null var setting; if (item.url) { res = item.url } else { const all_li = document.querySelectorAll('.sound-list>ul li'); for (var num = 0; num < all_li.length; num++) { var li = all_li[num] const item_a = li.querySelector('a'); const id = item_a.href.split('/')[item_a.href.split('/').length - 1] if (id == item.id) { li.querySelector('div.all-icon').click() while (!res) { await Sleep(1) setting = GM_getValue('priate_script_xmly_data') res = setting.manualMusicURL } setting.manualMusicURL = null GM_setValue('priate_script_xmly_data', setting) li.querySelector('div.all-icon').click() break } } } if (!res && item.isSingle) { document.querySelector('div.play-btn').click() while (!res) { await Sleep(1) setting = GM_getValue('priate_script_xmly_data') res = setting.manualMusicURL } setting.manualMusicURL = null GM_setValue('priate_script_xmly_data', setting) document.querySelector('div.play-btn').click() } return res } var vm = new Vue({ //处理数据等逻辑 el: '#priate_script_div', data: { copyMusicURLProgress: 0, setting: GM_getValue('priate_script_xmly_data'), data: [], musicList: [], isDownloading: false, cancelDownloadObj: null, stopDownload: false }, methods: { loadMusic() { const all_li = document.querySelectorAll('.sound-list>ul li'); var result = []; all_li.forEach((item) => { const item_a = item.querySelector('a'); const number = item.querySelector('span.num') ? parseInt(item.querySelector('span.num').innerText) + global_setting.offset : 0 const title = item_a.title.trim().replace(/\\|\/|\?|\?|\*|\"|\“|\”|\'|\‘|\’|\<|\>|\{|\}|\[|\]|\【|\】|\:|\:|\、|\^|\$|\!|\~|\`|\|/g, '').replace(/\./g, '-') const music = { id: item_a.href.split('/')[item_a.href.split('/').length - 1], number, title: global_setting.number ? `${number}-${title}` : title, isDownloading: false, isDownloaded: false, progress: 0, } result.push(music) }) if (result.length == 0 && location.pathname.split('/')[location.pathname.split('/').length - 1]) { //如果没有取到数据,则判断为单个音频 const music = { id: location.pathname.split('/')[location.pathname.split('/').length - 1], title: document.querySelector('h1.title-wrapper').innerText, isDownloading: false, isDownloaded: false, progress: 0, isSingle: true } result.push(music) } if (result.length == 0) { //如果仍未取到数据 swal("未获取到数据,请先选择一个专辑页面并等待页面完全加载!", { icon: "error", buttons: false, timer: 3000, }); } this.data = result this.musicList = [] this.data.forEach((item) => { this.musicList.push(item) }) }, async getMusicURL(item) { var res = await getSimpleMusicURL1(item) res = res || await getSimpleMusicURL2(item) res = res || await getAllMusicURL1(item) this.$set(item, 'url', res) return res }, async downloadMusic(item) { //this.isDownloading = true item.isDownloading = true item.isFailued = false var _this = this const details = { url: item.url || await this.getMusicURL(item), name: item.title.trim().replace(/\\|\/|\?|\?|\*|\"|\“|\”|\'|\‘|\’|\<|\>|\{|\}|\[|\]|\【|\】|\:|\:|\、|\^|\$|\!|\~|\`|\|/g, '').replace(/\./g, '-'), onload: function(e) { _this.isDownloading = false item.isDownloading = false item.isDownloaded = true _this.selectAllMusic() }, onerror: function(e) { _this.isDownloading = false console.log(e) item.isDownloading = false if (e.error != 'aborted') item.isFailued = true }, onprogress: function(d) { item.progress = ((Math.round(d.done / d.total * 10000 / 100.00))<100) ? (Math.round(d.done / d.total * 10000 / 100.00)) + "%" : '99%'; } } this.cancelDownloadObj = GM_download(details) }, async sequenceDownload(index, data) { //顺序下载 this.isDownloading = true const item = data[index] if (!item) { this.isDownloading = false this.selectAllMusic() this.stopDownload = false return; }; if (item.isDownloading || item.isDownloaded || this.stopDownload) return this.sequenceDownload(index + 1, data); item.isDownloading = true item.isFailued = false const _this = this const details = { url: item.url || await this.getMusicURL(item), name: item.title.trim().replace(/\\|\/|\?|\?|\*|\"|\“|\”|\'|\‘|\’|\<|\>|\{|\}|\[|\]|\【|\】|\:|\:|\、|\^|\$|\!|\~|\`|\|/g, '').replace(/\./g, '-'), onload: function(e) { item.isDownloading = false item.isDownloaded = true _this.cancelDownloadObj = _this.sequenceDownload(index + 1, data) }, onerror: function(e) { console.log(e) item.isDownloading = false if (e.error != 'aborted') item.isFailued = true _this.cancelDownloadObj = _this.sequenceDownload(index + 1, data) }, onprogress: function(d) { item.progress = (Math.round(d.done / d.total * 10000 / 100.00)) + "%"; } } this.cancelDownloadObj = GM_download(details) return this.cancelDownloadObj }, async copyMusic(item) { item.url = item.url || await this.getMusicURL(item) GM_setClipboard(item.url) swal("复制成功!", { icon: "success", buttons: false, timer: 1000, }); }, async downloadAll() { //下载全部音频 var all_down = document.querySelectorAll('#priate_script_table ._down'); for (var num = 0; num < all_down.length; num++) { all_down[num].click(); //console.log(all_down[num].innerText) await Sleep(0.2); } all_down=null; //alert("下載完畢!"); }, selectAllMusic() { if (this.musicList.length == this.notDownloadedData.length) { this.musicList = [] } else { this.musicList = [] this.data.forEach((item) => { !item.isDownloaded && this.musicList.push(item) }) } }, //取消下载功能 cancelDownload() { this.stopDownload = true this.cancelDownloadObj.abort() }, // 修改音质功能 changeQuality() { const _this = this swal("请选择需要设置的音质,注意:此功能处于测试中,超高音质仅登陆后VIP可用,且部分音频不存在超高音质。(切换后将刷新页面)", { buttons: { low: "标准", mid: "高清", high: "超高(仅VIP)", }, }).then((value) => { var setting = GM_getValue('priate_script_xmly_data') var changeFlag = true switch (value) { case "low": setting.quality = 0; break; case "mid": setting.quality = 1; break; case "high": setting.quality = 2; break; default: changeFlag = false } GM_setValue('priate_script_xmly_data', setting) _this.setting = setting changeFlag && location.reload() }); }, openDonate() { showDonate() } }, computed: { filterData() { if (this.isDownloading) { return this.musicList } else { return this.data } }, notDownloadedData() { return this.data.filter((item) => { return item.isDownloaded == false }) }, qualityStr() { var quality = (this.setting.quality >= 0 && this.setting.quality <= 2) ? this.setting.quality : 3 const str = ["标准", "高清", "超高", "未知"] return str[quality] }, qualityColor() { var quality = (this.setting.quality >= 0 && this.setting.quality <= 2) ? this.setting.quality : 3 const color = ["#5d718f", "#55ACEE", "#00947e", "#337ab7"] return color[quality] } } }) //设置div可拖动 dragFunc("priate_script_div"); })();