// ==UserScript==
// @name 网易云:音乐/歌词下载,云盘快速上传周杰伦
// @namespace https://github.com/Cinvin/myuserscripts
// @license MIT
// @version 0.3.2
// @description 在歌曲页面歌曲和歌词下载,在个人主页一键云盘上传解锁周杰伦
// @author cinvin
// @match https://music.163.com/*
// @grant GM_download
// @grant GM_getValue
// @grant GM_setValue
// @grant unsafeWindow
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
const weapiRequest=unsafeWindow.NEJ.P("nej.j").be0x
if (location.href.match('song')){
//song
let cvrwrap=document.querySelector(".cvrwrap")
if(cvrwrap){
let songId=Number(location.href.match(/\d+$/g));
let songTitle=document.head.querySelector("[property~='og:title'][content]").content;
if(!document.querySelector(".u-btni-play-dis")){
let newItem = document.createElement('div');
newItem.className="out s-fc3"
let dl = document.createElement('a');
dl.text = '歌曲下载';
dl.className="des s-fc7"
dl.addEventListener('click', () => {
dwonloadSong(songId,songTitle,dl)
})
newItem.appendChild(dl)
cvrwrap.appendChild(document.createElement('br'))
cvrwrap.appendChild(newItem)
}
//lyric
weapiRequest("/api/song/lyric", {
type: "json",
query: {
id: songId,
tv: -1,
lv: -1,
rv: -1,
kv: -1,
},
onload: function(content) {
let lyricObj=content
GM_setValue('lyric',content)
if(content.romalrc.lyric.length>0){
let lrcShowItem = document.createElement('div');
lrcShowItem.className="out s-fc3"
let p = document.createElement('p');
p.innerHTML = '歌词显示:';
let tl = document.createElement('a');
tl.text = '翻译';
tl.className="des s-fc7"
tl.addEventListener('click', () => {
switchLyric('tlyric');
})
p.appendChild(tl)
let span=document.createElement('span')
span.innerHTML = ' / ';
p.appendChild(span)
let rl = document.createElement('a');
rl.text = '罗马音';
rl.className="des s-fc7"
rl.addEventListener('click', () => {
switchLyric('romalrc');
})
p.appendChild(rl)
lrcShowItem.appendChild(p)
cvrwrap.appendChild(document.createElement('br'))
cvrwrap.appendChild(lrcShowItem)
}
let lrcDownloadItem = document.createElement('div');
lrcDownloadItem.className="out s-fc3"
let p = document.createElement('p');
p.innerHTML = '歌词下载:';
let lrc = document.createElement('a');
lrc.text = '原词';
lrc.className="des s-fc7"
lrc.addEventListener('click',() =>{
downloadLyric('lrc',songTitle)
})
p.appendChild(lrc)
if(content.tlyric.lyric.length>0){
let span=document.createElement('span')
span.innerHTML = ' / ';
p.appendChild(span)
let tlyric = document.createElement('a');
tlyric.text = '原词+翻译';
tlyric.className="des s-fc7"
tlyric.addEventListener('click',() =>{
downloadLyric('lrc-tlyric',songTitle)
})
p.appendChild(tlyric)
}
if(content.romalrc.lyric.length>0){
let span=document.createElement('span')
span.innerHTML = ' / ';
p.appendChild(span)
let romalrc = document.createElement('a');
romalrc.text = '原词+罗马音';
romalrc.className="des s-fc7"
romalrc.addEventListener('click',() =>{
downloadLyric('lrc-romalrc',songTitle)
})
p.appendChild(romalrc)
}
lrcDownloadItem.appendChild(p)
cvrwrap.appendChild(document.createElement('br'))
cvrwrap.appendChild(lrcDownloadItem)
},
});
}
}
function levelDesc(level) {
switch (level) {
case 'standard':
return '标准'
case 'higher':
return '较高'
case 'exhigh':
return '极高'
case 'lossless':
return '无损'
case 'hires':
return 'Hi-Res'
default:
return level
}
}
function dwonloadSong(songId,songTitle,dlbtn){
weapiRequest("/api/song/enhance/player/url/v1", {
type: "json",
query: {
ids: JSON.stringify([songId]),
level: 'hires',
encodeType: 'flac'
},
onload: function(content) {
// console.log(content)
if(content.data[0].code==200){
var filename=songTitle+'.'+content.data[0].type.toLowerCase();
var dlurl=content.data[0].url
var size=content.data[0].size
var level=levelDesc(content.data[0].level)
GM_download({
url: dlurl,
name: filename,
onprogress:function(e){
dlbtn.text=`正在下载${level}品质音乐... (${Math.round(e.loaded/e.totalSize*10000)/100}%)`
},
onload: function () {
dlbtn.text='下载'
},
onerror :function(){
dlbtn.text='下载失败'
}
});
}
else{
dlbtn.text='获取下载链接失败 可能无下载权限';
}
},
onerror: function(content) {
console.log(content)
}
});
}
function switchLyric(type) {
let lyric=GM_getValue('lyric')
let lyric1=lyric.lrc.lyric
let lyric2=null
switch (type) {
case 'tlyric':
lyric2=lyric.tlyric.lyric
break
case 'romalrc':
lyric2=lyric.romalrc.lyric
break
}
let lyric_content=document.querySelector("#lyric-content")
let songId=Number(location.href.match(/\d+$/g));
let lyrictimelines=unsafeWindow.NEJ.P("nm.ut").bFK0x(lyric1, lyric2);
let a9j = unsafeWindow.NEJ.P("nej.e")
a9j.dm1x(lyric_content, "m-lyric-content", {
id: songId,
nolyric: lyric.nolyric,
limit: lyric2 ? 6 : 13,
lines: lyrictimelines.lines,
scrollable: lyrictimelines.scrollable,
thirdCopy: a9j.v0x(lyric_content, "thirdCopy") == "true",
copyFrom: a9j.v0x(lyric_content, "copyFrom")
});
lyric.scrollable = lyrictimelines.scrollable;
lyric.songId = songId;
//a9j.dm1x("user-operation", "m-user-operation", lyric);
unsafeWindow.NEJ.P("nej.v").s0x("flag_ctrl", "click", () => {
var bBc9T = a9j.A0x("flag_more");
if (a9j.bE0x(bBc9T, "f-hide")) {
a9j.x0x(bBc9T, "f-hide");
a9j.A0x("flag_ctrl").innerHTML = '收起'
} else {
a9j.w0x(bBc9T, "f-hide");
a9j.A0x("flag_ctrl").innerHTML = '展开'
}
})
}
function downloadLyric(type,songTitle){
let lyric=GM_getValue('lyric')
let content=lyric.lrc.lyric
if (type=='lrc-tlyric'){
content=combineLyric(lyric.lrc.lyric,lyric.tlyric.lyric)
}
else if (type=='lrc-romalrc'){
content=combineLyric(lyric.lrc.lyric,lyric.romalrc.lyric)
}
let lrc = document.createElement('a');
let data=new Blob([content], {type:'type/plain'})
let fileurl = URL.createObjectURL(data)
lrc.href=fileurl
lrc.download=songTitle+'.lrc'
lrc.click()
URL.revokeObjectURL(data);
}
function combineLyric(lyric1,lyric2){
let lyrictimelines=unsafeWindow.NEJ.P("nm.ut").bFK0x(lyric1, lyric2);
let content=''
lyrictimelines.lines.forEach(line=>{
let linecontent=`[${line.tag}] ${line.lyric}`
linecontent=linecontent.replace('
','\n').trim()+'\n'
content=content+linecontent
})
return content.trim()
}
function showPopUp(msg){
unsafeWindow.NEJ.P("nm.x").iQ3x(msg);
}
function showTips(tip,type){
//type:1 √ 2:!
unsafeWindow.NEJ.P("nm.l").bb0x.I0x({
tip: tip,
type: type
})
}
if(location.href.match('user')){
let urlUserId=Number(location.href.match(/\d+$/g));
let editArea=document.querySelector('#head-box > dd > div.name.f-cb > div > div.edit')
if(editArea && urlUserId==unsafeWindow.GUser.userId){
//个人主页
let btn=document.createElement('a')
btn.id='cloudBtn'
btn.className='u-btn2 u-btn2-1'
let btni=document.createElement('i')
btni.innerHTML='上传周杰伦'
btn.appendChild(btni)
btn.setAttribute("hidefocus","true");
btn.style.marginRight='10px';
btn.addEventListener('click',startUpload)
editArea.insertBefore(btn,editArea.lastChild)
let cloudDesc=document.createElement('b')
cloudDesc.id='cloudDesc'
cloudDesc.style.marginRight='10px';
editArea.insertBefore(cloudDesc,btn)
function startUpload(){
let cloudDesc=document.getElementById('cloudDesc')
let cloudBtn=document.getElementById('cloudBtn')
cloudBtn.setAttribute("disabled","true");
cloudDesc.innerHTML='正在获取配置...'
//https://raw.githubusercontent.com/Cinvin/cdn/main/ncmJay.json
//https://cdn.jsdelivr.net/gh/Cinvin/cdn@1.0.1/ncmJay.json
fetch('https://cdn.jsdelivr.net/gh/Cinvin/cdn@1.0.1/ncmJay.json')
.then(r => r.json())
.then(r=>{
let songList=r.data
//console.log(songList)
let ids=songList.map(item=>{ return {'id':item.id} })
//获取需上传的song
cloudDesc.innerHTML='获取需要上传的歌曲...'
weapiRequest("/api/v3/song/detail", {
type: "json",
method: "post",
sync: true,
query: {c: JSON.stringify(ids)},
onload: function(content) {
//console.log(content)
let songs=Array()
let len=content.songs.length
for(let i =0;i{return item.id==content.songs[i].id})
songs.push({
id:content.songs[i].id,
name:content.songs[i].name,
album:content.songs[i].al.name,
artists:content.songs[i].ar.map(ar=>ar.name).join(),
filename:content.songs[i].name+'.'+config.ext,
ext:config.ext,
md5:config.md5,
size:config.size,
})
}
}
if ( songs.length == 0 ){
cloudDesc.innerHTML=''
showPopUp('没有需要上传的文件')
return
}
let md5UploadObj=new Md5Upload(songs,cloudDesc,onMD5Finnish)
md5UploadObj.start()
}
})
})
}
function onMD5Finnish(md5UploadObj){
let cloudBtn=document.getElementById('cloudBtn')
let cloudDesc=document.getElementById('cloudDesc')
cloudDesc.innerHTML=''
cloudBtn.setAttribute("disabled","true");
let text=`成功上传${md5UploadObj.sucessCount}首歌曲\n`
if(md5UploadObj.failCount>0){
text+='以下歌曲上传失败:'
md5UploadObj.forEach(idx=>{text+=`${md5UploadObj.failList[idx].name} `})
}
showPopUp(text)
}
class Md5Upload {
idx=0
get currentIndex(){return this.idx}
set currentIndex(currentIndex){
if(this.idx==currentIndex) return
this.idx=currentIndex
if(currentIndex= this.count) return
let song=this.songs[this.currentIndex]
try{
weapiRequest("/api/cloud/upload/check", {
method: "POST",
type: "json",
data:{
songId:song.id,
md5:song.md5,
length:song.size,
ext:song.ext,
version:1,
bitrate:128,
},
onload: (res1)=>{
if(res1.code!=200){
console.error(song.name,'1.检查资源',res1)
this.onUploadFail()
return
}
console.log(song.name,'1.检查资源',res1)
//step2 上传令牌
weapiRequest("/api/nos/token/alloc", {
method: "POST",
type: "json",
query: {
filename: song.filename,
length:song.size,
ext: song.ext,
type: 'audio',
bucket: 'jd-musicrep-privatecloud-audio-public',
local: false,
nos_product: 3,
md5: song.md5
},
onload: (res2)=>{
if(res2.code!=200){
console.error(song.name,'2.获取令牌',res2)
this.onUploadFail()
return
}
console.log(song.name,'2.获取令牌',res2)
//step3 提交
weapiRequest("/api/upload/cloud/info/v2", {
method: "POST",
type: "json",
data: {
md5: song.md5,
songid: res1.songId,
filename: song.filename,
song: song.name,
album: song.album,
artist: song.artists,
bitrate: '128',
resourceId: res2.result.resourceId,
},
onload: (res3)=>{
if(res3.code!=200){
console.error(song.name,'3.提交文件',res3)
this.onUploadFail()
return
}
console.log(song.name,'3.提交文件',res3)
//step4 发布
weapiRequest("/api/cloud/pub/v2", {
cookie:true,
method: "POST",
type: "json",
data: {
songid: res3.songId,
},
onload: (res4)=>{
if(res4.code!=200 && res4.code!=201){
console.error(song.name,'4.发布资源',res4)
this.onUploadFail()
return
}
console.log(song.name,'4.发布资源',res4)
//step5 关联
if(res4.privateCloud.songId!=song.id){
weapiRequest("/api/cloud/user/song/match", {
method: "POST",
type: "json",
sync:true,
data: {
songId: res4.privateCloud.songId,
adjustSongId: song.id,
},
onload: (res5)=>{
if(res5.code!=200){
console.error(song.name,'5.匹配歌曲',res5)
this.onUploadFail()
return
}
console.log(song.name,'5.匹配歌曲',res5)
console.log(song.name,'完成')
//完成
this.onUploadSucess()
},
onerror: function(res) {
console.error(song.name,'5.匹配歌曲',res)
this. onUploadFail()
}
})
}
else{
console.log(song.name,'完成')
//完成
this.onUploadSucess()
}
},
onerror: function(res) {
console.error(song.name,'4.发布资源',res)
this.onUploadFail()
}
})
},
onerror: function(res) {
console.error(song.name,'3.提交文件',res)
this.onUploadFail()
}
});
},
onerror: function(res) {
console.error(song.name,'2.获取令牌',res)
this.onUploadFail()
}
});
},
onerror: function(res) {
console.error(song.name,'1.检查资源',res)
this.onUploadFail()
}
})
}
catch(e){
console.error(e);
this.onUploadFail()
}
}
onUploadFail(){
this.failCount+=1
this.failList.push(this.currentIndex)
let song=this.songs[this.currentIndex]
showTips(`上传${song.name} - ${song.artists} - ${song.album}失败`,2)
this.currentIndex+=1
this.uploadSong()
}
onUploadSucess(){
this.sucessCount+=1
let song=this.songs[this.currentIndex]
showTips(`上传${song.name} - ${song.artists} - ${song.album}成功`,1)
this.currentIndex+=1
this.uploadSong()
}
}
}
}
})();