// ==UserScript==
// @name 网易云:云盘上传周杰伦等歌手歌曲,音乐、歌词、乐谱下载
// @namespace https://github.com/Cinvin/myuserscripts
// @license MIT
// @version 1.3.3
// @description 个人主页:云盘快速上传并关联歌手歌曲,歌曲页:音乐、歌词、乐谱下载
// @author cinvin
// @match https://music.163.com/*
// @grant GM_xmlhttpRequest
// @grant GM_download
// @grant unsafeWindow
// @require https://lf3-cdn-tos.bytecdntp.com/cdn/expire-1-M/limonte-sweetalert2/11.4.4/sweetalert2.all.min.js
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
function getcookie(key) {
var cookies = document.cookie
, text = "\\b" + key + "="
, find = cookies.search(text);
if (find < 0)
{
return ""
}
find += text.length - 2;
var index = cookies.indexOf(";", find);
if (index < 0){
index = cookies.length
}
return cookies.substring(find, index) || ""
};
function weapiRequest(url,config){
let data=config.data
data.csrf_token = getcookie("__csrf");
url = url.replace("api", "weapi");
config.method = "post";
config.cookie = true
delete config.query
config.headers={ "content-type": "application/x-www-form-urlencoded",}
var encRes = unsafeWindow.asrsea(
JSON.stringify(data),
"010001",
"00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7",
"0CoJUm6Qyw8W8jud");
config.data = `params=${ encodeURIComponent(encRes.encText) }&encSecKey=${ encodeURIComponent(encRes.encSecKey) }`
config.url=url+`?csrf_token=${data.csrf_token}`
//console.log(config)
GM_xmlhttpRequest(config)
}
function showConfirmBox(msg){
Swal.fire({
title: '提示',
text: msg,
confirmButtonText: '确定',
})
}
function showTips(tip,type){
//type:1 √ 2:!
unsafeWindow.g_showTipCard({
tip: tip,
type: type
})
}
//歌曲页
if (location.href.match('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;
let songArtist=document.head.querySelector("[property~='og:music:artist'][content]").content;//split by /
let songAlbum=document.head.querySelector("[property~='og:music:album'][content]").content;
//songdownload
let songDownloadDiv = document.createElement('div');
songDownloadDiv.className="out s-fc3"
let songDownloadP = document.createElement('p');
songDownloadP.innerHTML = '歌曲下载:';
songDownloadDiv.style.display="none"
songDownloadDiv.appendChild(songDownloadP)
cvrwrap.appendChild(songDownloadDiv)
//lyricDownload
let lrcDownloadDiv = document.createElement('div');
lrcDownloadDiv.className="out s-fc3"
let lrcDownloadP = document.createElement('p');
lrcDownloadP.innerHTML = '歌词下载:';
lrcDownloadDiv.style.display="none"
lrcDownloadDiv.appendChild(lrcDownloadP)
cvrwrap.appendChild(lrcDownloadDiv)
let lyricObj={}
//sheetDownload
let sheetDownloadDiv = document.createElement('div');
sheetDownloadDiv.className="out s-fc3"
let sheetDownloadP = document.createElement('p');
sheetDownloadP.innerHTML = '乐谱下载:';
sheetDownloadDiv.style.display="none"
sheetDownloadDiv.appendChild(sheetDownloadP)
cvrwrap.appendChild(sheetDownloadDiv)
//wikiMemory
let wikiMemoryDiv = document.createElement('div');
wikiMemoryDiv.className="out s-fc3"
let wikiMemoryP = document.createElement('p');
wikiMemoryP.innerHTML = '回忆坐标:';
wikiMemoryDiv.style.display="none"
wikiMemoryDiv.appendChild(wikiMemoryP)
cvrwrap.appendChild(wikiMemoryDiv)
//songDownload
class SongFetch{
constructor(songId,title,artists,album) {
this.songId=songId;
this.title=title;
this.artists=artists
this.album=album
};
ncmLevelDesc(level) {
switch (level) {
case 'standard':
return '标准'
case 'higher':
return '较高'
case 'exhigh':
return '极高'
case 'lossless':
return '无损'
case 'hires':
return 'Hi-Res'
case 'jyeffect':
return '鲸云臻音'
case 'jymaster':
return '鲸云母带'
default:
return level
}
};
getNCMSource(){
weapiRequest("/api/v3/song/detail", {
type: "json",
data: {
c: JSON.stringify([{'id':songId}]),
},
onload: (responses)=> {
let songdetail=JSON.parse(responses.response);
//console.log(songdetail)
if (songdetail.privileges[0].cs){
songDownloadP.innerHTML='歌曲下载(云盘版本):'
}
if (songdetail.privileges[0].plLevel!="none"){
weapiRequest("/api/song/enhance/player/url/v1", {
type: "json",
data: {
ids: JSON.stringify([songId]),
level: songdetail.privileges[0].plLevel,
encodeType: 'mp3'
},
onload: (responses)=> {
let content=JSON.parse(responses.response);
if(content.data[0].url!=null){
//console.log(content)
let config={
filename:songTitle+'.'+content.data[0].type.toLowerCase(),
url:content.data[0].url,
size:content.data[0].size,
desc:this.ncmLevelDesc(songdetail.privileges[0].plLevel)
}
if (songdetail.privileges[0].dlLevel!="none" && songdetail.privileges[0].plLevel!=songdetail.privileges[0].dlLevel){
config.desc=`试听版本(${config.desc})`
}
this.createButton(config)
}
}
})
}
//example songid:1914447186
if (songdetail.privileges[0].dlLevel!="none" && songdetail.privileges[0].plLevel!=songdetail.privileges[0].dlLevel && unsafeWindow.GUser.userType==0){
weapiRequest("/api/song/enhance/download/url/v1", {
type: "json",
data: {
id: songId,
level: songdetail.privileges[0].dlLevel,
encodeType: 'mp3'
},
onload: (responses)=> {
let content=JSON.parse(responses.response)
if(content.data.url!=null){
//console.log(content)
let config={
filename:songTitle+'.'+content.data.type.toLowerCase(),
url:content.data.url,
size:content.data.size,
desc:`下载版本(${this.ncmLevelDesc(songdetail.privileges[0].dlLevel)})`
}
this.createButton(config)
}
}
})
}
}
})
};
createButton(config){
let btn = document.createElement('a');
btn.text = config.desc;
btn.className="des s-fc7"
btn.style.margin='2px';
btn.addEventListener('click', () => {
dwonloadSong(config.url,config.filename,btn)
})
songDownloadP.appendChild(btn)
songDownloadDiv.style.display="inline"
};
}
let songFetch=new SongFetch(songId,songTitle,songArtist,songAlbum)
//wyy可播放
if(!document.querySelector(".u-btni-play-dis")){
songFetch.getNCMSource()
}
//lyric
weapiRequest("/api/song/lyric", {
type: "json",
data: {
id: songId,
tv: -1,
lv: -1,
rv: -1,
kv: -1,
},
onload: function(responses) {
let content=JSON.parse(responses.response)
lyricObj=content
let lrc = document.createElement('a');
lrc.text = '下载';
lrc.className="des s-fc7"
lrc.style.margin='2px';
lrc.addEventListener('click',() =>{
downloadLyric('lrc',songTitle)
})
lrcDownloadP.appendChild(lrc)
lrcDownloadDiv.style.display="inline"
},
});
//sheet
weapiRequest("/api/music/sheet/list/v1", {
type: "json",
data: {
id: songId,
abTest: 'b',
},
onload: (responses)=> {
//console.log(content)
let content=JSON.parse(responses.response)
if (content.data.errorCode==null){
content.data.musicSheetSimpleInfoVOS.forEach(item=>{
let texts=[item.name,item.playVersion,item.musicKey+"调"]
if (item.difficulty.length>0){
texts.push("难度"+item.difficulty)
}
if (item.chordName.length>0){
texts.push(item.chordName+"和弦")
}
if (item.bpm>0){
texts.push(item.bpm+"bpm")
}
texts.push(item.totalPageSize+"页")
let btn = document.createElement('a');
btn.text = texts.join("-");
btn.className="des s-fc7"
btn.style.margin='5px';
btn.addEventListener('click', () => {
dwonloadSheet(item.id,`${songTitle}-${item.name}-${item.playVersion}`)
})
sheetDownloadP.appendChild(btn)
sheetDownloadDiv.style.display="inline"
})
}
}
})
//wiki
weapiRequest("/api/song/play/about/block/page", {
type: "json",
data: {
songId: songId,
},
onload: function(responses) {
//console.log(content)
let content=JSON.parse(responses.response)
if(content.data.blocks[0].creatives.length>0){
content.data.blocks.forEach(block=>{
if(block.code=='SONG_PLAY_ABOUT_MUSIC_MEMORY' && block.creatives.length>0){
let info=block.creatives[0].resources
let firstTimeP = document.createElement('p');
firstTimeP.innerHTML = `第一次听:${info[0].resourceExt.musicFirstListenDto.date}`;
firstTimeP.className="des s-fc3"
firstTimeP.style.margin='5px';
wikiMemoryP.appendChild(firstTimeP)
let recordP = document.createElement('p');
recordP.innerHTML = `累计播放:${info[1].resourceExt.musicTotalPlayDto.playCount}次 ${info[1].resourceExt.musicTotalPlayDto.duration}分钟 ${info[1].resourceExt.musicTotalPlayDto.text}`;
recordP.className="des s-fc3"
recordP.style.margin='5px';
wikiMemoryP.appendChild(recordP)
wikiMemoryDiv.style.display="inline"
}
})
}
},
});
function downloadLyric(type,songTitle){
let content=lyricObj.lrc.lyric
if (type=='lrc-tlyric'){
content=combineLyric(lyricObj.lrc.lyric,lyricObj.tlyric.lyric)
}
else if (type=='lrc-romalrc'){
content=combineLyric(lyricObj.lrc.lyric,lyricObj.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 dwonloadSong(url,fileName,dlbtn) {
let btntext=dlbtn.text
GM_download({
url: url,
name: fileName,
onprogress:function(e){
dlbtn.text=btntext+` 正在下载(${Math.round(e.loaded/e.totalSize*10000)/100}%)`
},
onload: function () {
dlbtn.text=btntext
},
onerror :function(){
dlbtn.text=btntext+' 下载失败'
}
});
}
function dwonloadSheet(sheetId,desc) {
//console.log(sheetId,desc)
weapiRequest("/api//music/sheet/preview/info", {
type: "json",
data: {
id: sheetId,
},
onload: (responses)=> {
//console.log(content)
let content=JSON.parse(responses.response)
content.data.forEach(sheetPage=>{
let fileName=`${desc}-${sheetPage.pageNumber}.jpg`
GM_download({
url: sheetPage.url,
name: fileName,
});
})
}
})
}
}
//个人主页
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',ShowCloudUploadPopUp)
var toplist=[]
var selectOptions={"无版权歌手":{},"华语男歌手":{},"华语女歌手":{},"华语组合":{},"欧美男歌手":{},"欧美女歌手":{},"欧美组合":{},"日本男歌手":{},"日本女歌手":{},"日本组合":{},}
var optionMap={0:"无版权歌手",1:"华语男歌手",2:"华语女歌手",3:"华语组合",4:"欧美男歌手",5:"欧美女歌手",6:"欧美组合",7:"日本男歌手",8:"日本女歌手",9:"日本组合"}
var artistmap={}
//https://raw.githubusercontent.com/Cinvin/cdn/main/artist/top.json
//https://fastly.jsdelivr.net/gh/Cinvin/cdn/artist/top.json
fetch('https://fastly.jsdelivr.net/gh/Cinvin/cdn@latest/artist/top.json')
.then(r => r.json())
.then(r=>{
toplist=r;
toplist.forEach(artist=>{
let desc = `${artist.name}(${artist.count}首/${artist.sizeDesc})`;
let id = artist.id
selectOptions[optionMap[artist.categroy]][artist.id]=`${artist.name}(${artist.count}首/${artist.sizeDesc})`
artistmap[artist.id]=artist
})
//console.log(selectOptions)
editArea.insertBefore(btn,editArea.lastChild)
})
function ShowCloudUploadPopUp(){
Swal.fire({
title: '云盘上传',
input: 'select',
inputOptions: selectOptions,
inputPlaceholder: '选择歌手',
html: `
`,
confirmButtonText: '上传',
showCloseButton: true,
footer:'
',
focusConfirm: false,
preConfirm: (artist) => {
return [
artist,
document.getElementById('checkbox1').checked,
document.getElementById('checkbox2').checked,
document.getElementById('checkbox3').checked,
]
}
}).then(result=>{
//console.log(result)
if(result.isConfirmed){
startUpload(result.value)
}
})
}
function startUpload(config){
var artistobj=artistmap[config[0]]
var copyright=config[1]
var vip=config[2]
var lossless=config[3]
showTips(`正在获取${artistobj.name}资源配置...`,1)
//https://raw.githubusercontent.com/Cinvin/cdn/main/artist/${artistid}.json
//https://cdn.jsdelivr.net/gh/Cinvin/cdn/artist/${artistid}.json
fetch(`https://fastly.jsdelivr.net/gh/Cinvin/cdn@latest/artist/${artistobj.id}.json`)
.then(r => r.json())
.then(r=>{
let songList=r.data
//console.log(songList)
let ids=songList.map(item=>{ return {'id':item.id} })
//获取需上传的song
showTips(`正在获取${artistobj.name}需要上传的歌曲...`,1)
weapiRequest("/api/v3/song/detail", {
type: "json",
method: "post",
sync: true,
data: {c: JSON.stringify(ids)},
onload: function(responses) {
//console.log(content)
let content=JSON.parse(responses.response)
let songs=Array()
let len=content.songs.length
for(let i =0;i{return item.id==content.songs[i].id})
let item={
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 (config.name){
item.id=0
item.name=config.name
item.album=config.al
item.artists=config.ar
item.filename=config.name+'.'+config.ext
}
let needupload=false
//1.无版权
if(copyright&&content.privileges[i].st==-200){
needupload=true
}
//2.VIP
if(vip&&(content.songs[i].fee==1 || content.songs[i].fee==4)){
needupload=true
}
//3.无损
if(lossless&&config.ext=='flac'){
needupload=true
}
if(needupload||config.name){
songs.push(item)
}
}
}
if ( songs.length == 0 ){
showConfirmBox(artistobj.name+' 没有需要上传的文件')
return
}
let md5UploadObj=new Md5Upload(songs,artistobj.name,onMD5Finnish)
md5UploadObj.start()
}
})
})
.catch('获取md5配置失败')
}
function onMD5Finnish(md5UploadObj){
let text=`${md5UploadObj.name}成功上传${md5UploadObj.sucessCount}首歌曲\n`
if(md5UploadObj.failCount>0){
text+='以下歌曲上传失败:'
md5UploadObj.failList.forEach(idx=>{text+=`${md5UploadObj.songs[idx].name} `})
}
showConfirmBox(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){
//this.destructor()
this.finnishCallback(this)
}
}
constructor(songs,name,finnishCallback) {
this.songs=songs;
this.count=songs.length
this.name=name
this.failCount=0
this.failList=Array()
this.sucessCount=0
this.existCount=0
this.finnishCallback=finnishCallback
};
start(){
this.currentIndex=0
this.uploadSong()
}
uploadSong(){
if(this.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: (responses1)=>{
let res1=JSON.parse(responses1.response)
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",
data: {
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: (responses2)=>{
let res2=JSON.parse(responses2.response)
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: (responses3)=>{
let res3=JSON.parse(responses3.response)
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", {
method: "POST",
type: "json",
data: {
songid: res3.songId,
},
onload: (responses4)=>{
let res4=JSON.parse(responses4.response)
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 && song.id>0){
weapiRequest("/api/cloud/user/song/match", {
method: "POST",
type: "json",
sync:true,
data: {
songId: res4.privateCloud.songId,
adjustSongId: song.id,
},
onload: (responses5)=>{
let res5=JSON.parse(responses5.response)
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(`(${this.currentIndex+1}/${this.count}) ${song.name} - ${song.artists} - ${song.album} 上传失败`,2)
this.currentIndex+=1
this.uploadSong()
}
onUploadSucess(){
this.sucessCount+=1
let song=this.songs[this.currentIndex]
showTips(`(${this.currentIndex+1}/${this.count}) ${song.name} - ${song.artists} - ${song.album} 上传成功`,1)
this.currentIndex+=1
this.uploadSong()
}
}
}
}
})();