fix: 修改国际化问题 和 音源优先级以及音源解析错误处理

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