feat(同步功能): 改进网易云音乐歌曲在Navidrome中的搜索逻辑
重构searchSongInNavidrome函数,支持多种查询组合和重试机制 - 新增对歌曲名、艺术家、专辑的多维度查询 - 添加基于ID令牌的精确匹配 - 实现最多2次的重试机制 - 优化匹配逻辑,提高搜索成功率
This commit is contained in:
@@ -443,20 +443,69 @@ function processSong(song) {
|
||||
});
|
||||
}
|
||||
|
||||
async function searchSongInNavidrome(filename) {
|
||||
async function searchSongInNavidrome(song) {
|
||||
try {
|
||||
const result = await callSubsonicAPI('search3', { query: filename });
|
||||
if (result.searchResult3?.song) {
|
||||
const songs = Array.isArray(result.searchResult3.song)
|
||||
? result.searchResult3.song
|
||||
: [result.searchResult3.song];
|
||||
const rawName = (song?.name || '').trim();
|
||||
const rawArtist = (song?.artist || '').trim();
|
||||
const rawAlbum = (song?.album || '').trim();
|
||||
const idToken = song?.id ? `netease_${song.id}` : '';
|
||||
|
||||
for (const song of songs) {
|
||||
if (song.title && song.path) {
|
||||
return song.id;
|
||||
const normalize = (str) => (str || '').toString().trim().toLowerCase();
|
||||
const safeName = sanitizeFilename(rawName);
|
||||
const safeArtist = sanitizeFilename(rawArtist);
|
||||
|
||||
const queries = [];
|
||||
if (rawName && rawArtist) queries.push(`${rawName} ${rawArtist}`);
|
||||
if (rawName) queries.push(rawName);
|
||||
if (rawArtist && rawAlbum) queries.push(`${rawArtist} ${rawAlbum}`);
|
||||
if (safeName) {
|
||||
const fallbackName = safeArtist ? `${safeArtist} - ${safeName}` : safeName;
|
||||
queries.push(idToken ? `${fallbackName} [${idToken}]` : fallbackName);
|
||||
}
|
||||
|
||||
const seen = new Set();
|
||||
const uniqueQueries = queries.filter(q => {
|
||||
const key = normalize(q);
|
||||
if (!key || seen.has(key)) return false;
|
||||
seen.add(key);
|
||||
return true;
|
||||
});
|
||||
|
||||
const MAX_ATTEMPTS = 2;
|
||||
|
||||
for (const query of uniqueQueries) {
|
||||
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
|
||||
const result = await callSubsonicAPI('search3', { query });
|
||||
if (result.searchResult3?.song) {
|
||||
const songs = Array.isArray(result.searchResult3.song)
|
||||
? result.searchResult3.song
|
||||
: [result.searchResult3.song];
|
||||
|
||||
if (idToken) {
|
||||
const byPath = songs.find(s => typeof s.path === 'string' && s.path.includes(idToken));
|
||||
if (byPath) return byPath.id;
|
||||
}
|
||||
|
||||
const byTitleArtist = songs.find(s =>
|
||||
normalize(s.title) === normalize(rawName) &&
|
||||
normalize(s.artist) === normalize(rawArtist)
|
||||
);
|
||||
if (byTitleArtist) return byTitleArtist.id;
|
||||
|
||||
if (songs.length === 1) {
|
||||
const only = songs[0];
|
||||
if (normalize(only.title) === normalize(rawName)) {
|
||||
return only.id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (attempt < MAX_ATTEMPTS) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1500));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (error) {
|
||||
console.error('Search song error:', error.message);
|
||||
@@ -591,11 +640,7 @@ async function syncPlaylist(playlist, cachedInfo = null) {
|
||||
if (playlist.songMapping && playlist.songMapping[neteaseSongId]) {
|
||||
navidromeSongId = playlist.songMapping[neteaseSongId];
|
||||
} else {
|
||||
const safeName = sanitizeFilename(song.name);
|
||||
const safeArtist = sanitizeFilename(song.artist);
|
||||
const filename = `${safeArtist} - ${safeName} [netease_${neteaseSongId}]`;
|
||||
|
||||
navidromeSongId = await searchSongInNavidrome(filename);
|
||||
navidromeSongId = await searchSongInNavidrome(song);
|
||||
|
||||
if (navidromeSongId) {
|
||||
newSongIds.push(navidromeSongId);
|
||||
|
||||
Reference in New Issue
Block a user