您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
自动读取云盘音乐列表,并自动匹配对应歌曲
// ==UserScript== // @name 网易云盘音乐批量自动匹配 // @namespace http://tampermonkey.net/ // @version 1.0.1 // @description 自动读取云盘音乐列表,并自动匹配对应歌曲 // @author hbc007 // @match https://music.163.com/* // @icon https://s1.music.126.net/style/favicon.ico // @run-at document-end // @license GPLv3.0 // @grant none // @homepageURL https://github.com/hbc007/NeteaseMatch // ==/UserScript== (function () { "use strict"; // Your code here... let logFrame; let startBtn; let failedSongs = []; let songIdx = 0; var levenshteinenator = (function () { /** * @param String a * @param String b * @return Array */ function levenshteinenator(a, b) { var cost; var m = a.length; var n = b.length; // make sure a.length >= b.length to use O(min(n,m)) space, whatever that is if (m < n) { var c = a; a = b; b = c; var o = m; m = n; n = o; } var r = []; r[0] = []; for (var c = 0; c < n + 1; ++c) { r[0][c] = c; } for (var i = 1; i < m + 1; ++i) { r[i] = []; r[i][0] = i; for (var j = 1; j < n + 1; ++j) { cost = a.charAt(i - 1) === b.charAt(j - 1) ? 0 : 1; r[i][j] = minimator(r[i - 1][j] + 1, r[i][j - 1] + 1, r[i - 1][j - 1] + cost); } } return r; } /** * Return the smallest of the three numbers passed in * @param Number x * @param Number y * @param Number z * @return Number */ function minimator(x, y, z) { if (x <= y && x <= z) return x; if (y <= x && y <= z) return y; return z; } return levenshteinenator; })(); function seachForSongId(songName, artist, album) { return new Promise((resolve, reject) => { fetch("http://music.163.com/api/search/get/web?csrf_token=hlpretag=&hlposttag=&s=" + encodeURI(songName + " " + artist + " " + album) + "&type=1&offset=0&total=true&limit=100") .then((response) => response.json()) .then(function (data) { if (data.code === 200 && data.result.songs) resolve(data.result.songs); else reject(data); }) .catch((e) => reject(e)); }); } function getYunPanList() { return new Promise((resolve, reject) => { fetch("https://music.163.com/api/v1/cloud/get?limit=1000") .then((response) => response.json()) .then(function (data) { resolve(data.data); }) .catch((e) => reject(e)); }); } function matchSong(yunpanSongId, matchSongId) { console.log("matchSong", yunpanSongId, matchSongId); return new Promise((resolve, reject) => { fetch("https://music.163.com/api/cloud/user/song/match?uid=" + GAccount.id + "&songId=" + yunpanSongId + "&adjustSongId=" + matchSongId) .then((response) => response.json()) .then(function (data) { resolve(data); }) .catch((e) => reject(e)); }); } async function main() { let yunSongs; songIdx = 0; failedSongs = []; try { yunSongs = await getYunPanList(); } catch (e) { log("获取云盘列表失败"); console.error(e); if (e.code === 405) { log("API调用次数超限制, 请稍后再试"); setTimeout(summary,500); setTimeout(hideLogFrame, 4000); return; } return; } let doNextMatch = (timeout = 0) => { setTimeout(() => { songIdx = songIdx + 1; if (songIdx >= yunSongs.length) { log("匹配完成"); setTimeout(summary,500); setTimeout(hideLogFrame, 4000); return; } doMatch(); }, timeout); }; let doMatch = async () => { let song = yunSongs[songIdx]; if (song.simpleSong && song.simpleSong.al && song.simpleSong.al.id) { log("云盘:", song.songId, song.songName, song.artist, " > 已匹配:", song.simpleSong.al.id, song.simpleSong.name, song.simpleSong.ar[0].name, song.simpleSong.al.name); doNextMatch(); return; } let candicateSongs; try { candicateSongs = await seachForSongId(song.songName, song.artist, song.album); } catch (e) { log("搜索歌曲失败:", song.songId, song.songName, song.artist, e.message); console.error(e); failedSongs.push(song); if (e.code === 405) { log("API调用次数超限制, 请稍后再试"); setTimeout(summary,500); setTimeout(hideLogFrame, 4000); return; } doNextMatch(800); return; } let dists = []; for (const candicateSong of candicateSongs) { let d1 = levenshteinenator(song.songName, candicateSong.name); let d2 = levenshteinenator(song.artist, candicateSong.artists.join(" ")); let d3 = levenshteinenator(song.album, candicateSong.album); let candicateArtist = ""; for (const cs of candicateSong.artists) candicateArtist += cs.name; let d4 = candicateArtist.includes(song.artist) ? 0 : 100; let d5 = candicateSong.name.includes(song.songName) ? 0 : 100; dists.push(d1[d1.length - 1][d1[d1.length - 1].length - 1] + d2[d2.length - 1][d2[d2.length - 1].length - 1] + d3[d3.length - 1][d3[d3.length - 1].length - 1] + d4 + d5); } let minDist = Math.min(...dists); if (candicateSongs.length == 0 || isNaN(minDist) || minDist > 100) { log("找不到对应歌曲:", song.songId, song.songName, song.artist); failedSongs.push(song); doNextMatch(800); return; } let minIndex = dists.indexOf(minDist); let selectSong = candicateSongs[minIndex]; log("找到对应歌曲:", selectSong.id, selectSong.name, selectSong.artists[0].name, selectSong.album.name); console.log(song.songId, song.songName, song.artist, " > ", selectSong.id, selectSong.name, selectSong.artists[0].name, selectSong.album.name); try { let matchResult = await matchSong(song.songId, selectSong.id); if (matchResult.code === 200) { log("匹配成功:", song.songId, song.songName, song.artist, " > ", selectSong.id, selectSong.name, selectSong.artists[0].name, selectSong.album.name); } else { log("匹配失败:", song.songId, song.songName, song.artist, matchResult.message); failedSongs.push(song); console.log(matchResult); } } catch (e) { log("匹配失败:", song.songId, song.songName, song.artist); console.error(e); failedSongs.push(song); if (e.code === 405) { log("API调用次数超限制, 请稍后再试"); setTimeout(summary,500); setTimeout(hideLogFrame, 4000); return; } } doNextMatch(2000); return; }; doMatch(); } function getTimeStamp() { let date = new Date(); return date.getHours() + ":" + date.getMinutes() + ":" + date.getSeconds(); } function log(...args) { logFrame.innerText = "[" + getTimeStamp() + "] " + args.join(" ") + "\n" + logFrame.innerText; } function showLogFrame() { logFrame.style.display = "block"; startBtn.style.display = "none"; } function hideLogFrame() { logFrame.style.display = "none"; startBtn.style.display = "block"; } function summary() { logFrame.innerText = `匹配成功: [${songIdx+1-failedSongs.length}/${songIdx+1}]\n匹配失败: [${failedSongs.length}/${songIdx+1}]\n`; for (let song of failedSongs) { logFrame.innerText += `- ${song.songId} ${song.songName} ${song.artist}\n`; } } function init() { const container = document.getElementsByClassName("m-top")[0]; const body = document.querySelector("body"); logFrame = document.createElement("div"); logFrame.style = "position:fixed;top:0;left:0;width:500px;height:200px;background-color:#000000;opacity:0.8;color:#ffffff;z-index:9999;display:none;overflow-y:scroll;"; startBtn = document.createElement("button"); startBtn.style = `position:fixed;top:0;left:0;width:100px;height:${container.clientHeight}px;background-color:#000000;opacity:0.8;color:#ffffff;z-index:9999;`; startBtn.innerText = "开始云盘匹配"; startBtn.onclick = () => { showLogFrame(); main(); }; body.appendChild(startBtn); body.appendChild(logFrame); } window.onload = setTimeout(init(), 200); })();