Skip to content

feat: Add QQ Music as a lyric source#140

Merged
roitium merged 1 commit into
devfrom
feat/qq-music-lyrics
Jan 22, 2026
Merged

feat: Add QQ Music as a lyric source#140
roitium merged 1 commit into
devfrom
feat/qq-music-lyrics

Conversation

@roitium
Copy link
Copy Markdown
Collaborator

@roitium roitium commented Jan 22, 2026

Summary by CodeRabbit

发行说明

  • 新功能

    • 新增 QQ 音乐作为歌词源选项
    • 添加歌词源自动选择功能,支持网易云音乐、QQ 音乐或自动模式
    • 手动搜索歌词现已支持多个音乐平台
  • 优化

    • 改进歌词匹配算法以支持多个数据源

✏️ Tip: You can customize this high-level summary in your review settings.

@roitium roitium added the enhancement New feature or request label Jan 22, 2026
@safedep
Copy link
Copy Markdown

safedep Bot commented Jan 22, 2026

SafeDep Report Summary

Green Malicious Packages Badge Green Vulnerable Packages Badge Green Risky License Badge

No dependency changes detected. Nothing to scan.

This report is generated by SafeDep Github App

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 22, 2026

Walkthrough

该变更为应用添加了 QQ 音乐作为歌词源支持。包括新增 QQ 音乐 API 集成、重构歌词搜索钩子、添加歌词源选择设置界面、更新歌词服务层以支持多源搜索,并扩展了类型定义。

Changes

凝聚体 / 文件 变更摘要
歌词源API集成
src/lib/api/qqmusic/api.ts, src/types/apis/qqmusic.ts
新增 QQMusicApi 类,实现搜索、获取和解析 QQ 音乐歌词的方法;添加三个 TypeScript 接口来定义 QQ 音乐 API 响应结构(搜索响应、歌曲对象、歌词响应)
歌词搜索钩子重构
src/hooks/queries/lyrics/index.ts, src/components/modals/lyrics/ManualSearchLyrics.tsx
修改 useManualSearchLyrics 钩子签名,移除 query 参数,改为内部管理搜索状态;重新设计为并行双源搜索(网易云与 QQ 音乐);更新调用处以适应新的返回值结构
歌词服务层扩展
src/lib/services/lyricService.ts
注入 qqMusicApi 依赖;扩展 getBestMatchedLyrics 方法以支持歌词源参数(auto、netease、qqmusic);更新 smartFetchLyrics 和 fetchLyrics 方法以应用选定的歌词源
设置UI与状态管理
src/app/(tabs)/settings/playback.tsx, src/hooks/stores/useAppStore.ts, src/types/core/appStore.ts
在设置界面添加歌词源选择菜单(支持网易云、QQ 音乐、自动模式);在应用状态中新增 lyricSource 字段,默认值为 netease
类型定义更新
src/types/player/lyrics.ts
扩展 LyricSearchResult 类型以支持 qqmusic 变体(remoteId 为 string),与 netease 变体(remoteId 为 number)共存
网易云API与其他调整
src/lib/api/netease/api.ts, src/utils/lyrics.ts
网易云搜索结果中明确指定 source 为字符串字面量类型;放宽 LRC 标签正则表达式以支持空值标签
文档与UI文案更新
CHANGELOG.md, src/app/(tabs)/settings/index.tsx
在 CHANGELOG 中记录新功能;更新设置页面文案提及歌词功能

Sequence Diagram(s)

sequenceDiagram
    participant User as 用户
    participant Settings as 设置界面
    participant AppStore as 应用状态存储
    participant LyricService as 歌词服务
    participant NeteaseAPI as 网易云API
    participant QQMusicAPI as QQ音乐API

    User->>Settings: 选择歌词源(QQ音乐/网易云/自动)
    Settings->>AppStore: 更新 lyricSource 设置
    AppStore->>AppStore: 保存用户选择

    User->>LyricService: 触发歌词搜索
    LyricService->>LyricService: 读取 lyricSource 设置
    
    alt source = 'auto'
        LyricService->>NeteaseAPI: 并行搜索歌词
        LyricService->>QQMusicAPI: 并行搜索歌词
        NeteaseAPI-->>LyricService: 返回结果
        QQMusicAPI-->>LyricService: 返回结果
        LyricService->>LyricService: 合并结果,返回首个成功响应
    else source = 'netease'
        LyricService->>NeteaseAPI: 搜索歌词
        NeteaseAPI-->>LyricService: 返回结果
    else source = 'qqmusic'
        LyricService->>QQMusicAPI: 搜索歌词
        QQMusicAPI-->>LyricService: 返回结果
    end
    
    LyricService-->>User: 显示歌词搜索结果
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 兔兔欢呼跳跳跳,
QQ 音乐新歌谣,
网易云也来应好,
自动匹配最妙招,
歌词源头任君挑!🎵

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR标题准确反映了主要变更:添加QQ音乐作为歌词源,与整个变更集的核心目标一致。
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/hooks/stores/useAppStore.ts (1)

63-71: 旧持久化 settings 会覆盖新字段,可能导致 lyricSource 为 undefined。
当前 merge 是浅合并,老用户升级时 settings 会整体覆盖默认值,lyricSource 可能丢失并影响后续逻辑。建议在 merge 时对 settings 进行深合并并补默认值。

🛠️ 建议修复(示例)
-		merge: (persistedState, currentState) => {
-			if (persistedState) {
-				return { ...currentState, ...(persistedState as Partial<AppState>) }
-			}
+		merge: (persistedState, currentState) => {
+			if (persistedState) {
+				const merged = {
+					...currentState,
+					...(persistedState as Partial<AppState>),
+				}
+				merged.settings = {
+					...currentState.settings,
+					...(persistedState as Partial<AppState>).settings,
+				}
+				if (!merged.settings.lyricSource) {
+					merged.settings.lyricSource = 'netease'
+				}
+				return merged
+			}
🤖 Fix all issues with AI agents
In `@src/lib/api/qqmusic/api.ts`:
- Around line 113-116: The conditional currently checks the return value of
parseLrc which never is falsy; update the check to verify the presence of
parsedTrans.lyrics instead: after calling const parsedTrans =
parseLrc(transLyrics), test if parsedTrans.lyrics exists/has length, and if not
return parsedRaw; ensure you reference parsedTrans and parsedRaw and leave
parseLrc usage unchanged.
🧹 Nitpick comments (5)
src/hooks/queries/lyrics/index.ts (1)

43-71: 建议移除调试日志语句。

第 47 行和第 62 行的 console.log 语句应该移除或转换为项目使用的 logger 工具,以保持代码库的一致性。

♻️ 建议的修改
 	const neteaseQuery = useQuery({
 		queryKey: lyricsQueryKeys.manualSearch(uniqueKey, `netease-${searchQuery}`),
 		queryFn: async () => {
 			if (!searchQuery) return []
-			console.log('Searching Netease:', searchQuery)
 			const res = await neteaseApi.search({ keywords: searchQuery, limit: 20 })
 	const qqMusicQuery = useQuery({
 		queryKey: lyricsQueryKeys.manualSearch(uniqueKey, `qq-${searchQuery}`),
 		queryFn: async () => {
 			if (!searchQuery) return []
-			console.log('Searching QQ:', searchQuery)
 			const res = await qqMusicApi.search(searchQuery, 20)
src/lib/api/qqmusic/api.ts (3)

66-72: 可能丢失多艺术家信息。

当前实现只取第一个歌手名称。对于多艺术家的歌曲,可以考虑合并所有歌手名称以提供更完整的信息。

♻️ 建议的修改
 			return list.map((song) => ({
 				source: 'qqmusic' as const,
 				duration: song.interval,
 				title: song.name,
-				artist: song.singer[0]?.name ?? 'Unknown',
+				artist: song.singer.map((s) => s.name).join(' / ') || 'Unknown',
 				remoteId: song.mid,
 			}))

158-160: 不必要的类型断言。

bestMatch 来自 songs 数组,其 remoteId 已经是 string 类型,无需类型断言。

♻️ 建议的修改
-			return this.getLyrics(bestMatch.remoteId as string).map((response) =>
+			return this.getLyrics(bestMatch.remoteId).map((response) =>
 				this.parseLyrics(response),
 			)

46-63: 为 fetch 请求添加超时配置以防止请求长时间挂起。

当前的 fetch 调用在 search()getLyrics() 方法中缺少超时限制。如果 QQ Music API 响应缓慢或无响应,请求会无限期地挂起。建议使用 AbortSignal.timeout() 为每个请求设置合理的超时时间:

fetch(url, {
  signal: AbortSignal.timeout(5000), // 5 秒超时
  // ...其他选项
})
src/lib/services/lyricService.ts (1)

96-101: 错误类型语义不准确。

当所有歌词提供者都失败时,使用 FileSystemError 在语义上不太准确。建议使用更具描述性的错误类型(如 DataParsingError 或创建新的错误类型如 LyricFetchError)来更清晰地表达错误的性质。

♻️ 建议的修改
 		(e) => {
 			// All failed
 			// e will be an AggregateError if using Promise.any
-			return new FileSystemError('All lyric providers failed', { cause: e })
+			return new DataParsingError('All lyric providers failed', { cause: e })
 		},

或者考虑在 @/lib/errors 中创建一个专门的 LyricFetchError 类型。

Comment thread src/lib/api/qqmusic/api.ts
@roitium roitium merged commit 8d75f7b into dev Jan 22, 2026
4 checks passed
@roitium roitium deleted the feat/qq-music-lyrics branch January 22, 2026 12:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant