您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
主要是把网易云音乐歌单导出,将歌单组合成导入QQ音乐的格式,方便导入QQ音乐
// ==UserScript== // @name 导出个人网易云音乐歌单 // @namespace http://tampermonkey.net/ // @version 2024-09-09 // @description 主要是把网易云音乐歌单导出,将歌单组合成导入QQ音乐的格式,方便导入QQ音乐 // @author 手机千万不要放在外套口袋!!! // @match https://music.163.com/ // @icon https://www.google.com/s2/favicons?sz=64&domain=163.com // @license MIT // @grant none // ==/UserScript== (function() { 'use strict'; let isStart = false let isFinsh = false let RESULT = [] let CVR_LIST = [] init() async function init() { initUi() } // 初始化ui function initUi() { const htmlStr = ` <div id="_panel" style="width: 260px; display:flex;flex-direction:column; align-items:center;justify-content:center; position: fixed;top:0px;right:20px;z-index:999999;border-radius: 6px;padding: 10px;background-color: #fff;box-shadow: 0 0 10px rgba(0,0,0,.6);"> <p style="font-size: 16px; color: #000000;margin-bottom: 10px;">请确保当前页面为个人中心页面(https://music.163.com/#/user/home?id=xxxxxx需登录),否则正确导出歌单。 点击查看按钮前需点击开始按钮,如果要关闭查看,只需再次点击查看按钮即可</p> <div style="display: flex; align-items: center; justify-content: center; margin-bottom: 10px;"> <div id="_start" style="padding: 6px 20px; border-radius: 6px; background-color: #ff0000; color: #fff; cursor: pointer;border: 1px solid #ff0000;">开始</div> <div id="_download" style="padding: 6px 20px; border-radius: 6px; background-color: #999999; color: #ccc; cursor: pointer;border: 1px solid #999999; margin-left: 20px;">查看</div> <div id="_hidden" style="padding: 6px 20px; border-radius: 6px; color: #000000; cursor: pointer; border: 1px solid #000000;margin-left: 20px;">收起</div> </div> <div id="_info" style="display:none;max-height:400px; overflow-y: auto;"> <p id="_count" style="text-align: center;margin-bottom: 10px;">共0个歌单</p> <p id="_wait" style="text-align: center;margin-bottom: 10px;">请等待...</p> </div> <div> ` const showPanel = ` <div id="_show_panel" style="display:none;position: fixed;top:0px;right:20px;z-index:999999;padding: 6px 20px; border-radius: 6px; background-color: #ff0000; color: #fff; cursor: pointer;border: 1px solid #ff0000;">显示</div> ` document.body.insertAdjacentHTML('beforeend', htmlStr) document.body.insertAdjacentHTML('beforeend', showPanel) const sEl = document.querySelector('#_start') const dEl = document.querySelector('#_download') const cEl = document.querySelector('#_count') const hEl = document.querySelector('#_hidden') const pEl = document.querySelector('#_panel') const showEl = document.querySelector('#_show_panel') sEl.addEventListener('click', async () => { if(isStart) { isStart = false sEl.style.background = "#ff0000" sEl.style.borderColor = "#ff0000" return } isStart = true document.querySelector('#_wait').style.display = 'block' sEl.style.background = "#999999" sEl.style.borderColor = "#999999" sEl.style.color = '#ccc' CVR_LIST = getAllcvrlst() if(CVR_LIST.length === 0) { isStart = false sEl.style.background = "#ff0000" sEl.style.borderColor = "#ff0000" sEl.style.color = "#ffffff" alert('没有歌单,请确保当前页面为存在自定义歌单的个人中心页面,如:https://music.163.com/#/user/home?id=xxxxx') return } cEl.innerText = `共${CVR_LIST.length}个歌单` const infoEl = document.querySelector('#_info') infoEl.style.display = 'block' for await (let item of CVR_LIST) { item.list = await getCvrDetail(item.href) document.body.removeChild(document.querySelector('#cvr_detail')) } CVR_LIST.forEach((item,index) => { const p = document.createElement('p') p.style.color = '#000000' p.style.fontSize = '14px' p.style.marginBottom = '10px' p.id = `_p_${index}` p.innerHTML += `<p>${item.title}:共<span style="color: #ff0000;">${item.list.length}</span>首,已完成<span class="percent" style="color: #ff0000;">0%</span></p>` infoEl.appendChild(p) }) document.querySelector('#_wait').style.display = 'none' RESULT = await handleCvrListData() isStart = false isFinsh = true sEl.style.background = "#ff0000" sEl.style.borderColor = "#ff0000" sEl.style.color = "#ffffff" sEl.style.display = "none" dEl.style.background = "#ff0000" dEl.style.borderColor = "#ff0000" dEl.style.color = "#ffffff" }) dEl.addEventListener('click', async () => { if(!isFinsh) { alert('请先点击开始按钮,获取数据') return } if(document.querySelector('#_show_detail')) { if(document.querySelector('#_show_detail').style.display !== 'none') { document.querySelector('#_show_detail').style.display = 'none' return } else if(document.querySelector('#_show_detail').style.display === 'none'){ document.querySelector('#_show_detail').style.display = 'block' return } return } const r = RESULT.join("\n") dEl.insertAdjacentHTML('afterend',` <div id="_show_detail" style="position: fixed;top: 50%;left:50%;z-index:999999;border-radius: 10px;padding: 20px;background-color: #ffffff;transform: translate(-50%, -50%);"> <div id="_copy" style="width:fit-content;margin-bottom:10px; padding: 6px 20px; border-radius: 6px; background-color: #ff0000; color: #fff; cursor: pointer;border: 1px solid #ff0000;">复制</div> <div style="white-space: break-spaces;width:900px;height:700px;overflow-y:auto;"> ${r} </div> </div> `) document.querySelector("#_copy").addEventListener("click", () => { copyToClip(r) }) }) hEl.addEventListener('click', () => { pEl.style.display = 'none' showEl.style.display = 'block' }) showEl.addEventListener('click', () => { pEl.style.display = 'block' showEl.style.display = 'none' }) } // 处理视图更新 function updateView(index, percent) { console.log("更新:",index,percent) const percentSpan = document.querySelector(`#_p_${index} .percent`) percentSpan.innerHTML = `${percent}%` } // 获取所有歌单 function getAllcvrlst() { const lis = Array.from(document.querySelector('#g_iframe').contentDocument.querySelectorAll('#cBox li')) const cvrlst = lis.map(li => { const a = li.querySelector('a') return { title: a.title, href: a.href, el: a, list: [] } }) return cvrlst } // 获取歌单下的所有歌曲 function getCvrDetail(src) { return new Promise((res,rej) => { try { // 创建一个iframe const iframe = document.createElement("iframe"); iframe.src = src iframe.id = 'cvr_detail' iframe.style = 'display:none' document.body.appendChild(iframe) iframe.onload = function () { iframe.contentWindow.postMessage('loaded', '*'); const spanInfo = Array.from(iframe.contentDocument.querySelectorAll("#song-list-pre-cache table tr .s-fc3 .opt span:nth-of-type(2)")) const result = spanInfo.map(span => ({ author: span.dataset.resAuthor, name: span.dataset.resName, pic: span.dataset.resPic, id: span.dataset.resId, })) res(result) } } catch (error) { rej(e) } }) } // 处理数据 function handleCvrListData() { return new Promise((res,rej) => { try { const obj = { name: "", percent: 0 } const allCvrList = CVR_LIST.map((item,index) => { obj['name'] = item.title return ({ t: item.title, songs: item.list.map(ite => { return `${ite.author} 《${ite.name}》` }), id: index }) }).map((item) => { return item.songs.reduce((pre,cur,idx) => { obj['percent'] = Math.round((idx+1)/item.songs.length)*100 updateView(item.id,obj['percent']) return pre + cur + '\n' },`====${item.t}====\n`) // return `${item.t}\n` + item.songs.join('\n') }) console.log(allCvrList) res(allCvrList) } catch (error) { rej(error) } }) } function copyToClip(content, message) { if(!navigator.clipboard) { let txa = document.createElement('textarea') txa.value = content document.body.appendChild(txa) txa.select() let res = document.execCommand('copy') document.body.removeChild(txa) if (message == null) { alert("复制成功"); } else{ alert(message); } } else { navigator.clipboard.writeText(content).then(() => { if (message == null) { alert("复制成功"); } else { alert(message); } }).catch(err => { console.error('复制失败:', err); alert("复制失败"); }); } } })();