Conversation
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! 此拉取请求主要解决了在解锁音源搜索过程中可能返回错误歌曲的问题,以及 SongManager 中试听信息判断不准确的隐患。此前,当官方音源不可用时,系统通过第三方平台搜索替代音源时,由于未严格校验歌名,可能导致播放不匹配的歌曲。同时,SongManager 对 freeTrialInfo 的判断逻辑未能正确处理所有非 null 值,导致部分有效链接被误判。本次更新通过增强第三方音源的搜索匹配精确度,并修正 SongManager 的判断逻辑来提升播放体验。值得注意的是,补丁显示相关的音源文件和 SongManager 文件已被移除,这可能意味着这些功能已被重构或迁移到新的实现中。 Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. Footnotes
|
There was a problem hiding this comment.
Code Review
本次 PR 解决了解锁音源可能返回错误歌曲的关键问题。通过修改 Bodian、酷我和歌曲宝音源的搜索逻辑,对搜索结果和原歌曲名进行校验,有效避免了歌曲不匹配的情况。此外,还修复了 SongManager.ts 中对试听歌曲的判断逻辑,通过使用非严格不等判断,正确处理了 null 和 undefined 的情况。这些改动提升了音乐播放功能的稳定性和正确性。
I am having trouble creating individual review comments. Click here to see my feedback.
electron/server/unblock/bodian.ts (81-82)
当前实现总是直接获取搜索结果列表中的第一项 (list[0]),这种做法并不可靠。因为第三方平台的搜索结果排序我们无法控制,可能会把一首热度更高但不正确的歌曲排在最前面。为了确保播放正确的歌曲,应该遍历整个 list 列表,找到第一个歌名与原始查询匹配的歌曲。如果找不到匹配项,最好返回 null,以避免播放错误的歌曲。
const originalName = info.split("-")[0].trim();
const song = list.find((item) => item.name.includes(originalName));
return song?.id || null;electron/server/unblock/gequbao.ts (18-23)
当前实现通过正则表达式匹配页面上的第一个音乐链接并返回其 ID。这种方式不够可靠,因为第一个结果不一定是正确的歌曲。为了提高准确性,应修改实现,改为获取页面上所有的歌曲搜索结果,提取它们的歌名和 ID,然后遍历这些结果,找到与关键词中原始歌名相匹配的一项。
electron/server/unblock/kuwo.ts (22-27)
此函数只处理了搜索结果中的第一项 (abslist[0])。虽然代码中包含了对歌名的校验,但如果正确的歌曲没有排在第一位,这个校验就失去了意义。逻辑应更新为遍历整个 abslist 列表,以查找与原始歌名匹配的歌曲。这样可以确保即使热门结果不正确,函数仍然能从列表的其余部分找到并返回正确的歌曲 ID。
const songList = result.data.content[1].musicpage.abslist;
const originalSongName = keyword.split("-")[0].trim();
const foundSong = songList.find((song: any) => song.SONGNAME?.includes(originalSongName));
if (foundSong && foundSong.MUSICRID) {
return foundSong.MUSICRID.slice("MUSIC_".length);
}
return null;src/core/player/SongManager.ts (225)
此处使用严格不等于 !== null 来检查 freeTrialInfo 会导致逻辑问题。当 VIP 用户获取到完整音频链接时,API 返回的 freeTrialInfo 可能是 undefined,此时 undefined !== null 的结果为 true,会错误地将歌曲判断为“试听”状态。改用非严格不等于 != null 可以同时判断 null 和 undefined,从而确保能正确处理具有完整播放权限的歌曲。
const isTrial = songData?.freeTrialInfo != null;
|
What are you doing (? |
d6a99fb to
c3afc44
Compare
c3afc44 to
a11d429
Compare
|
抱歉,上一次推送的文件由于通过 API 上传时 blob 处理异常,导致 diff 显示为文件删除而非修改,已在最新提交中修正。 |
|
/gemini review |
src/core/player/SongManager.ts
Outdated
| } | ||
| const artist = Array.isArray(song.artists) ? song.artists[0].name : song.artists; | ||
| const keyWord = song.name + "-" + artist; | ||
| const artistName = Array.isArray(song.artists) ? song.artists[0].name : song.artists; |
There was a problem hiding this comment.
这里的逻辑只取了第一个艺术家,对于多位艺术家的歌曲,这可能会导致解锁失败。例如,如果歌曲有 A 和 B 两位艺术家,这里只取了 A,但第三方源可能将 B 列为主要艺术家,或显示为“A & B”,导致匹配失败。建议将所有艺术家的名字连接起来,以提高匹配的准确性。
| const artistName = Array.isArray(song.artists) ? song.artists[0].name : song.artists; | |
| const artistName = Array.isArray(song.artists) ? song.artists.map((a) => a.name).join(" & ") : song.artists; |
- 新增 match.ts 提供归一化匹配(歌名去括号 + 艺术家统一分隔符)
- 前端传递结构化参数 songName/artist,不再依赖 split("-") 解析
- 三个 unblock 源遍历全部搜索结果,同时校验歌名和艺术家
- 修复 DownloadManager 也传递 songName/artist(与播放链路一致)
- Kuwo 搜索 URL 对 keyword 做 encodeURIComponent 防止 & 截断
- 多艺术家合作歌曲传递所有艺术家名(join " & ")
- 修复 freeTrialInfo !== null → != null 避免 VIP 链接被误丢弃
- 更新 JSDoc 参数说明
a11d429 to
ebea684
Compare
|
更新了 |
|
/gemini review |
There was a problem hiding this comment.
Pull request overview
该 PR 修复在官方音源不可用时(走 Unblock 第三方搜索替代)可能返回并播放错误歌曲的问题,通过引入“结构化原曲信息 + 结果遍历匹配”来提升命中准确性,并修正 VIP 场景下被误判为“试听”导致错误走 Unblock 的问题。
Changes:
- 新增服务端公共匹配模块
match.ts,对歌名/艺人做归一化并进行匹配校验 - 前端调用
unlockSongUrl时传递songName/artist,服务端构造SongMatchInfo并用于 Kuwo/Bodian/Gequbao 的结果遍历匹配 - 修正
SongManager对freeTrialInfo的判定逻辑,避免误判丢弃官方 URL
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/core/resource/DownloadManager.ts | Unblock 下载请求补充 songName/artist,并调整艺术家拼接格式用于关键词 |
| src/core/player/SongManager.ts | 修正试听判定;Unblock 请求补充 songName/artist;关键词构造更完整 |
| src/api/song.ts | 扩展 unlockSongUrl 参数并把 songName/artist 传到 /api/unblock |
| electron/server/unblock/unblock.d.ts | 新增 SongMatchInfo 类型用于服务端匹配上下文 |
| electron/server/unblock/match.ts | 新增归一化与匹配函数,供各 Unblock 源复用 |
| electron/server/unblock/index.ts | 构建匹配信息(兼容旧 keyword 解析),并传递给各源实现 |
| electron/server/unblock/kuwo.ts | 遍历搜索结果并用 isSongMatch 校验,避免取第一条导致错歌 |
| electron/server/unblock/bodian.ts | 遍历搜索结果并用 isSongMatch 校验,避免取第一条导致错歌 |
| electron/server/unblock/gequbao.ts | 解析搜索页多个结果并用 isSongMatch 校验,避免误命中 |
| const normalizedResult = normalizeName(resultName); | ||
| const normalizedOriginal = normalizeName(match.songName); | ||
| // 歌名:双向 includes(兼容一方带后缀的情况) | ||
| if ( | ||
| !normalizedResult.includes(normalizedOriginal) && | ||
| !normalizedOriginal.includes(normalizedResult) | ||
| ) { |
There was a problem hiding this comment.
isSongMatch currently treats empty/blank song names as a match because String.prototype.includes("") is always true. If resultName (or the normalized form after removing brackets) ends up empty, the name check will incorrectly pass and the first search result could be accepted. Add an explicit guard so an empty normalized result/original name returns false before doing the bidirectional includes check.
| if (resultArtist && match.artist) { | ||
| const normalizedResultArtist = normalizeArtist(resultArtist); | ||
| const normalizedOriginalArtist = normalizeArtist(match.artist); | ||
| if ( | ||
| !normalizedResultArtist.includes(normalizedOriginalArtist) && | ||
| !normalizedOriginalArtist.includes(normalizedResultArtist) | ||
| ) { | ||
| return false; |
There was a problem hiding this comment.
The artist check has the same empty-string pitfall after normalization (e.g. resultArtist is whitespace-only): includes("") will pass and the artist constraint becomes ineffective. Consider normalizing first and requiring both normalized artist strings to be non-empty before applying the bidirectional includes logic; otherwise return false when an artist string is provided but normalizes to empty.
| // 构造匹配信息(fallback 用 lastIndexOf 兼容歌名含连字符的情况) | ||
| const buildMatchInfo = (query: { [key: string]: string }) => { | ||
| let songName = query.songName || ""; | ||
| let artist = query.artist || ""; |
There was a problem hiding this comment.
SongUrlResult is only used as a TypeScript type in this file, but it’s imported as a value (import { SongUrlResult } ...). Since ./unblock is a .d.ts-only module, preserving value imports (e.g. with verbatimModuleSyntax / future TS config changes) could turn this into a runtime import that fails. Switch this to import type to make the runtime dependency impossible.
MoYingJi
left a comment
There was a problem hiding this comment.
代码看着没有大问题,但我的账号有会员没法进行测试(
There was a problem hiding this comment.
差点忘了还有个 API 文档,在 /docs/api.md
Lines 253 to 312 in 405e01d
此外,为了保持 API 的兼容性,songName 和 artist 都需要为可选参数(不填保持原有匹配逻辑,现在应该是这样的,就像 AI 所说的 includes 空字符串,不过逻辑不是显式的)
| // 艺术家:归一化分隔符后双向 includes | ||
| if (resultArtist && match.artist) { | ||
| const normalizedResultArtist = normalizeArtist(resultArtist); | ||
| const normalizedOriginalArtist = normalizeArtist(match.artist); | ||
| if ( | ||
| !normalizedResultArtist.includes(normalizedOriginalArtist) && | ||
| !normalizedOriginalArtist.includes(normalizedResultArtist) | ||
| ) { | ||
| return false; | ||
| } | ||
| } |
There was a problem hiding this comment.
这样貌似也没法解决 AI 所说的 A & B 和 B & A 的比较问题(a b 与 b a 互不包含),只能解决像 A 和 A & B 这样的
我不知道这样的检查是否过于严格,我也没有账号测试这是否影响实际其它歌曲的匹配体验
- isSongMatch 增加空字符串保护,避免 includes("") 恒为 true
- SongUrlResult 改为 import type
- 补充 songName/artist 可选参数文档
|
已修复 review 中提到的问题:
本地已测试通过,感谢 review! |
| // 空字符串保护:避免 includes("") 恒为 true | ||
| if (!normalizedResult || !normalizedOriginal) return false; |
There was a problem hiding this comment.
这样 songName 是否就不是可选参数了?(songName 为空时这里直接返回 false 使得没有任何歌曲能够匹配,预期行为是保持旧行为,即直接忽略歌曲的歌名)
问题描述
搜索并播放"山雀"(万能青年旅店)时,实际播放的音频是"杀死那个石家庄人"——一首完全不同的歌。
根因分析
当网易云官方音源不可用时(无版权/VIP 限制),系统通过 Unblock 机制使用关键词(如
山雀-万能青年旅店)在第三方平台(酷我/波点/歌曲宝)搜索替代音源。问题有三层:
split("-")解析歌名,带连字符的歌名(如Anti-Hero)会被截断,导致误匹配Hello (Live)vsHello会被误杀,合法匹配返回 404此外,
SongManager.ts中freeTrialInfo !== null的严格不等判断存在隐患:当 VIP 用户获取到完整音频链接时,freeTrialInfo为undefined(非null),被误判为"试听",导致有效的官方 URL 被丢弃而走 Unblock。修复内容
match.ts:提取公共匹配逻辑normalizeName():归一化歌名(小写 + 去除括号及其内容)isSongMatch():同时校验歌名和艺术家,双向includesunlockSongUrl新增songName和artist参数,不再依赖split("-")解析isSongMatch校验,无匹配则返回空freeTrialInfo !== null→freeTrialInfo != null测试
⚠️ Bodian 搜索结果均不匹配原曲: "山雀"的警告pnpm typecheck:node+pnpm typecheck:web)