feat(音乐链接): 统一使用 TuneHub 格式的音乐页面链接

将网易云和QQ音乐的原始链接替换为 TuneHub 格式的统一链接
在同步任务中优先获取歌曲元数据,避免显示原始URL
增加日志输出以帮助调试链接解析过程
This commit is contained in:
史悦
2026-01-08 13:33:34 +08:00
parent e1ac2ba55f
commit b5778f8a03
5 changed files with 47 additions and 11 deletions

View File

@@ -30,7 +30,7 @@ function normalizeTunehubPlaylist(playlistId, detail) {
duration: 0,
album: item.album || '',
cover,
pageUrl: `https://music.163.com/song?id=${item.id}`,
pageUrl: `https://music-dl.sayqz.com/?source=netease&id=${item.id}`,
playUrl: item.url || '',
isBlocked,
isCloud: false,

View File

@@ -7,11 +7,12 @@ function buildPageUrl(source, songId) {
if (!source || !songId) {
return '';
}
// 使用 TuneHub 格式的页面 URL
if (source === 'netease') {
return `https://music.163.com/song?id=${songId}`;
return `https://music-dl.sayqz.com/?source=${source}&id=${songId}`;
}
if (source === 'qq') {
return `https://y.qq.com/n/ryqq/songDetail/${songId}`;
return `https://music-dl.sayqz.com/?source=${source}&id=${songId}`;
}
return '';
}

View File

@@ -64,15 +64,31 @@ async function createJob(req, res) {
let meta = {};
const songId = request.urlJob && request.urlJob.meta.songId ? request.urlJob.meta.songId : "";
// 先从 TuneHub 获取歌曲信息,避免显示原始 URL
if (request.urlJob.meta && (request.urlJob.meta.songName !== "" && request.urlJob.meta.artist !== "")) {
meta = {
songName: request.urlJob.meta.songName,
artist: request.urlJob.meta.artist,
album : request.urlJob.meta.album ? request.urlJob.meta.album : "",
};
} else {
// 没有前端元数据,先获取歌曲信息
logger.info(`[sync_jobs] No meta provided, fetching song info from URL: ${url}`);
const songInfo = await require('../service/media_fetcher').getMetaWithUrl(url);
if (songInfo && songInfo.songName) {
meta = {
songName: songInfo.songName,
artist: songInfo.artist || "",
album: songInfo.album || "",
};
logger.info(`[sync_jobs] Fetched song info: ${meta.songName} - ${meta.artist}`);
} else {
logger.warn(`[sync_jobs] Failed to fetch song info from URL, will use URL as fallback`);
meta.songName = url; // 备用方案
}
}
if (songId) {
const songFromWyCloud = await findTheBestMatchFromWyCloud(req.account.uid, {
songName: meta.songName,
@@ -90,7 +106,7 @@ async function createJob(req, res) {
}
meta.songFromWyCloud = songFromWyCloud;
}
// create job
const args = `${jobType}: {"url":${url}}`;
if (await JobManager.findActiveJobByArgs(uid, args)) {
@@ -108,7 +124,7 @@ async function createJob(req, res) {
tip: `等待${operation}`,
createdAt: Date.now()
});
// async job
syncSingleSongWithUrl(req.account.uid, url, meta, jobId, jobType).then(async ret => {
await JobManager.updateJob(uid, jobId, {

View File

@@ -36,25 +36,36 @@ function parseTunehubParams(url) {
function parsePageUrlParams(url) {
if (!url) {
logger.error(`[parsePageUrlParams] URL is empty or null`);
return null;
}
logger.info(`[parsePageUrlParams] Parsing URL: ${url}`);
// 支持网易云音乐PC版和手机版
if (url.indexOf('music.163.com') >= 0 || url.indexOf('m.music.163.com') >= 0) {
logger.info(`[parsePageUrlParams] Detected Netease domain in URL`);
const match = url.match(/id=(\d+)/);
if (match && match[1]) {
logger.info(`[parsePageUrlParams] Parsed Netease URL: ${url} -> netease:${match[1]}`);
return { source: 'netease', id: match[1] };
} else {
logger.error(`[parsePageUrlParams] Failed to extract song ID from Netease URL: ${url}`);
}
}
// 支持QQ音乐
if (url.indexOf('y.qq.com') >= 0) {
logger.info(`[parsePageUrlParams] Detected QQ domain in URL`);
const match = url.match(/songDetail\/([A-Za-z0-9]+)/);
if (match && match[1]) {
logger.info(`[parsePageUrlParams] Parsed QQ URL: ${url} -> qq:${match[1]}`);
return { source: 'qq', id: match[1] };
} else {
logger.error(`[parsePageUrlParams] Failed to extract song ID from QQ URL: ${url}`);
}
}
logger.info(`[parsePageUrlParams] URL not recognized: ${url}`);
logger.error(`[parsePageUrlParams] URL not recognized: ${url}`);
return null;
}
@@ -178,7 +189,14 @@ async function fetchWithUrl(url, {
async function getMetaWithUrl(url) {
logger.info(`getMetaWithUrl from ${url}`);
const params = parseTunehubParams(url) || parsePageUrlParams(url);
// DEBUG: 添加详细日志
logger.info(`[getMetaWithUrl] DEBUG: URL = ${url}`);
const tunehubParams = parseTunehubParams(url);
logger.info(`[getMetaWithUrl] DEBUG: tunehubParams = ${JSON.stringify(tunehubParams)}`);
const pageParams = parsePageUrlParams(url);
logger.info(`[getMetaWithUrl] DEBUG: pageParams = ${JSON.stringify(pageParams)}`);
const params = tunehubParams || pageParams;
if (params) {
logger.info(`[getMetaWithUrl] Trying TuneHub first for ${params.source}:${params.id}`);
const tunehubInfo = await getSongInfo(params.source, params.id);
@@ -188,7 +206,8 @@ async function getMetaWithUrl(url) {
}
logger.warn(`[getMetaWithUrl] TuneHub failed for ${params.source}:${params.id}, will try media-get as fallback`);
} else {
logger.info(`[getMetaWithUrl] URL not recognized as TuneHub-supported format, trying media-get`);
logger.error(`[getMetaWithUrl] ERROR: URL not recognized as TuneHub-supported format: ${url}`);
return false; // 直接返回失败,不再调用 media-get
}
// Fallback to media-get only if TuneHub fails or URL format not supported

View File

@@ -239,7 +239,7 @@ async function getSongsFromPlaylist(uid, source, playlistId) {
duration: songInfo.dt / 1000,
album: songInfo.al.name,
cover: songInfo.al.picUrl,
pageUrl: `https://music.163.com/song?id=${songInfo.id}`,
pageUrl: `https://music-dl.sayqz.com/?source=netease&id=${songInfo.id}`,
playUrl: !isBlocked && !isCloud ? `http://music.163.com/song/media/outer/url?id=${songInfo.id}.mp3` : '', // 不再建议使用这个 url建议每次都 Call API 获取
isBlocked,
isCloud,