feat(音乐链接): 统一使用 TuneHub 格式的音乐页面链接
将网易云和QQ音乐的原始链接替换为 TuneHub 格式的统一链接 在同步任务中优先获取歌曲元数据,避免显示原始URL 增加日志输出以帮助调试链接解析过程
This commit is contained in:
@@ -30,7 +30,7 @@ function normalizeTunehubPlaylist(playlistId, detail) {
|
|||||||
duration: 0,
|
duration: 0,
|
||||||
album: item.album || '',
|
album: item.album || '',
|
||||||
cover,
|
cover,
|
||||||
pageUrl: `https://music.163.com/song?id=${item.id}`,
|
pageUrl: `https://music-dl.sayqz.com/?source=netease&id=${item.id}`,
|
||||||
playUrl: item.url || '',
|
playUrl: item.url || '',
|
||||||
isBlocked,
|
isBlocked,
|
||||||
isCloud: false,
|
isCloud: false,
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ function buildPageUrl(source, songId) {
|
|||||||
if (!source || !songId) {
|
if (!source || !songId) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
// 使用 TuneHub 格式的页面 URL
|
||||||
if (source === 'netease') {
|
if (source === 'netease') {
|
||||||
return `https://music.163.com/song?id=${songId}`;
|
return `https://music-dl.sayqz.com/?source=${source}&id=${songId}`;
|
||||||
}
|
}
|
||||||
if (source === 'qq') {
|
if (source === 'qq') {
|
||||||
return `https://y.qq.com/n/ryqq/songDetail/${songId}`;
|
return `https://music-dl.sayqz.com/?source=${source}&id=${songId}`;
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,15 +64,31 @@ async function createJob(req, res) {
|
|||||||
|
|
||||||
let meta = {};
|
let meta = {};
|
||||||
const songId = request.urlJob && request.urlJob.meta.songId ? request.urlJob.meta.songId : "";
|
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 !== "")) {
|
if (request.urlJob.meta && (request.urlJob.meta.songName !== "" && request.urlJob.meta.artist !== "")) {
|
||||||
meta = {
|
meta = {
|
||||||
songName: request.urlJob.meta.songName,
|
songName: request.urlJob.meta.songName,
|
||||||
artist: request.urlJob.meta.artist,
|
artist: request.urlJob.meta.artist,
|
||||||
album : request.urlJob.meta.album ? request.urlJob.meta.album : "",
|
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) {
|
if (songId) {
|
||||||
const songFromWyCloud = await findTheBestMatchFromWyCloud(req.account.uid, {
|
const songFromWyCloud = await findTheBestMatchFromWyCloud(req.account.uid, {
|
||||||
songName: meta.songName,
|
songName: meta.songName,
|
||||||
@@ -90,7 +106,7 @@ async function createJob(req, res) {
|
|||||||
}
|
}
|
||||||
meta.songFromWyCloud = songFromWyCloud;
|
meta.songFromWyCloud = songFromWyCloud;
|
||||||
}
|
}
|
||||||
|
|
||||||
// create job
|
// create job
|
||||||
const args = `${jobType}: {"url":${url}}`;
|
const args = `${jobType}: {"url":${url}}`;
|
||||||
if (await JobManager.findActiveJobByArgs(uid, args)) {
|
if (await JobManager.findActiveJobByArgs(uid, args)) {
|
||||||
@@ -108,7 +124,7 @@ async function createJob(req, res) {
|
|||||||
tip: `等待${operation}`,
|
tip: `等待${operation}`,
|
||||||
createdAt: Date.now()
|
createdAt: Date.now()
|
||||||
});
|
});
|
||||||
|
|
||||||
// async job
|
// async job
|
||||||
syncSingleSongWithUrl(req.account.uid, url, meta, jobId, jobType).then(async ret => {
|
syncSingleSongWithUrl(req.account.uid, url, meta, jobId, jobType).then(async ret => {
|
||||||
await JobManager.updateJob(uid, jobId, {
|
await JobManager.updateJob(uid, jobId, {
|
||||||
|
|||||||
@@ -36,25 +36,36 @@ function parseTunehubParams(url) {
|
|||||||
|
|
||||||
function parsePageUrlParams(url) {
|
function parsePageUrlParams(url) {
|
||||||
if (!url) {
|
if (!url) {
|
||||||
|
logger.error(`[parsePageUrlParams] URL is empty or null`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
logger.info(`[parsePageUrlParams] Parsing URL: ${url}`);
|
||||||
|
|
||||||
// 支持网易云音乐PC版和手机版
|
// 支持网易云音乐PC版和手机版
|
||||||
if (url.indexOf('music.163.com') >= 0 || url.indexOf('m.music.163.com') >= 0) {
|
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+)/);
|
const match = url.match(/id=(\d+)/);
|
||||||
if (match && match[1]) {
|
if (match && match[1]) {
|
||||||
logger.info(`[parsePageUrlParams] Parsed Netease URL: ${url} -> netease:${match[1]}`);
|
logger.info(`[parsePageUrlParams] Parsed Netease URL: ${url} -> netease:${match[1]}`);
|
||||||
return { source: 'netease', id: match[1] };
|
return { source: 'netease', id: match[1] };
|
||||||
|
} else {
|
||||||
|
logger.error(`[parsePageUrlParams] Failed to extract song ID from Netease URL: ${url}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 支持QQ音乐
|
// 支持QQ音乐
|
||||||
if (url.indexOf('y.qq.com') >= 0) {
|
if (url.indexOf('y.qq.com') >= 0) {
|
||||||
|
logger.info(`[parsePageUrlParams] Detected QQ domain in URL`);
|
||||||
const match = url.match(/songDetail\/([A-Za-z0-9]+)/);
|
const match = url.match(/songDetail\/([A-Za-z0-9]+)/);
|
||||||
if (match && match[1]) {
|
if (match && match[1]) {
|
||||||
logger.info(`[parsePageUrlParams] Parsed QQ URL: ${url} -> qq:${match[1]}`);
|
logger.info(`[parsePageUrlParams] Parsed QQ URL: ${url} -> qq:${match[1]}`);
|
||||||
return { source: 'qq', id: 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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,7 +189,14 @@ async function fetchWithUrl(url, {
|
|||||||
async function getMetaWithUrl(url) {
|
async function getMetaWithUrl(url) {
|
||||||
logger.info(`getMetaWithUrl from ${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) {
|
if (params) {
|
||||||
logger.info(`[getMetaWithUrl] Trying TuneHub first for ${params.source}:${params.id}`);
|
logger.info(`[getMetaWithUrl] Trying TuneHub first for ${params.source}:${params.id}`);
|
||||||
const tunehubInfo = await getSongInfo(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`);
|
logger.warn(`[getMetaWithUrl] TuneHub failed for ${params.source}:${params.id}, will try media-get as fallback`);
|
||||||
} else {
|
} 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
|
// Fallback to media-get only if TuneHub fails or URL format not supported
|
||||||
|
|||||||
@@ -239,7 +239,7 @@ async function getSongsFromPlaylist(uid, source, playlistId) {
|
|||||||
duration: songInfo.dt / 1000,
|
duration: songInfo.dt / 1000,
|
||||||
album: songInfo.al.name,
|
album: songInfo.al.name,
|
||||||
cover: songInfo.al.picUrl,
|
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 获取
|
playUrl: !isBlocked && !isCloud ? `http://music.163.com/song/media/outer/url?id=${songInfo.id}.mp3` : '', // 不再建议使用这个 url,建议每次都 Call API 获取
|
||||||
isBlocked,
|
isBlocked,
|
||||||
isCloud,
|
isCloud,
|
||||||
|
|||||||
Reference in New Issue
Block a user