diff --git a/src/i18n/lang/en-US/player.ts b/src/i18n/lang/en-US/player.ts index 279d367..3a74211 100644 --- a/src/i18n/lang/en-US/player.ts +++ b/src/i18n/lang/en-US/player.ts @@ -39,7 +39,9 @@ export default { warning: 'Please select a music source', bilibiliNotSupported: 'Bilibili videos do not support reparsing', processing: 'Processing...', - clear: 'Clear Custom Source' + clear: 'Clear Custom Source', + customApiFailed: 'Custom API parsing failed, trying built-in sources...', + customApiError: 'Custom API request error, trying built-in sources...' }, playBar: { expand: 'Expand Lyrics', diff --git a/src/i18n/lang/ja-JP/player.ts b/src/i18n/lang/ja-JP/player.ts index 21e4777..a90efde 100644 --- a/src/i18n/lang/ja-JP/player.ts +++ b/src/i18n/lang/ja-JP/player.ts @@ -1,123 +1,125 @@ -export default { - nowPlaying: '再生中', - playlist: 'プレイリスト', - lyrics: '歌詞', - previous: '前へ', - play: '再生', - pause: '一時停止', - next: '次へ', - volumeUp: '音量を上げる', - volumeDown: '音量を下げる', - mute: 'ミュート', - unmute: 'ミュート解除', - songNum: '楽曲総数:{num}', - addCorrection: '{num}秒早める', - subtractCorrection: '{num}秒遅らせる', - playFailed: '現在の楽曲の再生に失敗しました。次の曲を再生します', - playMode: { - sequence: '順次再生', - loop: 'リピート再生', - random: 'ランダム再生' - }, - fullscreen: { - enter: 'フルスクリーン', - exit: 'フルスクリーン終了' - }, - close: '閉じる', - modeHint: { - single: 'リピート再生', - list: '自動で次の曲を再生' - }, - lrc: { - noLrc: '歌詞がありません。お楽しみください' - }, - reparse: { - title: '解析音源を選択', - desc: '音源をクリックして直接解析します。次回この楽曲を再生する際は選択した音源を使用します', - success: '再解析成功', - failed: '再解析失敗', - warning: '音源を選択してください', - bilibiliNotSupported: 'Bilibili動画は再解析をサポートしていません', - processing: '解析中...', - clear: 'カスタム音源をクリア' - }, - playBar: { - expand: '歌詞を展開', - collapse: '歌詞を折りたたみ', - like: 'いいね', - lyric: '歌詞', - noSongPlaying: '再生中の楽曲がありません', - eq: 'イコライザー', - playList: 'プレイリスト', - reparse: '再解析', - playMode: { - sequence: '順次再生', - loop: 'ループ再生', - random: 'ランダム再生' - }, - play: '再生開始', - pause: '再生一時停止', - prev: '前の曲', - next: '次の曲', - volume: '音量', - favorite: '{name}をお気に入りに追加しました', - unFavorite: '{name}をお気に入りから削除しました', - miniPlayBar: 'ミニ再生バー', - playbackSpeed: '再生速度', - advancedControls: 'その他の設定' - }, - eq: { - title: 'イコライザー', - reset: 'リセット', - on: 'オン', - off: 'オフ', - bass: '低音', - midrange: '中音', - treble: '高音', - presets: { - flat: 'フラット', - pop: 'ポップ', - rock: 'ロック', - classical: 'クラシック', - jazz: 'ジャズ', - electronic: 'エレクトロニック', - hiphop: 'ヒップホップ', - rb: 'R&B', - metal: 'メタル', - vocal: 'ボーカル', - dance: 'ダンス', - acoustic: 'アコースティック', - custom: 'カスタム' - } - }, - // タイマー機能関連 - sleepTimer: { - title: 'スリープタイマー', - cancel: 'タイマーをキャンセル', - timeMode: '時間で停止', - songsMode: '楽曲数で停止', - playlistEnd: 'プレイリスト終了後に停止', - afterPlaylist: 'プレイリスト終了後に停止', - activeUntilEnd: 'リスト終了まで再生', - minutes: '分', - hours: '時間', - songs: '曲', - set: '設定', - timerSetSuccess: '{minutes}分後に停止するよう設定しました', - songsSetSuccess: '{songs}曲再生後に停止するよう設定しました', - playlistEndSetSuccess: 'プレイリスト終了後に停止するよう設定しました', - timerCancelled: 'スリープタイマーをキャンセルしました', - timerEnded: 'スリープタイマーが作動しました', - playbackStopped: '音楽再生を停止しました', - minutesRemaining: '残り{minutes}分', - songsRemaining: '残り{count}曲' - }, - playList: { - clearAll: 'プレイリストをクリア', - alreadyEmpty: 'プレイリストは既に空です', - cleared: 'プレイリストをクリアしました', - empty: 'プレイリストが空です', - clearConfirmTitle: 'プレイリストをクリア', - clearConfirmContent: 'これによりプレイリスト内のすべての楽曲がクリアされ、現在の再生が停止されます。続行しますか?' - } +export default { + nowPlaying: '再生中', + playlist: 'プレイリスト', + lyrics: '歌詞', + previous: '前へ', + play: '再生', + pause: '一時停止', + next: '次へ', + volumeUp: '音量を上げる', + volumeDown: '音量を下げる', + mute: 'ミュート', + unmute: 'ミュート解除', + songNum: '楽曲総数:{num}', + addCorrection: '{num}秒早める', + subtractCorrection: '{num}秒遅らせる', + playFailed: '現在の楽曲の再生に失敗しました。次の曲を再生します', + playMode: { + sequence: '順次再生', + loop: 'リピート再生', + random: 'ランダム再生' + }, + fullscreen: { + enter: 'フルスクリーン', + exit: 'フルスクリーン終了' + }, + close: '閉じる', + modeHint: { + single: 'リピート再生', + list: '自動で次の曲を再生' + }, + lrc: { + noLrc: '歌詞がありません。お楽しみください' + }, + reparse: { + title: '解析音源を選択', + desc: '音源をクリックして直接解析します。次回この楽曲を再生する際は選択した音源を使用します', + success: '再解析成功', + failed: '再解析失敗', + warning: '音源を選択してください', + bilibiliNotSupported: 'Bilibili動画は再解析をサポートしていません', + processing: '解析中...', + clear: 'カスタム音源をクリア', + customApiFailed: 'カスタムAPIの解析に失敗しました。内蔵音源を試しています...', + customApiError: 'カスタムAPIのリクエストでエラーが発生しました。内蔵音源を試しています...' + }, + playBar: { + expand: '歌詞を展開', + collapse: '歌詞を折りたたみ', + like: 'いいね', + lyric: '歌詞', + noSongPlaying: '再生中の楽曲がありません', + eq: 'イコライザー', + playList: 'プレイリスト', + reparse: '再解析', + playMode: { + sequence: '順次再生', + loop: 'ループ再生', + random: 'ランダム再生' + }, + play: '再生開始', + pause: '再生一時停止', + prev: '前の曲', + next: '次の曲', + volume: '音量', + favorite: '{name}をお気に入りに追加しました', + unFavorite: '{name}をお気に入りから削除しました', + miniPlayBar: 'ミニ再生バー', + playbackSpeed: '再生速度', + advancedControls: 'その他の設定' + }, + eq: { + title: 'イコライザー', + reset: 'リセット', + on: 'オン', + off: 'オフ', + bass: '低音', + midrange: '中音', + treble: '高音', + presets: { + flat: 'フラット', + pop: 'ポップ', + rock: 'ロック', + classical: 'クラシック', + jazz: 'ジャズ', + electronic: 'エレクトロニック', + hiphop: 'ヒップホップ', + rb: 'R&B', + metal: 'メタル', + vocal: 'ボーカル', + dance: 'ダンス', + acoustic: 'アコースティック', + custom: 'カスタム' + } + }, + // タイマー機能関連 + sleepTimer: { + title: 'スリープタイマー', + cancel: 'タイマーをキャンセル', + timeMode: '時間で停止', + songsMode: '楽曲数で停止', + playlistEnd: 'プレイリスト終了後に停止', + afterPlaylist: 'プレイリスト終了後に停止', + activeUntilEnd: 'リスト終了まで再生', + minutes: '分', + hours: '時間', + songs: '曲', + set: '設定', + timerSetSuccess: '{minutes}分後に停止するよう設定しました', + songsSetSuccess: '{songs}曲再生後に停止するよう設定しました', + playlistEndSetSuccess: 'プレイリスト終了後に停止するよう設定しました', + timerCancelled: 'スリープタイマーをキャンセルしました', + timerEnded: 'スリープタイマーが作動しました', + playbackStopped: '音楽再生を停止しました', + minutesRemaining: '残り{minutes}分', + songsRemaining: '残り{count}曲' + }, + playList: { + clearAll: 'プレイリストをクリア', + alreadyEmpty: 'プレイリストは既に空です', + cleared: 'プレイリストをクリアしました', + empty: 'プレイリストが空です', + clearConfirmTitle: 'プレイリストをクリア', + clearConfirmContent: 'これによりプレイリスト内のすべての楽曲がクリアされ、現在の再生が停止されます。続行しますか?' + } }; \ No newline at end of file diff --git a/src/i18n/lang/ko-KR/player.ts b/src/i18n/lang/ko-KR/player.ts index 4d3c302..5a1c8b0 100644 --- a/src/i18n/lang/ko-KR/player.ts +++ b/src/i18n/lang/ko-KR/player.ts @@ -1,122 +1,124 @@ -export default { - nowPlaying: '현재 재생 중', - playlist: '재생 목록', - lyrics: '가사', - previous: '이전', - play: '재생', - pause: '일시정지', - next: '다음', - volumeUp: '볼륨 증가', - volumeDown: '볼륨 감소', - mute: '음소거', - unmute: '음소거 해제', - songNum: '총 곡 수: {num}', - addCorrection: '{num}초 앞당기기', - subtractCorrection: '{num}초 지연', - playFailed: '현재 곡 재생 실패, 다음 곡 재생', - playMode: { - sequence: '순차 재생', - loop: '한 곡 반복', - random: '랜덤 재생' - }, - fullscreen: { - enter: '전체화면', - exit: '전체화면 종료' - }, - close: '닫기', - modeHint: { - single: '한 곡 반복', - list: '자동으로 다음 곡 재생' - }, - lrc: { - noLrc: '가사가 없습니다. 음악을 감상해주세요' - }, - reparse: { - title: '음원 선택', - desc: '음원을 클릭하여 직접 분석하세요. 다음에 이 곡을 재생할 때 선택한 음원을 사용합니다', - success: '재분석 성공', - failed: '재분석 실패', - warning: '음원을 선택해주세요', - bilibiliNotSupported: 'B站 비디오는 재분석을 지원하지 않습니다', - processing: '분석 중...', - clear: '사용자 정의 음원 지우기' - }, - playBar: { - expand: '가사 펼치기', - collapse: '가사 접기', - like: '좋아요', - lyric: '가사', - noSongPlaying: '재생 중인 곡이 없습니다', - eq: '이퀄라이저', - playList: '재생 목록', - reparse: '재분석', - playMode: { - sequence: '순차 재생', - loop: '반복 재생', - random: '랜덤 재생' - }, - play: '재생 시작', - pause: '재생 일시정지', - prev: '이전 곡', - next: '다음 곡', - volume: '볼륨', - favorite: '{name} 즐겨찾기 추가됨', - unFavorite: '{name} 즐겨찾기 해제됨', - miniPlayBar: '미니 재생바', - playbackSpeed: '재생 속도', - advancedControls: '고급 설정' - }, - eq: { - title: '이퀄라이저', - reset: '재설정', - on: '켜기', - off: '끄기', - bass: '저음', - midrange: '중음', - treble: '고음', - presets: { - flat: '플랫', - pop: '팝', - rock: '록', - classical: '클래식', - jazz: '재즈', - electronic: '일렉트로닉', - hiphop: '힙합', - rb: 'R&B', - metal: '메탈', - vocal: '보컬', - dance: '댄스', - acoustic: '어쿠스틱', - custom: '사용자 정의' - } - }, - sleepTimer: { - title: '타이머 종료', - cancel: '타이머 취소', - timeMode: '시간으로 종료', - songsMode: '곡 수로 종료', - playlistEnd: '재생 목록 완료 후 종료', - afterPlaylist: '재생 목록 완료 후 종료', - activeUntilEnd: '목록 끝까지 재생', - minutes: '분', - hours: '시간', - songs: '곡', - set: '설정', - timerSetSuccess: '{minutes}분 후 종료로 설정됨', - songsSetSuccess: '{songs}곡 재생 후 종료로 설정됨', - playlistEndSetSuccess: '재생 목록 완료 후 종료로 설정됨', - timerCancelled: '타이머 종료 취소됨', - timerEnded: '타이머 종료 실행됨', - playbackStopped: '음악 재생이 중지됨', - minutesRemaining: '남은 시간 {minutes}분', - songsRemaining: '남은 곡 수 {count}곡' - }, - playList: { - clearAll: '재생 목록 비우기', - alreadyEmpty: '재생 목록이 이미 비어있습니다', - cleared: '재생 목록이 비워졌습니다', - empty: '재생 목록이 비어있습니다', - clearConfirmTitle: '재생 목록 비우기', - clearConfirmContent: '재생 목록의 모든 곡을 삭제하고 현재 재생을 중지합니다. 계속하시겠습니까?' - } +export default { + nowPlaying: '현재 재생 중', + playlist: '재생 목록', + lyrics: '가사', + previous: '이전', + play: '재생', + pause: '일시정지', + next: '다음', + volumeUp: '볼륨 증가', + volumeDown: '볼륨 감소', + mute: '음소거', + unmute: '음소거 해제', + songNum: '총 곡 수: {num}', + addCorrection: '{num}초 앞당기기', + subtractCorrection: '{num}초 지연', + playFailed: '현재 곡 재생 실패, 다음 곡 재생', + playMode: { + sequence: '순차 재생', + loop: '한 곡 반복', + random: '랜덤 재생' + }, + fullscreen: { + enter: '전체화면', + exit: '전체화면 종료' + }, + close: '닫기', + modeHint: { + single: '한 곡 반복', + list: '자동으로 다음 곡 재생' + }, + lrc: { + noLrc: '가사가 없습니다. 음악을 감상해주세요' + }, + reparse: { + title: '음원 선택', + desc: '음원을 클릭하여 직접 분석하세요. 다음에 이 곡을 재생할 때 선택한 음원을 사용합니다', + success: '재분석 성공', + failed: '재분석 실패', + warning: '음원을 선택해주세요', + bilibiliNotSupported: 'B站 비디오는 재분석을 지원하지 않습니다', + processing: '분석 중...', + clear: '사용자 정의 음원 지우기', + customApiFailed: '사용자 정의 API 분석 실패, 기본 음원을 시도합니다...', + customApiError: '사용자 정의 API 요청 오류, 기본 음원을 시도합니다...' + }, + playBar: { + expand: '가사 펼치기', + collapse: '가사 접기', + like: '좋아요', + lyric: '가사', + noSongPlaying: '재생 중인 곡이 없습니다', + eq: '이퀄라이저', + playList: '재생 목록', + reparse: '재분석', + playMode: { + sequence: '순차 재생', + loop: '반복 재생', + random: '랜덤 재생' + }, + play: '재생 시작', + pause: '재생 일시정지', + prev: '이전 곡', + next: '다음 곡', + volume: '볼륨', + favorite: '{name} 즐겨찾기 추가됨', + unFavorite: '{name} 즐겨찾기 해제됨', + miniPlayBar: '미니 재생바', + playbackSpeed: '재생 속도', + advancedControls: '고급 설정' + }, + eq: { + title: '이퀄라이저', + reset: '재설정', + on: '켜기', + off: '끄기', + bass: '저음', + midrange: '중음', + treble: '고음', + presets: { + flat: '플랫', + pop: '팝', + rock: '록', + classical: '클래식', + jazz: '재즈', + electronic: '일렉트로닉', + hiphop: '힙합', + rb: 'R&B', + metal: '메탈', + vocal: '보컬', + dance: '댄스', + acoustic: '어쿠스틱', + custom: '사용자 정의' + } + }, + sleepTimer: { + title: '타이머 종료', + cancel: '타이머 취소', + timeMode: '시간으로 종료', + songsMode: '곡 수로 종료', + playlistEnd: '재생 목록 완료 후 종료', + afterPlaylist: '재생 목록 완료 후 종료', + activeUntilEnd: '목록 끝까지 재생', + minutes: '분', + hours: '시간', + songs: '곡', + set: '설정', + timerSetSuccess: '{minutes}분 후 종료로 설정됨', + songsSetSuccess: '{songs}곡 재생 후 종료로 설정됨', + playlistEndSetSuccess: '재생 목록 완료 후 종료로 설정됨', + timerCancelled: '타이머 종료 취소됨', + timerEnded: '타이머 종료 실행됨', + playbackStopped: '음악 재생이 중지됨', + minutesRemaining: '남은 시간 {minutes}분', + songsRemaining: '남은 곡 수 {count}곡' + }, + playList: { + clearAll: '재생 목록 비우기', + alreadyEmpty: '재생 목록이 이미 비어있습니다', + cleared: '재생 목록이 비워졌습니다', + empty: '재생 목록이 비어있습니다', + clearConfirmTitle: '재생 목록 비우기', + clearConfirmContent: '재생 목록의 모든 곡을 삭제하고 현재 재생을 중지합니다. 계속하시겠습니까?' + } }; \ No newline at end of file diff --git a/src/i18n/lang/zh-CN/player.ts b/src/i18n/lang/zh-CN/player.ts index c7b4497..c9e567e 100644 --- a/src/i18n/lang/zh-CN/player.ts +++ b/src/i18n/lang/zh-CN/player.ts @@ -39,7 +39,9 @@ export default { warning: '请选择一个音源', bilibiliNotSupported: 'B站视频不支持重新解析', processing: '解析中...', - clear: '清除自定义音源' + clear: '清除自定义音源', + customApiFailed: '自定义API解析失败,正在尝试使用内置音源...', + customApiError: '自定义API请求出错,正在尝试使用内置音源...' }, playBar: { expand: '展开歌词', diff --git a/src/i18n/lang/zh-Hant/player.ts b/src/i18n/lang/zh-Hant/player.ts index 5e3ae78..8ca3b5e 100644 --- a/src/i18n/lang/zh-Hant/player.ts +++ b/src/i18n/lang/zh-Hant/player.ts @@ -39,7 +39,9 @@ export default { warning: '請選擇一個音源', bilibiliNotSupported: 'B站影片不支援重新解析', processing: '解析中...', - clear: '清除自訂音源' + clear: '清除自訂音源', + customApiFailed: '自定義API解析失敗,正在嘗試使用內置音源...', + customApiError: '自定義API請求出錯,正在嘗試使用內置音源...' }, playBar: { expand: '展開歌詞', diff --git a/src/main/set.json b/src/main/set.json index dfe9406..ac1785b 100644 --- a/src/main/set.json +++ b/src/main/set.json @@ -27,8 +27,7 @@ "showTopAction": false, "contentZoomFactor": 1, "autoTheme": false, - "manualTheme": "light" "manualTheme": "light", "customApiPlugin": "", - "customApiPluginName": "", + "customApiPluginName": "" } diff --git a/src/renderer/api/music.ts b/src/renderer/api/music.ts index 0f52b73..e92029e 100644 --- a/src/renderer/api/music.ts +++ b/src/renderer/api/music.ts @@ -9,9 +9,9 @@ import request from '@/utils/request'; import requestMusic from '@/utils/request_music'; import { searchAndGetBilibiliAudioUrl } from './bilibili'; +import type { ParsedMusicResult } from './gdmusic'; import { parseFromGDMusic } from './gdmusic'; import { parseFromCustomApi } from './parseFromCustomApi'; -import type { ParsedMusicResult } from './gdmusic'; const { addData, getData, deleteData } = musicDB; @@ -32,6 +32,8 @@ export const getMusicUrl = async (id: number, isDownloaded: boolean = false) => params: { id, level: settingStore.setData.musicQuality || 'higher', + encodeType: settingStore.setData.musicQuality == 'lossless' ? 'aac' : 'flac', + // level为lossless时,encodeType=flac时网易云会返回hires音质,encodeType=aac时网易云会返回lossless音质 cookie: `${localStorage.getItem('token')} os=pc;` } }); @@ -47,7 +49,8 @@ export const getMusicUrl = async (id: number, isDownloaded: boolean = false) => return await request.get('/song/url/v1', { params: { id, - level: settingStore.setData.musicQuality || 'higher' + level: settingStore.setData.musicQuality || 'higher', + encodeType: settingStore.setData.musicQuality == 'lossless' ? 'aac' : 'flac' } }); }; @@ -116,7 +119,8 @@ const getBilibiliAudio = async (data: SongResult) => { * @param data 歌曲数据 * @returns 解析结果,失败时返回null */ -const getGDMusicAudio = async (id: number, data: SongResult): Promise => { // <-- 在这里明确声明返回类型 +const getGDMusicAudio = async (id: number, data: SongResult): Promise => { + // <-- 在这里明确声明返回类型 try { const gdResult = await parseFromGDMusic(id, data, '999'); if (gdResult) { @@ -148,74 +152,120 @@ const getUnblockMusicAudio = (id: number, data: SongResult, sources: any[]) => { * @returns 解析结果 */ export const getParsingMusicUrl = async (id: number, data: SongResult) => { - if (isElectron) { - const settingStore = useSettingsStore(); + try { + if (isElectron) { + let musicSources: any[] = []; + let quality: string = 'higher'; + try { + const settingStore = useSettingsStore(); + const enableMusicUnblock = settingStore?.setData?.enableMusicUnblock; - // 如果禁用了音乐解析功能,则直接返回空结果 - if (!settingStore.setData.enableMusicUnblock) { - return Promise.resolve({ data: { code: 404, message: '音乐解析功能已禁用' } }); - } + // 如果禁用了音乐解析功能,则直接返回空结果 + if (!enableMusicUnblock) { + return Promise.resolve({ data: { code: 404, message: '音乐解析功能已禁用' } }); + } - // 1. 确定使用的音源列表(自定义或全局) - const songId = String(id); - const savedSourceStr = localStorage.getItem(`song_source_${songId}`); - let musicSources: any[] = []; + // 1. 确定使用的音源列表(自定义或全局) + const songId = String(id); + const savedSourceStr = (() => { + try { + return localStorage.getItem(`song_source_${songId}`); + } catch (e) { + console.warn('读取本地存储失败,忽略自定义音源', e); + return null; + } + })(); - try { - if (savedSourceStr) { - // 使用自定义音源 - musicSources = JSON.parse(savedSourceStr); - console.log(`使用歌曲 ${id} 自定义音源:`, musicSources); - } else { - // 使用全局音源设置 - musicSources = settingStore.setData.enabledMusicSources || []; - console.log(`使用全局音源设置:`, musicSources); + if (savedSourceStr) { + try { + musicSources = JSON.parse(savedSourceStr); + console.log(`使用歌曲 ${id} 自定义音源:`, musicSources); + } catch (e) { + console.error('解析音源设置失败,回退到默认全局设置', e); + musicSources = settingStore?.setData?.enabledMusicSources || []; + } + } else { + // 使用全局音源设置 + musicSources = settingStore?.setData?.enabledMusicSources || []; + console.log(`使用全局音源设置:`, musicSources); + } + + quality = settingStore?.setData?.musicQuality || 'higher'; + } catch (e) { + console.error('读取设置失败,使用默认配置', e); + musicSources = []; + quality = 'higher'; } - } catch (e) { - console.error('解析音源设置失败,回退到默认全局设置', e); - musicSources = settingStore.setData.enabledMusicSources || []; - } - const quality = settingStore.setData.musicQuality || 'higher'; - - // 优先级 1: 自定义 API - if (musicSources.includes('custom') && settingStore.setData.customApiPlugin) { - console.log('尝试使用 自定义API 解析...'); - const customResult = await parseFromCustomApi(id, data, quality); - if (customResult) { - return customResult; // 成功则直接返回 + // 优先级 1: 自定义 API + try { + const hasCustom = Array.isArray(musicSources) && musicSources.includes('custom'); + const customEnabled = (() => { + try { + const st = useSettingsStore(); + return Boolean(st?.setData?.customApiPlugin); + } catch { + return false; + } + })(); + if (hasCustom && customEnabled) { + console.log('尝试使用 自定义API 解析...'); + const customResult = await parseFromCustomApi(id, data, quality); + if (customResult) { + return customResult; // 成功则直接返回 + } + console.log('自定义API解析失败,继续尝试其他音源...'); + } + } catch (e) { + console.error('自定义API解析发生异常,继续尝试其他音源', e); } - console.log('自定义API解析失败,继续尝试其他音源...'); - } - // 优先级 2: Bilibili - if (musicSources.includes('bilibili')) { - console.log('尝试使用 Bilibili 解析...'); - const bilibiliResult = await getBilibiliAudio(data); - if (bilibiliResult?.data?.data?.url) { // 检查返回的 URL 是否有效 - return bilibiliResult; + // 优先级 2: Bilibili + try { + if (Array.isArray(musicSources) && musicSources.includes('bilibili')) { + console.log('尝试使用 Bilibili 解析...'); + const bilibiliResult = await getBilibiliAudio(data); + if (bilibiliResult?.data?.data?.url) { + return bilibiliResult; + } + console.log('Bilibili解析失败,继续尝试其他音源...'); + } + } catch (e) { + console.error('Bilibili解析发生异常,继续尝试其他音源', e); } - console.log('Bilibili解析失败,继续尝试其他音源...'); - } - // 优先级 3: GD 音乐台 - if (musicSources.includes('gdmusic')) { - console.log('尝试使用 GD音乐台 解析...'); - const gdResult = await getGDMusicAudio(id, data); - if (gdResult) { - return gdResult; + // 优先级 3: GD 音乐台 + try { + if (Array.isArray(musicSources) && musicSources.includes('gdmusic')) { + console.log('尝试使用 GD音乐台 解析...'); + const gdResult = await getGDMusicAudio(id, data); + if (gdResult) { + return gdResult; + } + console.log('GD音乐台解析失败,继续尝试其他音源...'); + } + } catch (e) { + console.error('GD音乐台解析发生异常,继续尝试其他音源', e); } - console.log('GD音乐台解析失败,继续尝试其他音源...'); - } - // 优先级 4: UnblockMusic (migu, kugou, pyncmd) - const unblockSources = musicSources.filter( - source => !['custom', 'bilibili', 'gdmusic'].includes(source) - ); - if (unblockSources.length > 0) { - console.log('尝试使用 UnblockMusic 解析:', unblockSources); - return getUnblockMusicAudio(id, data, unblockSources); + // 优先级 4: UnblockMusic (migu, kugou, pyncmd) + try { + const unblockSources = (Array.isArray(musicSources) ? musicSources : []).filter( + (source) => !['custom', 'bilibili', 'gdmusic'].includes(source) + ); + if (unblockSources.length > 0) { + console.log('尝试使用 UnblockMusic 解析:', unblockSources); + // 捕获内部可能的异常 + return await getUnblockMusicAudio(id, data, unblockSources); + } else { + console.warn('UnblockMusic API 不可用,跳过此解析方式'); + } + } catch (e) { + console.error('UnblockMusic 解析发生异常,继续后备方案', e); + } } + } catch (e) { + console.error('getParsingMusicUrl 执行异常,将使用后备方案:', e); } // 后备方案:使用API请求 @@ -228,6 +278,12 @@ export const likeSong = (id: number, like: boolean = true) => { return request.get('/like', { params: { id, like } }); }; +// 将每日推荐中的歌曲标记为不感兴趣,并获取一首新歌 +export const dislikeRecommendedSong = (id: number | string) => { + return request.get('/recommend/songs/dislike', { + params: { id } + }); +}; // 获取用户喜欢的音乐列表 export const getLikedList = (uid: number) => { return request.get('/likelist', { diff --git a/src/renderer/api/parseFromCustomApi.ts b/src/renderer/api/parseFromCustomApi.ts index e257739..c61d45c 100644 --- a/src/renderer/api/parseFromCustomApi.ts +++ b/src/renderer/api/parseFromCustomApi.ts @@ -1,106 +1,107 @@ import axios from 'axios'; -import {get} from 'lodash'; -import {useSettingsStore} from '@/store'; +import { get } from 'lodash'; -// 从同级目录的 gdmusic.ts 导入类型,确保兼容性 -import type {ParsedMusicResult} from './gdmusic'; +import { useSettingsStore } from '@/store'; + +import type { ParsedMusicResult } from './gdmusic'; /** * 定义自定义API JSON插件的结构 */ interface CustomApiPlugin { - name: string; - apiUrl: string; - method?: 'GET' | 'POST'; - params: Record; - qualityMapping?: Record; - responseUrlPath: string; + name: string; + apiUrl: string; + method?: 'GET' | 'POST'; + params: Record; + qualityMapping?: Record; + responseUrlPath: string; } /** * 从用户导入的自定义API JSON配置中解析音乐URL */ export const parseFromCustomApi = async ( - id: number, - _songData: any, - quality: string = 'higher', - timeout: number = 10000 + id: number, + _songData: any, + quality: string = 'higher', + timeout: number = 10000 ): Promise => { - const settingsStore = useSettingsStore(); - const pluginString = settingsStore.setData.customApiPlugin; + const settingsStore = useSettingsStore(); + const pluginString = settingsStore.setData.customApiPlugin; - if (!pluginString) { - return null; + if (!pluginString) { + return null; + } + + let plugin: CustomApiPlugin; + try { + plugin = JSON.parse(pluginString); + if (!plugin.apiUrl || !plugin.params || !plugin.responseUrlPath) { + console.error('自定义API:JSON配置文件格式不正确。'); + return null; + } + } catch (error) { + console.error('自定义API:解析JSON配置文件失败。', error); + return null; + } + + console.log(`自定义API:正在使用插件 [${plugin.name}] 进行解析...`); + + try { + // 1. 准备请求参数,替换占位符 + const finalParams: Record = {}; + for (const [key, value] of Object.entries(plugin.params)) { + if (value === '{songId}') { + finalParams[key] = String(id); + } else if (value === '{quality}') { + // 使用 qualityMapping (如果存在) 进行音质翻译,否则直接使用原quality + finalParams[key] = plugin.qualityMapping?.[quality] ?? quality; + } else { + // 固定值参数 + finalParams[key] = value; + } } - let plugin: CustomApiPlugin; - try { - plugin = JSON.parse(pluginString); - if (!plugin.apiUrl || !plugin.params || !plugin.responseUrlPath) { - console.error('自定义API:JSON配置文件格式不正确。'); - return null; - } - } catch (error) { - console.error('自定义API:解析JSON配置文件失败。', error); - return null; + // 2. 判断请求方法,默认为GET + const method = plugin.method?.toUpperCase() === 'POST' ? 'POST' : 'GET'; + let response; + + // 3. 根据方法发送不同的请求 + if (method === 'POST') { + console.log('自定义API:发送 POST 请求到:', plugin.apiUrl, '参数:', finalParams); + response = await axios.post(plugin.apiUrl, finalParams, { timeout }); + } else { + // 默认为 GET + const finalUrl = `${plugin.apiUrl}?${new URLSearchParams(finalParams).toString()}`; + console.log('自定义API:发送 GET 请求到:', finalUrl); + response = await axios.get(finalUrl, { timeout }); } - console.log(`自定义API:正在使用插件 [${plugin.name}] 进行解析...`); + // 4. 使用 lodash.get 安全地从响应数据中提取URL + const musicUrl = get(response.data, plugin.responseUrlPath); - try { - // 1. 准备请求参数,替换占位符 - const finalParams: Record = {}; - for (const [key, value] of Object.entries(plugin.params)) { - if (value === '{songId}') { - finalParams[key] = String(id); - } else if (value === '{quality}') { - // 使用 qualityMapping (如果存在) 进行音质翻译,否则直接使用原quality - finalParams[key] = plugin.qualityMapping?.[quality] ?? quality; - } else { - // 固定值参数 - finalParams[key] = value; - } + if (musicUrl && typeof musicUrl === 'string') { + console.log('自定义API:成功获取URL!'); + // 5. 组装成应用所需的标准格式并返回 + return { + data: { + data: { + url: musicUrl, + br: parseInt(quality) * 1000, + size: 0, + md5: '', + platform: plugin.name.toLowerCase().replace(/\s/g, ''), + gain: 0 + }, + params: { id, type: 'song' } } - - // 2. 判断请求方法,默认为GET - const method = plugin.method?.toUpperCase() === 'POST' ? 'POST' : 'GET'; - let response; - - // 3. 根据方法发送不同的请求 - if (method === 'POST') { - console.log('自定义API:发送 POST 请求到:', plugin.apiUrl, '参数:', finalParams); - response = await axios.post(plugin.apiUrl, finalParams, { timeout }); - } else { // 默认为 GET - const finalUrl = `${plugin.apiUrl}?${new URLSearchParams(finalParams).toString()}`; - console.log('自定义API:发送 GET 请求到:', finalUrl); - response = await axios.get(finalUrl, { timeout }); - } - - // 4. 使用 lodash.get 安全地从响应数据中提取URL - const musicUrl = get(response.data, plugin.responseUrlPath); - - if (musicUrl && typeof musicUrl === 'string') { - console.log('自定义API:成功获取URL!'); - // 5. 组装成应用所需的标准格式并返回 - return { - data: { - data: { - url: musicUrl, - br: parseInt(quality) * 1000, - size: 0, - md5: '', - platform: plugin.name.toLowerCase().replace(/\s/g, ''), - gain: 0 - }, - params: { id, type: 'song' } - } - }; - } else { - console.error('自定义API:根据路径未能从响应中找到URL:', plugin.responseUrlPath); - return null; - } - } catch (error) { - console.error(`自定义API [${plugin.name}] 执行失败:`, error); - return null; + }; + } else { + console.error('自定义API:根据路径未能从响应中找到URL:', plugin.responseUrlPath); + return null; } -}; \ No newline at end of file + } catch (error) { + console.error(`自定义API [${plugin.name}] 执行失败:`, error); + return null; + } +}; diff --git a/src/renderer/components/settings/MusicSourceSettings.vue b/src/renderer/components/settings/MusicSourceSettings.vue index c55c027..ef23a9b 100644 --- a/src/renderer/components/settings/MusicSourceSettings.vue +++ b/src/renderer/components/settings/MusicSourceSettings.vue @@ -1,12 +1,12 @@ @@ -68,8 +67,8 @@ import { useMessage } from 'naive-ui'; import { ref, watch } from 'vue'; import { useI18n } from 'vue-i18n'; -import { useSettingsStore } from '@/store'; +import { useSettingsStore } from '@/store'; import { type Platform } from '@/types/music'; // 扩展 Platform 类型以包含 'custom' @@ -120,45 +119,48 @@ const importPlugin = async () => { }; // 监听自定义插件内容的变化。如果用户清除了插件,要确保 'custom' 选项被取消勾选 -watch(() => settingsStore.setData.customApiPlugin, (newPluginContent) => { - if (!newPluginContent) { - const index = selectedSources.value.indexOf('custom'); - if (index > -1) { - selectedSources.value.splice(index, 1); +watch( + () => settingsStore.setData.customApiPlugin, + (newPluginContent) => { + if (!newPluginContent) { + const index = selectedSources.value.indexOf('custom'); + if (index > -1) { + selectedSources.value.splice(index, 1); + } } } -}); +); // 同步外部show属性变化 watch( - () => props.show, - (newVal) => { - visible.value = newVal; - } + () => props.show, + (newVal) => { + visible.value = newVal; + } ); // 同步内部visible变化 watch( - () => visible.value, - (newVal) => { - emit('update:show', newVal); - } + () => visible.value, + (newVal) => { + emit('update:show', newVal); + } ); // 同步外部sources属性变化 watch( - () => props.sources, - (newVal) => { - selectedSources.value = [...newVal]; - }, - { deep: true } + () => props.sources, + (newVal) => { + selectedSources.value = [...newVal]; + }, + { deep: true } ); const handleConfirm = () => { // 确保至少选择一个音源 const defaultPlatforms = ['migu', 'kugou', 'pyncmd', 'bilibili']; const valuesToEmit = - selectedSources.value.length > 0 ? [...new Set(selectedSources.value)] : defaultPlatforms; + selectedSources.value.length > 0 ? [...new Set(selectedSources.value)] : defaultPlatforms; emit('update:sources', valuesToEmit); visible.value = false; }; @@ -167,4 +169,4 @@ const handleCancel = () => { selectedSources.value = [...props.sources]; visible.value = false; }; - \ No newline at end of file + diff --git a/src/renderer/store/modules/player.ts b/src/renderer/store/modules/player.ts index 1b9bc46..e32165b 100644 --- a/src/renderer/store/modules/player.ts +++ b/src/renderer/store/modules/player.ts @@ -122,7 +122,9 @@ export const getSongUrl = async ( try { const songSources = JSON.parse(savedSourceStr); useCustomApiForSong = songSources.includes('custom'); - } catch (e) { /* ignore parsing error */ } + } catch (e) { + console.error('解析歌曲音源设置失败:', e); + } } // 如果全局或歌曲专属设置中启用了自定义API,则最优先尝试 @@ -140,11 +142,11 @@ export const getSongUrl = async ( } else { // 自定义API失败,给出提示,然后继续走默认流程 console.log('自定义API解析失败,将使用默认降级流程...'); - message.warning('自定义API解析失败,正在尝试使用内置音源...'); // 给用户一个提示 + message.warning(i18n.global.t('player.reparse.customApiFailed')); // 给用户一个提示 } } catch (error) { console.error('调用自定义API时发生错误:', error); - message.error('自定义API请求出错,正在尝试使用内置音源...'); + message.error(i18n.global.t('player.reparse.customApiError')); } } // 如果自定义API失败或未启用,则执行【原有】的解析流程