// ==UserScript==
// @name 网易云:高音质试听|云盘歌曲快传(含周杰伦)|云盘匹配纠正|听歌量打卡|音乐歌词乐谱下载
// @description 选择更高音质试听(支持超清母带,默认无损)。个人主页:无需文件快速上传云盘歌曲(含周杰伦)、云盘匹配纠正、快速完成300首听歌量打卡任务、云盘导入导出。歌曲页:音乐、歌词、乐谱下载。
// @namespace https://github.com/Cinvin/myuserscripts
// @version 2.2.2
// @author cinvin
// @license MIT
// @match https://music.163.com/*
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAALiSURBVHgBpVZLThtBEH3d40WkIGWWSSDSGMw6cAP7BBlOgDkB5gSYE2BOgDlBnBNgToCzDmQmii0iZYEXsRRBPJ1X6R4+Mz3IKLVxT1f1q9/rais8IQmiCLjZNlBNA8O1iqzGpAoqNcAnjfmgjh9pFYbyA7+ODIJjAjSxmPTp6MDnSJfBV3YzBOfPABdp88zpBd7ERcWjDC7xdp9bXfyXZHtruOqVHNjITW8RCGZ3xOSHClnITwaF6KFeQ7XqGA/tGrbmBO9W4E0tIFL33W9g0gmQpWuY9A30XvEAsT4mCMM7B3MEXf6EZUN8ZvM2BVAY47bPyK6QuvNLLCcGWdcTFPVLnX8OJHrWadsHXsOsKeuvWD6lza7ThHWkzEqJw4gRvodXzK5kodn92KNNa5hz/0Uo7BBGicOMtc0b2MA4ZnZ17h34HUgWL9uakX3wKIfCaVe6yDqcNWv4NUrINIkswfKGGK5j3K1yItkp1vEahfpr3GwCwZTRJ25rRxoqNbdlUS2gNspwe02Qdh2TEymj5+6MNDzNreMnDwfNe4ezwQXexS4bksLE0geOC9qhJxkR/ASeMmlUS1ilCIBXdmWm1m5pg/0Y+mzFQVrclIhY11H+zWbFDXwfkDlHjHrAHA7svMKGzWheFcxUmpwWdwWwxhqLgds6FEAyp7OK8Rbwm/3R+3BZBjCjP6hFRRxO4G96DnVWVMi9kBpzmbND6JpII8meYwbAZqu20/WFcQqmXcZRA2Vv5e01SmKH1hesdDXMPjzCQDiPZlvuviRFvdwTbdmAYfm4PpTxKzwXQ2EJ7UbusWE/sq1VTFr5ZfT4d5khH3bBOfzMqXxMeC/a/Dn0nEt5pnXnwBl3nLFXJEsZF7IWmnIdVwQEya6Bq4E7dy9P1XtRoeO9dUzKD04uLpM7Cj5DOGGznTzyXEo3mTOnJ29AxdWvkr59Nx6Di6inTrnmxzJx3a11WT382zIjW6bTKoy/H+Iy6oHlZ+kAAAAASUVORK5CYII=
// @grant GM_xmlhttpRequest
// @grant GM_download
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant unsafeWindow
// @require https://unpkg.com/sweetalert2@11.7.12/dist/sweetalert2.all.min.js
// @require https://unpkg.com/ajax-hook@3.0.1/dist/ajaxhook.min.js
// @downloadURL none
// ==/UserScript==
(function() {
'use strict';
// 备用CDN
// https://raw.kgithub.com/Cinvin/cdn/main/artist/
// https://gcore.jsdelivr.net/gh/Cinvin/cdn@latest/artist/
const baseCDNURL='https://fastly.jsdelivr.net/gh/Cinvin/cdn@latest/artist/'
const levelOptions={jymaster:'超清母带',sky:'沉浸环绕声',jyeffect:'高清环绕声',hires:'Hi-Res',lossless:'无损',exhigh:'极高',higher:'较高',standard:'标准'}
const defaultOfDEFAULT_LEVEL='lossless'
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";
if(!config.cookie) config.cookie = 'os=android;appver=8.10.05'
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}`
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
})
}
function fileSizeDesc(fileSize) {
if (fileSize < 1024) {
return fileSize + 'B'
} else if (fileSize >= 1024 && fileSize < Math.pow(1024, 2)) {
return (fileSize / 1024)
.toFixed(1)
.toString() + 'K'
} else if (fileSize >= Math.pow(1024, 2) && fileSize < Math.pow(1024, 3)) {
return (fileSize / Math.pow(1024, 2))
.toFixed(1)
.toString() + 'M';
} else if (fileSize > Math.pow(1024, 3) && fileSize < Math.pow(1024, 4)) {
return (fileSize / Math.pow(1024, 3))
.toFixed(2)
.toString() + 'G';
} else if (fileSize > Math.pow(1024, 4)) {
return (fileSize / Math.pow(1024, 4))
.toFixed(2)
.toString() + 'T';
}
};
function duringTimeDesc(dt) {
let secondTotal = Math.round(dt / 1000)
let min = Math.floor(secondTotal / 60)
let sec = secondTotal % 60
return min.toString()
.padStart(2, '0') + ':' + sec.toString()
.padStart(2, '0')
};
function levelDesc(level) {
return levelOptions[level]||level
};
//歌曲页
if (location.href.includes('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
};
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: 'flac'
},
onload: (responses) => {
let content = JSON.parse(responses.response);
if (content.data[0].url != null) {
//console.log(content)
let config = {
filename: songArtist + '-' + songTitle + '.' + content.data[0].type.toLowerCase(),
url: content.data[0].url,
size: content.data[0].size,
desc: levelDesc(songdetail.privileges[0].plLevel)
}
if (songdetail.privileges[0].dlLevel != "none" && songdetail.privileges[0].plLevel != songdetail.privileges[0].dlLevel && songdetail.songs[0].fee==0) {
config.desc = `试听版本(${config.desc})`
}
this.createButton(config)
}
}
})
}
//example songid:1914447186
if (songdetail.privileges[0].dlLevel != "none" && songdetail.privileges[0].plLevel != songdetail.privileges[0].dlLevel && songdetail.songs[0].fee==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: songArtist + '-' + songTitle + '.' + content.data.type.toLowerCase(),
url: content.data.url,
size: content.data.size,
desc: `下载版本(${levelDesc(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
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.includes('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 btnUpload = document.createElement('a')
btnUpload.id = 'cloudBtn'
btnUpload.className = 'u-btn2 u-btn2-1'
let btnUploadDesc = document.createElement('i')
btnUploadDesc.innerHTML = '快速上传加载中(或许是fastly.jsdelivr.net无法访问...)'
btnUpload.appendChild(btnUploadDesc)
btnUpload.setAttribute("hidefocus", "true");
btnUpload.style.marginRight = '10px';
editArea.insertBefore(btnUpload, editArea.lastChild)
var toplist = []
var selectOptions = {
"热门": {},
"华语男歌手": {},
"华语女歌手": {},
"华语组合": {},
"欧美男歌手": {},
"欧美女歌手": {},
"欧美组合": {},
"日本男歌手": {},
"日本女歌手": {},
"日本组合": {},
"韩国男歌手": {},
"韩国女歌手": {},
"韩国组合": {},
}
var optionMap = {
0: "热门",
1: "华语男歌手",
2: "华语女歌手",
3: "华语组合",
4: "欧美男歌手",
5: "欧美女歌手",
6: "欧美组合",
7: "日本男歌手",
8: "日本女歌手",
9: "日本组合",
10: "韩国男歌手",
11: "韩国女歌手",
12: "韩国组合"
}
var artistmap = {}
fetch(`${baseCDNURL}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)
btnUpload.addEventListener('click', ShowCloudUploadPopUp)
btnUploadDesc.innerHTML = '快速上传'
})
function ShowCloudUploadPopUp() {
Swal.fire({
title: '快速上传',
input: 'select',
inputOptions: selectOptions,
inputPlaceholder: '选择歌手',
confirmButtonText: '下一步',
showCloseButton: true,
footer: '',
inputValidator: (value) => {
if (!value) {
return '请选择歌手'
}
},
})
.then(result => {
if (result.isConfirmed) {
fetchCDNConfig(result.value)
}
})
}
function fetchCDNConfig(artistId) {
showTips(`正在获取资源配置...`, 1)
fetch(`${baseCDNURL}${artistId}.json`)
.then(r => r.json())
.then(r => {
let uploader = new Uploader(r)
uploader.start()
})
.catch(`获取资源配置失败`)
}
class Uploader {
constructor(config, showAll = false) {
this.songs = []
this.config = config
this.filter = {
text: '',
noCopyright: true,
vip: true,
pay: true,
lossless: false,
all: showAll,
songIndexs: []
}
this.page = {
current: 1,
max: 1,
limitCount: 50
}
this.batchUpload = {
threadCount: 5,
working: false,
finnishThread: 0,
songIndexs: []
}
};
start() {
this.showPopup()
}
showPopup() {
Swal.fire({
showCloseButton: true,
showConfirmButton: false,
width: 800,
html: `
操作 | 歌曲标题 | 歌手 | 时长 | 文件信息 | 备注 |
---|