refactor(音乐服务): 重构网易云音乐匹配和歌曲同步逻辑

- 将网易云音乐API调用迁移至tunehub服务
- 新增歌曲信息解析和URL处理工具函数
- 改进歌曲匹配逻辑,支持更多来源的URL解析
- 优化下载流程,增加tunehub下载支持
This commit is contained in:
史悦
2026-01-07 17:20:28 +08:00
parent 6fa4f1a72e
commit 6baa2c4868
2 changed files with 113 additions and 17 deletions

View File

@@ -1,14 +1,27 @@
const { searchSong, getSongInfo } = require('../music_platform/wycloud');
const { getSongInfo, searchSongs } = require('../music_platform/tunehub');
const logger = require('consola');
function splitArtists(artist) {
if (!artist) {
return [];
}
return artist
.split(/[、/]/)
.map(item => item.trim())
.filter(Boolean);
}
module.exports = async function findTheBestMatchFromWyCloud(uid, {songName, artist, album, musicPlatformSongId} = {}) {
if (musicPlatformSongId) {
const songInfo = await getSongInfo(uid, musicPlatformSongId);
const songInfo = await getSongInfo('netease', musicPlatformSongId);
if (songInfo) {
return songInfo;
return {
songId: musicPlatformSongId,
songName: songInfo.name || songName,
artists: splitArtists(songInfo.artist),
album: songInfo.album || album,
};
}
if (songName && artist) {
return {
songId: musicPlatformSongId,
@@ -24,12 +37,24 @@ module.exports = async function findTheBestMatchFromWyCloud(uid, {songName, arti
if (songName === "" || artist === "") {
return null;
}
const searchLists = await searchSong(uid, songName, artist);
logger.info('searchLists', searchLists);
if (searchLists === false) {
const searchData = await searchSongs(`${songName} ${artist}`, {
source: 'netease',
limit: 20,
aggregate: false,
});
if (searchData === false || !searchData.results) {
logger.warn(`search song failed, no matter, go on`);
return null;
}
const searchLists = searchData.results.map(item => {
return {
songId: item.id,
songName: item.name,
artists: splitArtists(item.artist),
album: item.album,
};
});
logger.info('searchLists', searchLists);
let matchSongAndArtist = null;
for (const searchItem of searchLists) {

View File

@@ -1,4 +1,4 @@
const { fetchWithUrl, getMetaWithUrl } = require('../media_fetcher');
const { fetchWithUrl, getMetaWithUrl, downloadViaSourceUrl } = require('../media_fetcher');
const logger = require('consola');
const sleep = require('../../utils/sleep');
const findTheBestMatchFromWyCloud = require('../search_songs/find_the_best_match_from_wycloud');
@@ -11,6 +11,57 @@ const libPath = require('path');
const utilFs = require('../../utils/fs');
const { downloadFromLocalTmpPath } = require('./download_to_local');
const uploadWithRetryThenMatch = require('./upload_to_wycloud_disk_with_retry_then_match');
const { getSongInfo, buildSongUrl } = require('../music_platform/tunehub');
function parseTunehubParams(url) {
try {
const parsed = new URL(url);
if (!parsed.hostname.includes('music-dl.sayqz.com')) {
return null;
}
const source = parsed.searchParams.get('source');
const id = parsed.searchParams.get('id');
if (!source || !id) {
return null;
}
return { source, id };
} catch (err) {
return null;
}
}
function parsePageUrlParams(url) {
if (!url) {
return null;
}
if (url.indexOf('music.163.com') >= 0) {
const match = url.match(/id=(\d+)/);
if (match && match[1]) {
return { source: 'netease', id: match[1] };
}
}
if (url.indexOf('y.qq.com') >= 0) {
const match = url.match(/songDetail\/([A-Za-z0-9]+)/);
if (match && match[1]) {
return { source: 'qq', id: match[1] };
}
}
return null;
}
function buildSongInfoFromTunehub(source, info) {
return {
songName: info.name || '',
artist: info.artist || '',
album: info.album || '',
coverUrl: info.pic || '',
duration: 0,
fromMusicPlatform: true,
resourceForbidden: false,
source,
audios: info.url ? [{ url: info.url }] : [],
};
}
module.exports = async function syncSingleSongWithUrl(uid, url, {
songName = "",
@@ -19,11 +70,25 @@ module.exports = async function syncSingleSongWithUrl(uid, url, {
songFromWyCloud = null
} = {}, jobId = 0, jobType = JobType.SyncSongFromUrl, playlistName = "", collectRet) {
// step 1. fetch song info
const songInfo = await getMetaWithUrl(url);
logger.info(songInfo);
if (songInfo === false || songInfo.isTrial) {
logger.error(`fetch song info failed or it's a trial song. ${JSON.stringify(songInfo)}`);
return false;
let songInfo = null;
let downloadUrl = url;
let usedTunehub = false;
const tunehubParams = parseTunehubParams(url) || parsePageUrlParams(url);
if (tunehubParams) {
const tunehubInfo = await getSongInfo(tunehubParams.source, tunehubParams.id);
if (tunehubInfo) {
usedTunehub = true;
songInfo = buildSongInfoFromTunehub(tunehubParams.source, tunehubInfo);
downloadUrl = tunehubInfo.url || buildSongUrl(tunehubParams.source, tunehubParams.id);
}
}
if (!songInfo) {
songInfo = await getMetaWithUrl(url);
logger.info(songInfo);
if (songInfo === false || songInfo.isTrial) {
logger.error(`fetch song info failed or it's a trial song. ${JSON.stringify(songInfo)}`);
return false;
}
}
await updateJobIfNeed(uid, jobId, songInfo, jobType);
@@ -36,7 +101,7 @@ module.exports = async function syncSingleSongWithUrl(uid, url, {
findSongName = songName;
findArtist = artist;
findAlbum = album;
} else if (songInfo.fromMusicPlatform) {
} else if (songInfo && songInfo.fromMusicPlatform) {
findSongName = songInfo.songName;
findArtist = songInfo.artist;
findAlbum = songInfo.album;
@@ -54,7 +119,13 @@ module.exports = async function syncSingleSongWithUrl(uid, url, {
// step 3. download
// should add meta tag if not matched song on wycloud
const path = await fetchWithUrl(url, {songName: songInfo.songName, addMediaTag: songFromWyCloud ? false : true});
let path = false;
if (usedTunehub && downloadUrl) {
path = await downloadViaSourceUrl(downloadUrl);
}
if (path === false) {
path = await fetchWithUrl(url, {songName: songInfo.songName, addMediaTag: songFromWyCloud ? false : true});
}
if (path === false) {
return false;
}
@@ -80,4 +151,4 @@ async function updateJobIfNeed(uid, jobId, songInfo, jobType) {
desc: `歌曲: ${songInfo.songName}`,
tip: "任务开始",
});
}
}