mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-04-24 16:27:23 +08:00
feat: add custom api
This commit is contained in:
@@ -0,0 +1,62 @@
|
|||||||
|
## 🎵 自定义音源API配置
|
||||||
|
|
||||||
|
现在支持通过导入一个简单的 JSON 配置文件来对接第三方的音乐解析 API。这将提供极大的灵活性,可以接入任意第三方音源。
|
||||||
|
|
||||||
|
### 如何使用
|
||||||
|
|
||||||
|
1. 前往 **设置 -> 播放设置 -> 音源设置**。
|
||||||
|
2. 在 **自定义 API 设置** 区域,点击 **“导入 JSON 配置”** 按钮。
|
||||||
|
3. 选择你已经编写好的 `xxx.json` 配置文件。
|
||||||
|
4. 导入成功后,程序将优先使用你的自定义 API 进行解析。
|
||||||
|
|
||||||
|
### JSON 配置文件格式说明
|
||||||
|
|
||||||
|
导入的配置文件必须是一个合法的 JSON 文件,并包含以下字段:
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 是否必须 | 描述 |
|
||||||
|
| ---------------- | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| `name` | `string` | 是 | API 名称,将显示在应用的 UI 界面上。 |
|
||||||
|
| `apiUrl` | `string` | 是 | API 的基础请求地址。 |
|
||||||
|
| `method` | `string` | 否 | HTTP 请求方法。可以是 `"GET"` 或 `"POST"`。**如果省略,默认为 "GET"**。 |
|
||||||
|
| `params` | `object` | 是 | 请求时需要发送的参数。对于 `GET` 请求,它们会作为查询字符串;对于 `POST` 请求,它们会作为请求体。 |
|
||||||
|
| `qualityMapping` | `object` | 否 | **音质映射表**。用于将应用内部的音质值(如 `"lossless"`)翻译成你的 API 需要的特定值。如果省略,则直接使用应用内部值。 |
|
||||||
|
| `responseUrlPath`| `string` | 是 | **URL提取路径**。用于从 API 返回的 JSON 响应中找到最终可播放的音乐链接。支持点 `.` 和方括号 `[]` 语法来访问嵌套对象和数组。 |
|
||||||
|
|
||||||
|
#### 占位符
|
||||||
|
|
||||||
|
在 `params` 对象的值中,你可以使用以下占位符,程序在请求时会自动替换它们:
|
||||||
|
|
||||||
|
* `{songId}`: 将被替换为当前歌曲的 ID。
|
||||||
|
* `{quality}`: 将被替换为当前用户设置的音质字符串 (例如, `"higher"`, `"lossless"`)。
|
||||||
|
|
||||||
|
#### 音质值列表
|
||||||
|
|
||||||
|
应用内部使用的音质值如下,你可以在 `qualityMapping` 中使用它们作为**键**:
|
||||||
|
`standard`, `higher`, `exhigh`, `lossless`, `hires`, `jyeffect`, `sky`, `dolby`, `jymaster`
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
假设有一个 API 如下:
|
||||||
|
`https://api.example.com/music?song_id=12345&bitrate=320000`
|
||||||
|
它返回的 JSON 是:
|
||||||
|
`{ "code": 200, "data": { "play_url": "http://..." } }`
|
||||||
|
|
||||||
|
那么对应的 JSON 配置文件应该是:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"name": "Example API",
|
||||||
|
"apiUrl": "https://api.example.com/music",
|
||||||
|
"method": "GET",
|
||||||
|
"params": {
|
||||||
|
"song_id": "{songId}",
|
||||||
|
"bitrate": "{quality}"
|
||||||
|
},
|
||||||
|
"qualityMapping": {
|
||||||
|
"higher": "128000",
|
||||||
|
"exhigh": "320000",
|
||||||
|
"lossless": "999000"
|
||||||
|
},
|
||||||
|
"responseUrlPath": "data.play_url"
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -81,6 +81,19 @@ export default {
|
|||||||
showStatusBar: 'Show Status Bar',
|
showStatusBar: 'Show Status Bar',
|
||||||
showStatusBarContent:
|
showStatusBarContent:
|
||||||
'You can display the music control function in your mac status bar (effective after a restart)'
|
'You can display the music control function in your mac status bar (effective after a restart)'
|
||||||
|
'You can display the music control function in your mac status bar (effective after a restart)',
|
||||||
|
fallbackParser: 'Fallback Parser (GD Music)',
|
||||||
|
fallbackParserDesc: 'When "GD Music" is checked and regular sources fail, this service will be used.',
|
||||||
|
parserGD: 'GD Music (Built-in)',
|
||||||
|
parserCustom: 'Custom API',
|
||||||
|
|
||||||
|
customApi: {
|
||||||
|
importConfig: 'Import JSON Config',
|
||||||
|
currentSource: 'Current Source',
|
||||||
|
notImported: 'No custom source imported yet.',
|
||||||
|
importSuccess: 'Successfully imported source: {name}',
|
||||||
|
importFailed: 'Import failed: {message}',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
application: {
|
application: {
|
||||||
closeAction: 'Close Action',
|
closeAction: 'Close Action',
|
||||||
|
|||||||
+334
-321
@@ -1,322 +1,335 @@
|
|||||||
export default {
|
export default {
|
||||||
theme: 'テーマ',
|
theme: 'テーマ',
|
||||||
language: '言語',
|
language: '言語',
|
||||||
regard: 'について',
|
regard: 'について',
|
||||||
logout: 'ログアウト',
|
logout: 'ログアウト',
|
||||||
sections: {
|
sections: {
|
||||||
basic: '基本設定',
|
basic: '基本設定',
|
||||||
playback: '再生設定',
|
playback: '再生設定',
|
||||||
application: 'アプリケーション設定',
|
application: 'アプリケーション設定',
|
||||||
network: 'ネットワーク設定',
|
network: 'ネットワーク設定',
|
||||||
system: 'システム管理',
|
system: 'システム管理',
|
||||||
donation: '寄付サポート',
|
donation: '寄付サポート',
|
||||||
regard: 'について'
|
regard: 'について'
|
||||||
},
|
},
|
||||||
basic: {
|
basic: {
|
||||||
themeMode: 'テーマモード',
|
themeMode: 'テーマモード',
|
||||||
themeModeDesc: 'ライト/ダークテーマの切り替え',
|
themeModeDesc: 'ライト/ダークテーマの切り替え',
|
||||||
autoTheme: 'システムに従う',
|
autoTheme: 'システムに従う',
|
||||||
manualTheme: '手動切り替え',
|
manualTheme: '手動切り替え',
|
||||||
language: '言語設定',
|
language: '言語設定',
|
||||||
languageDesc: '表示言語を切り替え',
|
languageDesc: '表示言語を切り替え',
|
||||||
tokenManagement: 'Cookie管理',
|
tokenManagement: 'Cookie管理',
|
||||||
tokenManagementDesc: 'NetEase Cloud MusicログインCookieを管理',
|
tokenManagementDesc: 'NetEase Cloud MusicログインCookieを管理',
|
||||||
tokenStatus: '現在のCookieステータス',
|
tokenStatus: '現在のCookieステータス',
|
||||||
tokenSet: '設定済み',
|
tokenSet: '設定済み',
|
||||||
tokenNotSet: '未設定',
|
tokenNotSet: '未設定',
|
||||||
setToken: 'Cookieを設定',
|
setToken: 'Cookieを設定',
|
||||||
modifyToken: 'Cookieを変更',
|
modifyToken: 'Cookieを変更',
|
||||||
clearToken: 'Cookieをクリア',
|
clearToken: 'Cookieをクリア',
|
||||||
font: 'フォント設定',
|
font: 'フォント設定',
|
||||||
fontDesc: 'フォントを選択します。前に配置されたフォントが優先されます',
|
fontDesc: 'フォントを選択します。前に配置されたフォントが優先されます',
|
||||||
fontScope: {
|
fontScope: {
|
||||||
global: 'グローバル',
|
global: 'グローバル',
|
||||||
lyric: '歌詞のみ'
|
lyric: '歌詞のみ'
|
||||||
},
|
},
|
||||||
animation: 'アニメーション速度',
|
animation: 'アニメーション速度',
|
||||||
animationDesc: 'アニメーションを有効にするかどうか',
|
animationDesc: 'アニメーションを有効にするかどうか',
|
||||||
animationSpeed: {
|
animationSpeed: {
|
||||||
slow: '非常に遅い',
|
slow: '非常に遅い',
|
||||||
normal: '通常',
|
normal: '通常',
|
||||||
fast: '非常に速い'
|
fast: '非常に速い'
|
||||||
},
|
},
|
||||||
fontPreview: {
|
fontPreview: {
|
||||||
title: 'フォントプレビュー',
|
title: 'フォントプレビュー',
|
||||||
chinese: '中国語',
|
chinese: '中国語',
|
||||||
english: 'English',
|
english: 'English',
|
||||||
japanese: '日本語',
|
japanese: '日本語',
|
||||||
korean: '韓国語',
|
korean: '韓国語',
|
||||||
chineseText: '静夜思 床前明月光 疑是地上霜',
|
chineseText: '静夜思 床前明月光 疑是地上霜',
|
||||||
englishText: 'The quick brown fox jumps over the lazy dog',
|
englishText: 'The quick brown fox jumps over the lazy dog',
|
||||||
japaneseText: 'あいうえお かきくけこ さしすせそ',
|
japaneseText: 'あいうえお かきくけこ さしすせそ',
|
||||||
koreanText: '가나다라마 바사아자차 카타파하'
|
koreanText: '가나다라마 바사아자차 카타파하'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
playback: {
|
playback: {
|
||||||
quality: '音質設定',
|
quality: '音質設定',
|
||||||
qualityDesc: '音楽再生の音質を選択(NetEase Cloud VIP)',
|
qualityDesc: '音楽再生の音質を選択(NetEase Cloud VIP)',
|
||||||
qualityOptions: {
|
qualityOptions: {
|
||||||
standard: '標準',
|
standard: '標準',
|
||||||
higher: '高音質',
|
higher: '高音質',
|
||||||
exhigh: '超高音質',
|
exhigh: '超高音質',
|
||||||
lossless: 'ロスレス',
|
lossless: 'ロスレス',
|
||||||
hires: 'Hi-Res',
|
hires: 'Hi-Res',
|
||||||
jyeffect: 'HD サラウンド',
|
jyeffect: 'HD サラウンド',
|
||||||
sky: 'イマーシブサラウンド',
|
sky: 'イマーシブサラウンド',
|
||||||
dolby: 'Dolby Atmos',
|
dolby: 'Dolby Atmos',
|
||||||
jymaster: '超高解像度マスター'
|
jymaster: '超高解像度マスター'
|
||||||
},
|
},
|
||||||
musicSources: '音源設定',
|
musicSources: '音源設定',
|
||||||
musicSourcesDesc: '音楽解析に使用する音源プラットフォームを選択',
|
musicSourcesDesc: '音楽解析に使用する音源プラットフォームを選択',
|
||||||
musicSourcesWarning: '少なくとも1つの音源プラットフォームを選択する必要があります',
|
musicSourcesWarning: '少なくとも1つの音源プラットフォームを選択する必要があります',
|
||||||
musicUnblockEnable: '音楽解析を有効にする',
|
musicUnblockEnable: '音楽解析を有効にする',
|
||||||
musicUnblockEnableDesc: '有効にすると、再生できない音楽の解析を試みます',
|
musicUnblockEnableDesc: '有効にすると、再生できない音楽の解析を試みます',
|
||||||
configureMusicSources: '音源を設定',
|
configureMusicSources: '音源を設定',
|
||||||
selectedMusicSources: '選択された音源:',
|
selectedMusicSources: '選択された音源:',
|
||||||
noMusicSources: '音源が選択されていません',
|
noMusicSources: '音源が選択されていません',
|
||||||
gdmusicInfo: 'GD音楽台は複数のプラットフォーム音源を自動解析し、最適な結果を自動選択できます',
|
gdmusicInfo: 'GD音楽台は複数のプラットフォーム音源を自動解析し、最適な結果を自動選択できます',
|
||||||
autoPlay: '自動再生',
|
autoPlay: '自動再生',
|
||||||
autoPlayDesc: 'アプリを再起動した際に自動的に再生を継続するかどうか',
|
autoPlayDesc: 'アプリを再起動した際に自動的に再生を継続するかどうか',
|
||||||
showStatusBar: 'ステータスバーコントロール機能を表示するかどうか',
|
showStatusBar: 'ステータスバーコントロール機能を表示するかどうか',
|
||||||
showStatusBarContent: 'Macのステータスバーに音楽コントロール機能を表示できます(再起動後に有効)'
|
showStatusBarContent: 'Macのステータスバーに音楽コントロール機能を表示できます(再起動後に有効)'
|
||||||
},
|
showStatusBarContent: 'Macのステータスバーに音楽コントロール機能を表示できます(再起動後に有効)',
|
||||||
application: {
|
fallbackParser: '代替解析サービス (GD音楽台)',
|
||||||
closeAction: '閉じる動作',
|
fallbackParserDesc: '「GD音楽台」にチェックが入っていて、通常の音源で再生できない場合、このサービスが使用されます。',
|
||||||
closeActionDesc: 'ウィンドウを閉じる際の動作を選択',
|
parserGD: 'GD音楽台 (内蔵)',
|
||||||
closeOptions: {
|
parserCustom: 'カスタムAPI',
|
||||||
ask: '毎回確認',
|
|
||||||
minimize: 'トレイに最小化',
|
customApi: {
|
||||||
close: '直接終了'
|
importConfig: 'JSON設定をインポート',
|
||||||
},
|
currentSource: '現在の音源',
|
||||||
shortcut: 'ショートカット設定',
|
notImported: 'カスタム音源はまだインポートされていません。',
|
||||||
shortcutDesc: 'グローバルショートカットをカスタマイズ',
|
importSuccess: '音源のインポートに成功しました: {name}',
|
||||||
download: 'ダウンロード管理',
|
importFailed: 'インポートに失敗しました: {message}',
|
||||||
downloadDesc: 'ダウンロードリストボタンを常に表示するかどうか',
|
},
|
||||||
unlimitedDownload: '無制限ダウンロード',
|
},
|
||||||
unlimitedDownloadDesc: '有効にすると音楽を無制限でダウンロードします(ダウンロード失敗の可能性があります)。デフォルトは300曲制限',
|
application: {
|
||||||
downloadPath: 'ダウンロードディレクトリ',
|
closeAction: '閉じる動作',
|
||||||
downloadPathDesc: '音楽ファイルのダウンロード場所を選択',
|
closeActionDesc: 'ウィンドウを閉じる際の動作を選択',
|
||||||
remoteControl: 'リモートコントロール',
|
closeOptions: {
|
||||||
remoteControlDesc: 'リモートコントロール機能を設定'
|
ask: '毎回確認',
|
||||||
},
|
minimize: 'トレイに最小化',
|
||||||
network: {
|
close: '直接終了'
|
||||||
apiPort: '音楽APIポート',
|
},
|
||||||
apiPortDesc: '変更後はアプリの再起動が必要です',
|
shortcut: 'ショートカット設定',
|
||||||
proxy: 'プロキシ設定',
|
shortcutDesc: 'グローバルショートカットをカスタマイズ',
|
||||||
proxyDesc: '音楽にアクセスできない場合はプロキシを有効にできます',
|
download: 'ダウンロード管理',
|
||||||
proxyHost: 'プロキシアドレス',
|
downloadDesc: 'ダウンロードリストボタンを常に表示するかどうか',
|
||||||
proxyHostPlaceholder: 'プロキシアドレスを入力してください',
|
unlimitedDownload: '無制限ダウンロード',
|
||||||
proxyPort: 'プロキシポート',
|
unlimitedDownloadDesc: '有効にすると音楽を無制限でダウンロードします(ダウンロード失敗の可能性があります)。デフォルトは300曲制限',
|
||||||
proxyPortPlaceholder: 'プロキシポートを入力してください',
|
downloadPath: 'ダウンロードディレクトリ',
|
||||||
realIP: 'realIP設定',
|
downloadPathDesc: '音楽ファイルのダウンロード場所を選択',
|
||||||
realIPDesc: '制限により、このプロジェクトは海外での使用が制限されます。realIPパラメータを使用して国内IPを渡すことで解決できます',
|
remoteControl: 'リモートコントロール',
|
||||||
messages: {
|
remoteControlDesc: 'リモートコントロール機能を設定'
|
||||||
proxySuccess: 'プロキシ設定を保存しました。アプリ再起動後に有効になります',
|
},
|
||||||
proxyError: '入力が正しいかどうか確認してください',
|
network: {
|
||||||
realIPSuccess: '実IPアドレス設定を保存しました',
|
apiPort: '音楽APIポート',
|
||||||
realIPError: '有効なIPアドレスを入力してください'
|
apiPortDesc: '変更後はアプリの再起動が必要です',
|
||||||
}
|
proxy: 'プロキシ設定',
|
||||||
},
|
proxyDesc: '音楽にアクセスできない場合はプロキシを有効にできます',
|
||||||
system: {
|
proxyHost: 'プロキシアドレス',
|
||||||
cache: 'キャッシュ管理',
|
proxyHostPlaceholder: 'プロキシアドレスを入力してください',
|
||||||
cacheDesc: 'キャッシュをクリア',
|
proxyPort: 'プロキシポート',
|
||||||
cacheClearTitle: 'クリアするキャッシュタイプを選択してください:',
|
proxyPortPlaceholder: 'プロキシポートを入力してください',
|
||||||
cacheTypes: {
|
realIP: 'realIP設定',
|
||||||
history: {
|
realIPDesc: '制限により、このプロジェクトは海外での使用が制限されます。realIPパラメータを使用して国内IPを渡すことで解決できます',
|
||||||
label: '再生履歴',
|
messages: {
|
||||||
description: '再生した楽曲の記録をクリア'
|
proxySuccess: 'プロキシ設定を保存しました。アプリ再起動後に有効になります',
|
||||||
},
|
proxyError: '入力が正しいかどうか確認してください',
|
||||||
favorite: {
|
realIPSuccess: '実IPアドレス設定を保存しました',
|
||||||
label: 'お気に入り記録',
|
realIPError: '有効なIPアドレスを入力してください'
|
||||||
description: 'ローカルのお気に入り楽曲記録をクリア(クラウドのお気に入りには影響しません)'
|
}
|
||||||
},
|
},
|
||||||
user: {
|
system: {
|
||||||
label: 'ユーザーデータ',
|
cache: 'キャッシュ管理',
|
||||||
description: 'ログイン情報とユーザー関連データをクリア'
|
cacheDesc: 'キャッシュをクリア',
|
||||||
},
|
cacheClearTitle: 'クリアするキャッシュタイプを選択してください:',
|
||||||
settings: {
|
cacheTypes: {
|
||||||
label: 'アプリ設定',
|
history: {
|
||||||
description: 'アプリのすべてのカスタム設定をクリア'
|
label: '再生履歴',
|
||||||
},
|
description: '再生した楽曲の記録をクリア'
|
||||||
downloads: {
|
},
|
||||||
label: 'ダウンロード記録',
|
favorite: {
|
||||||
description: 'ダウンロード履歴をクリア(ダウンロード済みファイルは削除されません)'
|
label: 'お気に入り記録',
|
||||||
},
|
description: 'ローカルのお気に入り楽曲記録をクリア(クラウドのお気に入りには影響しません)'
|
||||||
resources: {
|
},
|
||||||
label: '音楽リソース',
|
user: {
|
||||||
description: '読み込み済みの音楽ファイル、歌詞などのリソースキャッシュをクリア'
|
label: 'ユーザーデータ',
|
||||||
},
|
description: 'ログイン情報とユーザー関連データをクリア'
|
||||||
lyrics: {
|
},
|
||||||
label: '歌詞リソース',
|
settings: {
|
||||||
description: '読み込み済みの歌詞リソースキャッシュをクリア'
|
label: 'アプリ設定',
|
||||||
}
|
description: 'アプリのすべてのカスタム設定をクリア'
|
||||||
},
|
},
|
||||||
restart: '再起動',
|
downloads: {
|
||||||
restartDesc: 'アプリを再起動',
|
label: 'ダウンロード記録',
|
||||||
messages: {
|
description: 'ダウンロード履歴をクリア(ダウンロード済みファイルは削除されません)'
|
||||||
clearSuccess: 'クリア成功。一部の設定は再起動後に有効になります'
|
},
|
||||||
}
|
resources: {
|
||||||
},
|
label: '音楽リソース',
|
||||||
about: {
|
description: '読み込み済みの音楽ファイル、歌詞などのリソースキャッシュをクリア'
|
||||||
version: 'バージョン',
|
},
|
||||||
checkUpdate: '更新を確認',
|
lyrics: {
|
||||||
checking: '確認中...',
|
label: '歌詞リソース',
|
||||||
latest: '現在最新バージョンです',
|
description: '読み込み済みの歌詞リソースキャッシュをクリア'
|
||||||
hasUpdate: '新しいバージョンが見つかりました',
|
}
|
||||||
gotoUpdate: '更新へ',
|
},
|
||||||
gotoGithub: 'Githubへ',
|
restart: '再起動',
|
||||||
author: '作者',
|
restartDesc: 'アプリを再起動',
|
||||||
authorDesc: 'algerkong スターを付けてください🌟',
|
messages: {
|
||||||
messages: {
|
clearSuccess: 'クリア成功。一部の設定は再起動後に有効になります'
|
||||||
checkError: '更新確認に失敗しました。後でもう一度お試しください'
|
}
|
||||||
}
|
},
|
||||||
},
|
about: {
|
||||||
validation: {
|
version: 'バージョン',
|
||||||
selectProxyProtocol: 'プロキシプロトコルを選択してください',
|
checkUpdate: '更新を確認',
|
||||||
proxyHost: 'プロキシアドレスを入力してください',
|
checking: '確認中...',
|
||||||
portNumber: '有効なポート番号を入力してください(1-65535)'
|
latest: '現在最新バージョンです',
|
||||||
},
|
hasUpdate: '新しいバージョンが見つかりました',
|
||||||
lyricSettings: {
|
gotoUpdate: '更新へ',
|
||||||
title: '歌詞設定',
|
gotoGithub: 'Githubへ',
|
||||||
tabs: {
|
author: '作者',
|
||||||
display: '表示',
|
authorDesc: 'algerkong スターを付けてください🌟',
|
||||||
interface: 'インターフェース',
|
messages: {
|
||||||
typography: 'テキスト',
|
checkError: '更新確認に失敗しました。後でもう一度お試しください'
|
||||||
mobile: 'モバイル'
|
}
|
||||||
},
|
},
|
||||||
pureMode: 'ピュアモード',
|
validation: {
|
||||||
hideCover: 'カバーを非表示',
|
selectProxyProtocol: 'プロキシプロトコルを選択してください',
|
||||||
centerDisplay: '中央表示',
|
proxyHost: 'プロキシアドレスを入力してください',
|
||||||
showTranslation: '翻訳を表示',
|
portNumber: '有効なポート番号を入力してください(1-65535)'
|
||||||
hideLyrics: '歌詞を非表示',
|
},
|
||||||
hidePlayBar: '再生バーを非表示',
|
lyricSettings: {
|
||||||
hideMiniPlayBar: 'ミニ再生バーを非表示',
|
title: '歌詞設定',
|
||||||
showMiniPlayBar: 'ミニ再生バーを表示',
|
tabs: {
|
||||||
backgroundTheme: '背景テーマ',
|
display: '表示',
|
||||||
themeOptions: {
|
interface: 'インターフェース',
|
||||||
default: 'デフォルト',
|
typography: 'テキスト',
|
||||||
light: 'ライト',
|
mobile: 'モバイル'
|
||||||
dark: 'ダーク'
|
},
|
||||||
},
|
pureMode: 'ピュアモード',
|
||||||
fontSize: 'フォントサイズ',
|
hideCover: 'カバーを非表示',
|
||||||
fontSizeMarks: {
|
centerDisplay: '中央表示',
|
||||||
small: '小',
|
showTranslation: '翻訳を表示',
|
||||||
medium: '中',
|
hideLyrics: '歌詞を非表示',
|
||||||
large: '大'
|
hidePlayBar: '再生バーを非表示',
|
||||||
},
|
hideMiniPlayBar: 'ミニ再生バーを非表示',
|
||||||
letterSpacing: '文字間隔',
|
showMiniPlayBar: 'ミニ再生バーを表示',
|
||||||
letterSpacingMarks: {
|
backgroundTheme: '背景テーマ',
|
||||||
compact: 'コンパクト',
|
themeOptions: {
|
||||||
default: 'デフォルト',
|
default: 'デフォルト',
|
||||||
loose: 'ゆったり'
|
light: 'ライト',
|
||||||
},
|
dark: 'ダーク'
|
||||||
lineHeight: '行の高さ',
|
},
|
||||||
lineHeightMarks: {
|
fontSize: 'フォントサイズ',
|
||||||
compact: 'コンパクト',
|
fontSizeMarks: {
|
||||||
default: 'デフォルト',
|
small: '小',
|
||||||
loose: 'ゆったり'
|
medium: '中',
|
||||||
},
|
large: '大'
|
||||||
mobileLayout: 'モバイルレイアウト',
|
},
|
||||||
layoutOptions: {
|
letterSpacing: '文字間隔',
|
||||||
default: 'デフォルト',
|
letterSpacingMarks: {
|
||||||
ios: 'iOSスタイル',
|
compact: 'コンパクト',
|
||||||
android: 'Androidスタイル'
|
default: 'デフォルト',
|
||||||
},
|
loose: 'ゆったり'
|
||||||
mobileCoverStyle: 'カバースタイル',
|
},
|
||||||
coverOptions: {
|
lineHeight: '行の高さ',
|
||||||
record: 'レコード',
|
lineHeightMarks: {
|
||||||
square: '正方形',
|
compact: 'コンパクト',
|
||||||
full: 'フルスクリーン'
|
default: 'デフォルト',
|
||||||
},
|
loose: 'ゆったり'
|
||||||
lyricLines: '歌詞行数',
|
},
|
||||||
mobileUnavailable: 'この設定はモバイルでのみ利用可能です'
|
mobileLayout: 'モバイルレイアウト',
|
||||||
},
|
layoutOptions: {
|
||||||
themeColor: {
|
default: 'デフォルト',
|
||||||
title: '歌詞テーマカラー',
|
ios: 'iOSスタイル',
|
||||||
presetColors: 'プリセットカラー',
|
android: 'Androidスタイル'
|
||||||
customColor: 'カスタムカラー',
|
},
|
||||||
preview: 'プレビュー効果',
|
mobileCoverStyle: 'カバースタイル',
|
||||||
previewText: '歌詞効果',
|
coverOptions: {
|
||||||
colorNames: {
|
record: 'レコード',
|
||||||
'spotify-green': 'Spotify グリーン',
|
square: '正方形',
|
||||||
'apple-blue': 'Apple ブルー',
|
full: 'フルスクリーン'
|
||||||
'youtube-red': 'YouTube レッド',
|
},
|
||||||
orange: 'バイタルオレンジ',
|
lyricLines: '歌詞行数',
|
||||||
purple: 'ミステリアスパープル',
|
mobileUnavailable: 'この設定はモバイルでのみ利用可能です'
|
||||||
pink: 'サクラピンク'
|
},
|
||||||
},
|
themeColor: {
|
||||||
tooltips: {
|
title: '歌詞テーマカラー',
|
||||||
openColorPicker: 'カラーパレットを開く',
|
presetColors: 'プリセットカラー',
|
||||||
closeColorPicker: 'カラーパレットを閉じる'
|
customColor: 'カスタムカラー',
|
||||||
},
|
preview: 'プレビュー効果',
|
||||||
placeholder: '#1db954'
|
previewText: '歌詞効果',
|
||||||
},
|
colorNames: {
|
||||||
shortcutSettings: {
|
'spotify-green': 'Spotify グリーン',
|
||||||
title: 'ショートカット設定',
|
'apple-blue': 'Apple ブルー',
|
||||||
shortcut: 'ショートカット',
|
'youtube-red': 'YouTube レッド',
|
||||||
shortcutDesc: 'ショートカットをカスタマイズ',
|
orange: 'バイタルオレンジ',
|
||||||
shortcutConflict: 'ショートカットの競合',
|
purple: 'ミステリアスパープル',
|
||||||
inputPlaceholder: 'クリックしてショートカットを入力',
|
pink: 'サクラピンク'
|
||||||
resetShortcuts: 'デフォルトに戻す',
|
},
|
||||||
disableAll: 'すべて無効',
|
tooltips: {
|
||||||
enableAll: 'すべて有効',
|
openColorPicker: 'カラーパレットを開く',
|
||||||
togglePlay: '再生/一時停止',
|
closeColorPicker: 'カラーパレットを閉じる'
|
||||||
prevPlay: '前の曲',
|
},
|
||||||
nextPlay: '次の曲',
|
placeholder: '#1db954'
|
||||||
volumeUp: '音量を上げる',
|
},
|
||||||
volumeDown: '音量を下げる',
|
shortcutSettings: {
|
||||||
toggleFavorite: 'お気に入り/お気に入り解除',
|
title: 'ショートカット設定',
|
||||||
toggleWindow: 'ウィンドウ表示/非表示',
|
shortcut: 'ショートカット',
|
||||||
scopeGlobal: 'グローバル',
|
shortcutDesc: 'ショートカットをカスタマイズ',
|
||||||
scopeApp: 'アプリ内',
|
shortcutConflict: 'ショートカットの競合',
|
||||||
enabled: '有効',
|
inputPlaceholder: 'クリックしてショートカットを入力',
|
||||||
disabled: '無効',
|
resetShortcuts: 'デフォルトに戻す',
|
||||||
messages: {
|
disableAll: 'すべて無効',
|
||||||
resetSuccess: 'デフォルトのショートカットに戻しました。保存を忘れずに',
|
enableAll: 'すべて有効',
|
||||||
conflict: '競合するショートカットがあります。再設定してください',
|
togglePlay: '再生/一時停止',
|
||||||
saveSuccess: 'ショートカット設定を保存しました',
|
prevPlay: '前の曲',
|
||||||
saveError: 'ショートカットの保存に失敗しました。再試行してください',
|
nextPlay: '次の曲',
|
||||||
cancelEdit: '変更をキャンセルしました',
|
volumeUp: '音量を上げる',
|
||||||
disableAll: 'すべてのショートカットを無効にしました。保存を忘れずに',
|
volumeDown: '音量を下げる',
|
||||||
enableAll: 'すべてのショートカットを有効にしました。保存を忘れずに'
|
toggleFavorite: 'お気に入り/お気に入り解除',
|
||||||
}
|
toggleWindow: 'ウィンドウ表示/非表示',
|
||||||
},
|
scopeGlobal: 'グローバル',
|
||||||
remoteControl: {
|
scopeApp: 'アプリ内',
|
||||||
title: 'リモートコントロール',
|
enabled: '有効',
|
||||||
enable: 'リモートコントロールを有効にする',
|
disabled: '無効',
|
||||||
port: 'サービスポート',
|
messages: {
|
||||||
allowedIps: '許可されたIPアドレス',
|
resetSuccess: 'デフォルトのショートカットに戻しました。保存を忘れずに',
|
||||||
addIp: 'IPを追加',
|
conflict: '競合するショートカットがあります。再設定してください',
|
||||||
emptyListHint: '空のリストはすべてのIPアクセスを許可することを意味します',
|
saveSuccess: 'ショートカット設定を保存しました',
|
||||||
saveSuccess: 'リモートコントロール設定を保存しました',
|
saveError: 'ショートカットの保存に失敗しました。再試行してください',
|
||||||
accessInfo: 'リモートコントロールアクセスアドレス:'
|
cancelEdit: '変更をキャンセルしました',
|
||||||
},
|
disableAll: 'すべてのショートカットを無効にしました。保存を忘れずに',
|
||||||
cookie: {
|
enableAll: 'すべてのショートカットを有効にしました。保存を忘れずに'
|
||||||
title: 'Cookie設定',
|
}
|
||||||
description: 'NetEase Cloud MusicのCookieを入力してください:',
|
},
|
||||||
placeholder: '完全なCookieを貼り付けてください...',
|
remoteControl: {
|
||||||
help: {
|
title: 'リモートコントロール',
|
||||||
format: 'Cookieは通常「MUSIC_U=」で始まります',
|
enable: 'リモートコントロールを有効にする',
|
||||||
source: 'ブラウザの開発者ツールのネットワークリクエストから取得できます',
|
port: 'サービスポート',
|
||||||
storage: 'Cookie設定後、自動的にローカルストレージに保存されます'
|
allowedIps: '許可されたIPアドレス',
|
||||||
},
|
addIp: 'IPを追加',
|
||||||
action: {
|
emptyListHint: '空のリストはすべてのIPアクセスを許可することを意味します',
|
||||||
save: 'Cookieを保存',
|
saveSuccess: 'リモートコントロール設定を保存しました',
|
||||||
paste: '貼り付け',
|
accessInfo: 'リモートコントロールアクセスアドレス:'
|
||||||
clear: 'クリア'
|
},
|
||||||
},
|
cookie: {
|
||||||
validation: {
|
title: 'Cookie設定',
|
||||||
required: 'Cookieを入力してください',
|
description: 'NetEase Cloud MusicのCookieを入力してください:',
|
||||||
format: 'Cookie形式が正しくない可能性があります。MUSIC_Uが含まれているか確認してください'
|
placeholder: '完全なCookieを貼り付けてください...',
|
||||||
},
|
help: {
|
||||||
message: {
|
format: 'Cookieは通常「MUSIC_U=」で始まります',
|
||||||
saveSuccess: 'Cookieの保存に成功しました',
|
source: 'ブラウザの開発者ツールのネットワークリクエストから取得できます',
|
||||||
saveError: 'Cookieの保存に失敗しました',
|
storage: 'Cookie設定後、自動的にローカルストレージに保存されます'
|
||||||
pasteSuccess: '貼り付けに成功しました',
|
},
|
||||||
pasteError: '貼り付けに失敗しました。手動でコピーしてください'
|
action: {
|
||||||
},
|
save: 'Cookieを保存',
|
||||||
info: {
|
paste: '貼り付け',
|
||||||
length: '現在の長さ:{length} 文字'
|
clear: 'クリア'
|
||||||
}
|
},
|
||||||
}
|
validation: {
|
||||||
|
required: 'Cookieを入力してください',
|
||||||
|
format: 'Cookie形式が正しくない可能性があります。MUSIC_Uが含まれているか確認してください'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
saveSuccess: 'Cookieの保存に成功しました',
|
||||||
|
saveError: 'Cookieの保存に失敗しました',
|
||||||
|
pasteSuccess: '貼り付けに成功しました',
|
||||||
|
pasteError: '貼り付けに失敗しました。手動でコピーしてください'
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
length: '現在の長さ:{length} 文字'
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
+334
-321
@@ -1,322 +1,335 @@
|
|||||||
export default {
|
export default {
|
||||||
theme: '테마',
|
theme: '테마',
|
||||||
language: '언어',
|
language: '언어',
|
||||||
regard: '정보',
|
regard: '정보',
|
||||||
logout: '로그아웃',
|
logout: '로그아웃',
|
||||||
sections: {
|
sections: {
|
||||||
basic: '기본 설정',
|
basic: '기본 설정',
|
||||||
playback: '재생 설정',
|
playback: '재생 설정',
|
||||||
application: '애플리케이션 설정',
|
application: '애플리케이션 설정',
|
||||||
network: '네트워크 설정',
|
network: '네트워크 설정',
|
||||||
system: '시스템 관리',
|
system: '시스템 관리',
|
||||||
donation: '후원 지원',
|
donation: '후원 지원',
|
||||||
regard: '정보'
|
regard: '정보'
|
||||||
},
|
},
|
||||||
basic: {
|
basic: {
|
||||||
themeMode: '테마 모드',
|
themeMode: '테마 모드',
|
||||||
themeModeDesc: '낮/밤 테마 전환',
|
themeModeDesc: '낮/밤 테마 전환',
|
||||||
autoTheme: '시스템 따라가기',
|
autoTheme: '시스템 따라가기',
|
||||||
manualTheme: '수동 전환',
|
manualTheme: '수동 전환',
|
||||||
language: '언어 설정',
|
language: '언어 설정',
|
||||||
languageDesc: '표시 언어 전환',
|
languageDesc: '표시 언어 전환',
|
||||||
tokenManagement: 'Cookie 관리',
|
tokenManagement: 'Cookie 관리',
|
||||||
tokenManagementDesc: '넷이즈 클라우드 뮤직 로그인 Cookie 관리',
|
tokenManagementDesc: '넷이즈 클라우드 뮤직 로그인 Cookie 관리',
|
||||||
tokenStatus: '현재 Cookie 상태',
|
tokenStatus: '현재 Cookie 상태',
|
||||||
tokenSet: '설정됨',
|
tokenSet: '설정됨',
|
||||||
tokenNotSet: '설정되지 않음',
|
tokenNotSet: '설정되지 않음',
|
||||||
setToken: 'Cookie 설정',
|
setToken: 'Cookie 설정',
|
||||||
modifyToken: 'Cookie 수정',
|
modifyToken: 'Cookie 수정',
|
||||||
clearToken: 'Cookie 지우기',
|
clearToken: 'Cookie 지우기',
|
||||||
font: '폰트 설정',
|
font: '폰트 설정',
|
||||||
fontDesc: '폰트 선택, 앞에 있는 폰트를 우선 사용',
|
fontDesc: '폰트 선택, 앞에 있는 폰트를 우선 사용',
|
||||||
fontScope: {
|
fontScope: {
|
||||||
global: '전역',
|
global: '전역',
|
||||||
lyric: '가사만'
|
lyric: '가사만'
|
||||||
},
|
},
|
||||||
animation: '애니메이션 속도',
|
animation: '애니메이션 속도',
|
||||||
animationDesc: '애니메이션 활성화 여부',
|
animationDesc: '애니메이션 활성화 여부',
|
||||||
animationSpeed: {
|
animationSpeed: {
|
||||||
slow: '매우 느림',
|
slow: '매우 느림',
|
||||||
normal: '보통',
|
normal: '보통',
|
||||||
fast: '매우 빠름'
|
fast: '매우 빠름'
|
||||||
},
|
},
|
||||||
fontPreview: {
|
fontPreview: {
|
||||||
title: '폰트 미리보기',
|
title: '폰트 미리보기',
|
||||||
chinese: '中文',
|
chinese: '中文',
|
||||||
english: 'English',
|
english: 'English',
|
||||||
japanese: '日本語',
|
japanese: '日本語',
|
||||||
korean: '한국어',
|
korean: '한국어',
|
||||||
chineseText: '静夜思 床前明月光 疑是地上霜',
|
chineseText: '静夜思 床前明月光 疑是地上霜',
|
||||||
englishText: 'The quick brown fox jumps over the lazy dog',
|
englishText: 'The quick brown fox jumps over the lazy dog',
|
||||||
japaneseText: 'あいうえお かきくけこ さしすせそ',
|
japaneseText: 'あいうえお かきくけこ さしすせそ',
|
||||||
koreanText: '가나다라마 바사아자차 카타파하'
|
koreanText: '가나다라마 바사아자차 카타파하'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
playback: {
|
playback: {
|
||||||
quality: '음질 설정',
|
quality: '음질 설정',
|
||||||
qualityDesc: '음악 재생 음질 선택 (넷이즈 클라우드 VIP)',
|
qualityDesc: '음악 재생 음질 선택 (넷이즈 클라우드 VIP)',
|
||||||
qualityOptions: {
|
qualityOptions: {
|
||||||
standard: '표준',
|
standard: '표준',
|
||||||
higher: '높음',
|
higher: '높음',
|
||||||
exhigh: '매우 높음',
|
exhigh: '매우 높음',
|
||||||
lossless: '무손실',
|
lossless: '무손실',
|
||||||
hires: 'Hi-Res',
|
hires: 'Hi-Res',
|
||||||
jyeffect: 'HD 서라운드',
|
jyeffect: 'HD 서라운드',
|
||||||
sky: '몰입형 서라운드',
|
sky: '몰입형 서라운드',
|
||||||
dolby: '돌비 애트모스',
|
dolby: '돌비 애트모스',
|
||||||
jymaster: '초고화질 마스터'
|
jymaster: '초고화질 마스터'
|
||||||
},
|
},
|
||||||
musicSources: '음원 설정',
|
musicSources: '음원 설정',
|
||||||
musicSourcesDesc: '음악 해석에 사용할 음원 플랫폼 선택',
|
musicSourcesDesc: '음악 해석에 사용할 음원 플랫폼 선택',
|
||||||
musicSourcesWarning: '최소 하나의 음원 플랫폼을 선택해야 합니다',
|
musicSourcesWarning: '최소 하나의 음원 플랫폼을 선택해야 합니다',
|
||||||
musicUnblockEnable: '음악 해석 활성화',
|
musicUnblockEnable: '음악 해석 활성화',
|
||||||
musicUnblockEnableDesc: '활성화하면 재생할 수 없는 음악을 해석하려고 시도합니다',
|
musicUnblockEnableDesc: '활성화하면 재생할 수 없는 음악을 해석하려고 시도합니다',
|
||||||
configureMusicSources: '음원 구성',
|
configureMusicSources: '음원 구성',
|
||||||
selectedMusicSources: '선택된 음원:',
|
selectedMusicSources: '선택된 음원:',
|
||||||
noMusicSources: '음원이 선택되지 않음',
|
noMusicSources: '음원이 선택되지 않음',
|
||||||
gdmusicInfo: 'GD 뮤직은 여러 플랫폼 음원을 자동으로 해석하고 최적의 결과를 자동 선택합니다',
|
gdmusicInfo: 'GD 뮤직은 여러 플랫폼 음원을 자동으로 해석하고 최적의 결과를 자동 선택합니다',
|
||||||
autoPlay: '자동 재생',
|
autoPlay: '자동 재생',
|
||||||
autoPlayDesc: '앱을 다시 열 때 자동으로 재생을 계속할지 여부',
|
autoPlayDesc: '앱을 다시 열 때 자동으로 재생을 계속할지 여부',
|
||||||
showStatusBar: '상태바 제어 기능 표시 여부',
|
showStatusBar: '상태바 제어 기능 표시 여부',
|
||||||
showStatusBarContent: 'Mac 상태바에 음악 제어 기능을 표시할 수 있습니다 (재시작 후 적용)'
|
showStatusBarContent: 'Mac 상태바에 음악 제어 기능을 표시할 수 있습니다 (재시작 후 적용)'
|
||||||
},
|
showStatusBarContent: 'Mac 상태바에 음악 제어 기능을 표시할 수 있습니다 (재시작 후 적용)',
|
||||||
application: {
|
fallbackParser: '대체 분석 서비스 (GD Music)',
|
||||||
closeAction: '닫기 동작',
|
fallbackParserDesc: '"GD Music"을 선택하고 일반 음원을 사용할 수 없을 때 이 서비스를 사용합니다.',
|
||||||
closeActionDesc: '창을 닫을 때의 동작 선택',
|
parserGD: 'GD Music (내장)',
|
||||||
closeOptions: {
|
parserCustom: '사용자 지정 API',
|
||||||
ask: '매번 묻기',
|
|
||||||
minimize: '트레이로 최소화',
|
customApi: {
|
||||||
close: '직접 종료'
|
importConfig: 'JSON 설정 가져오기',
|
||||||
},
|
currentSource: '현재 음원',
|
||||||
shortcut: '단축키 설정',
|
notImported: '아직 사용자 지정 음원을 가져오지 않았습니다.',
|
||||||
shortcutDesc: '전역 단축키 사용자 정의',
|
importSuccess: '음원 가져오기 성공: {name}',
|
||||||
download: '다운로드 관리',
|
importFailed: '가져오기 실패: {message}',
|
||||||
downloadDesc: '다운로드 목록 버튼을 항상 표시할지 여부',
|
},
|
||||||
unlimitedDownload: '무제한 다운로드',
|
},
|
||||||
unlimitedDownloadDesc: '활성화하면 음악을 무제한으로 다운로드합니다 (다운로드 실패가 발생할 수 있음), 기본 제한 300곡',
|
application: {
|
||||||
downloadPath: '다운로드 디렉토리',
|
closeAction: '닫기 동작',
|
||||||
downloadPathDesc: '음악 파일의 다운로드 위치 선택',
|
closeActionDesc: '창을 닫을 때의 동작 선택',
|
||||||
remoteControl: '원격 제어',
|
closeOptions: {
|
||||||
remoteControlDesc: '원격 제어 기능 설정'
|
ask: '매번 묻기',
|
||||||
},
|
minimize: '트레이로 최소화',
|
||||||
network: {
|
close: '직접 종료'
|
||||||
apiPort: '음악 API 포트',
|
},
|
||||||
apiPortDesc: '수정 후 앱을 재시작해야 합니다',
|
shortcut: '단축키 설정',
|
||||||
proxy: '프록시 설정',
|
shortcutDesc: '전역 단축키 사용자 정의',
|
||||||
proxyDesc: '음악에 액세스할 수 없을 때 프록시를 활성화할 수 있습니다',
|
download: '다운로드 관리',
|
||||||
proxyHost: '프록시 주소',
|
downloadDesc: '다운로드 목록 버튼을 항상 표시할지 여부',
|
||||||
proxyHostPlaceholder: '프록시 주소를 입력하세요',
|
unlimitedDownload: '무제한 다운로드',
|
||||||
proxyPort: '프록시 포트',
|
unlimitedDownloadDesc: '활성화하면 음악을 무제한으로 다운로드합니다 (다운로드 실패가 발생할 수 있음), 기본 제한 300곡',
|
||||||
proxyPortPlaceholder: '프록시 포트를 입력하세요',
|
downloadPath: '다운로드 디렉토리',
|
||||||
realIP: 'realIP 설정',
|
downloadPathDesc: '음악 파일의 다운로드 위치 선택',
|
||||||
realIPDesc: '제한으로 인해 이 프로젝트는 해외에서 사용할 때 제한을 받을 수 있으며, realIP 매개변수를 사용하여 국내 IP를 전달하여 해결할 수 있습니다',
|
remoteControl: '원격 제어',
|
||||||
messages: {
|
remoteControlDesc: '원격 제어 기능 설정'
|
||||||
proxySuccess: '프록시 설정이 저장되었습니다. 앱을 재시작한 후 적용됩니다',
|
},
|
||||||
proxyError: '입력이 올바른지 확인하세요',
|
network: {
|
||||||
realIPSuccess: '실제 IP 설정이 저장되었습니다',
|
apiPort: '음악 API 포트',
|
||||||
realIPError: '유효한 IP 주소를 입력하세요'
|
apiPortDesc: '수정 후 앱을 재시작해야 합니다',
|
||||||
}
|
proxy: '프록시 설정',
|
||||||
},
|
proxyDesc: '음악에 액세스할 수 없을 때 프록시를 활성화할 수 있습니다',
|
||||||
system: {
|
proxyHost: '프록시 주소',
|
||||||
cache: '캐시 관리',
|
proxyHostPlaceholder: '프록시 주소를 입력하세요',
|
||||||
cacheDesc: '캐시 지우기',
|
proxyPort: '프록시 포트',
|
||||||
cacheClearTitle: '지울 캐시 유형을 선택하세요:',
|
proxyPortPlaceholder: '프록시 포트를 입력하세요',
|
||||||
cacheTypes: {
|
realIP: 'realIP 설정',
|
||||||
history: {
|
realIPDesc: '제한으로 인해 이 프로젝트는 해외에서 사용할 때 제한을 받을 수 있으며, realIP 매개변수를 사용하여 국내 IP를 전달하여 해결할 수 있습니다',
|
||||||
label: '재생 기록',
|
messages: {
|
||||||
description: '재생한 곡 기록 지우기'
|
proxySuccess: '프록시 설정이 저장되었습니다. 앱을 재시작한 후 적용됩니다',
|
||||||
},
|
proxyError: '입력이 올바른지 확인하세요',
|
||||||
favorite: {
|
realIPSuccess: '실제 IP 설정이 저장되었습니다',
|
||||||
label: '즐겨찾기 기록',
|
realIPError: '유효한 IP 주소를 입력하세요'
|
||||||
description: '로컬 즐겨찾기 곡 기록 지우기 (클라우드 즐겨찾기에는 영향 없음)'
|
}
|
||||||
},
|
},
|
||||||
user: {
|
system: {
|
||||||
label: '사용자 데이터',
|
cache: '캐시 관리',
|
||||||
description: '로그인 정보 및 사용자 관련 데이터 지우기'
|
cacheDesc: '캐시 지우기',
|
||||||
},
|
cacheClearTitle: '지울 캐시 유형을 선택하세요:',
|
||||||
settings: {
|
cacheTypes: {
|
||||||
label: '앱 설정',
|
history: {
|
||||||
description: '앱의 모든 사용자 정의 설정 지우기'
|
label: '재생 기록',
|
||||||
},
|
description: '재생한 곡 기록 지우기'
|
||||||
downloads: {
|
},
|
||||||
label: '다운로드 기록',
|
favorite: {
|
||||||
description: '다운로드 기록 지우기 (다운로드된 파일은 삭제되지 않음)'
|
label: '즐겨찾기 기록',
|
||||||
},
|
description: '로컬 즐겨찾기 곡 기록 지우기 (클라우드 즐겨찾기에는 영향 없음)'
|
||||||
resources: {
|
},
|
||||||
label: '음악 리소스',
|
user: {
|
||||||
description: '로드된 음악 파일, 가사 등 리소스 캐시 지우기'
|
label: '사용자 데이터',
|
||||||
},
|
description: '로그인 정보 및 사용자 관련 데이터 지우기'
|
||||||
lyrics: {
|
},
|
||||||
label: '가사 리소스',
|
settings: {
|
||||||
description: '로드된 가사 리소스 캐시 지우기'
|
label: '앱 설정',
|
||||||
}
|
description: '앱의 모든 사용자 정의 설정 지우기'
|
||||||
},
|
},
|
||||||
restart: '재시작',
|
downloads: {
|
||||||
restartDesc: '앱 재시작',
|
label: '다운로드 기록',
|
||||||
messages: {
|
description: '다운로드 기록 지우기 (다운로드된 파일은 삭제되지 않음)'
|
||||||
clearSuccess: '지우기 성공, 일부 설정은 재시작 후 적용됩니다'
|
},
|
||||||
}
|
resources: {
|
||||||
},
|
label: '음악 리소스',
|
||||||
about: {
|
description: '로드된 음악 파일, 가사 등 리소스 캐시 지우기'
|
||||||
version: '버전',
|
},
|
||||||
checkUpdate: '업데이트 확인',
|
lyrics: {
|
||||||
checking: '확인 중...',
|
label: '가사 리소스',
|
||||||
latest: '현재 최신 버전입니다',
|
description: '로드된 가사 리소스 캐시 지우기'
|
||||||
hasUpdate: '새 버전 발견',
|
}
|
||||||
gotoUpdate: '업데이트하러 가기',
|
},
|
||||||
gotoGithub: 'Github로 이동',
|
restart: '재시작',
|
||||||
author: '작성자',
|
restartDesc: '앱 재시작',
|
||||||
authorDesc: 'algerkong 별점🌟 부탁드려요',
|
messages: {
|
||||||
messages: {
|
clearSuccess: '지우기 성공, 일부 설정은 재시작 후 적용됩니다'
|
||||||
checkError: '업데이트 확인 실패, 나중에 다시 시도하세요'
|
}
|
||||||
}
|
},
|
||||||
},
|
about: {
|
||||||
validation: {
|
version: '버전',
|
||||||
selectProxyProtocol: '프록시 프로토콜을 선택하세요',
|
checkUpdate: '업데이트 확인',
|
||||||
proxyHost: '프록시 주소를 입력하세요',
|
checking: '확인 중...',
|
||||||
portNumber: '유효한 포트 번호를 입력하세요 (1-65535)'
|
latest: '현재 최신 버전입니다',
|
||||||
},
|
hasUpdate: '새 버전 발견',
|
||||||
lyricSettings: {
|
gotoUpdate: '업데이트하러 가기',
|
||||||
title: '가사 설정',
|
gotoGithub: 'Github로 이동',
|
||||||
tabs: {
|
author: '작성자',
|
||||||
display: '표시',
|
authorDesc: 'algerkong 별점🌟 부탁드려요',
|
||||||
interface: '인터페이스',
|
messages: {
|
||||||
typography: '텍스트',
|
checkError: '업데이트 확인 실패, 나중에 다시 시도하세요'
|
||||||
mobile: '모바일'
|
}
|
||||||
},
|
},
|
||||||
pureMode: '순수 모드',
|
validation: {
|
||||||
hideCover: '커버 숨기기',
|
selectProxyProtocol: '프록시 프로토콜을 선택하세요',
|
||||||
centerDisplay: '중앙 표시',
|
proxyHost: '프록시 주소를 입력하세요',
|
||||||
showTranslation: '번역 표시',
|
portNumber: '유효한 포트 번호를 입력하세요 (1-65535)'
|
||||||
hideLyrics: '가사 숨기기',
|
},
|
||||||
hidePlayBar: '재생바 숨기기',
|
lyricSettings: {
|
||||||
hideMiniPlayBar: '미니 재생바 숨기기',
|
title: '가사 설정',
|
||||||
showMiniPlayBar: '미니 재생바 표시',
|
tabs: {
|
||||||
backgroundTheme: '배경 테마',
|
display: '표시',
|
||||||
themeOptions: {
|
interface: '인터페이스',
|
||||||
default: '기본',
|
typography: '텍스트',
|
||||||
light: '밝음',
|
mobile: '모바일'
|
||||||
dark: '어둠'
|
},
|
||||||
},
|
pureMode: '순수 모드',
|
||||||
fontSize: '폰트 크기',
|
hideCover: '커버 숨기기',
|
||||||
fontSizeMarks: {
|
centerDisplay: '중앙 표시',
|
||||||
small: '작음',
|
showTranslation: '번역 표시',
|
||||||
medium: '중간',
|
hideLyrics: '가사 숨기기',
|
||||||
large: '큼'
|
hidePlayBar: '재생바 숨기기',
|
||||||
},
|
hideMiniPlayBar: '미니 재생바 숨기기',
|
||||||
letterSpacing: '글자 간격',
|
showMiniPlayBar: '미니 재생바 표시',
|
||||||
letterSpacingMarks: {
|
backgroundTheme: '배경 테마',
|
||||||
compact: '좁음',
|
themeOptions: {
|
||||||
default: '기본',
|
default: '기본',
|
||||||
loose: '넓음'
|
light: '밝음',
|
||||||
},
|
dark: '어둠'
|
||||||
lineHeight: '줄 높이',
|
},
|
||||||
lineHeightMarks: {
|
fontSize: '폰트 크기',
|
||||||
compact: '좁음',
|
fontSizeMarks: {
|
||||||
default: '기본',
|
small: '작음',
|
||||||
loose: '넓음'
|
medium: '중간',
|
||||||
},
|
large: '큼'
|
||||||
mobileLayout: '모바일 레이아웃',
|
},
|
||||||
layoutOptions: {
|
letterSpacing: '글자 간격',
|
||||||
default: '기본',
|
letterSpacingMarks: {
|
||||||
ios: 'iOS 스타일',
|
compact: '좁음',
|
||||||
android: '안드로이드 스타일'
|
default: '기본',
|
||||||
},
|
loose: '넓음'
|
||||||
mobileCoverStyle: '커버 스타일',
|
},
|
||||||
coverOptions: {
|
lineHeight: '줄 높이',
|
||||||
record: '레코드',
|
lineHeightMarks: {
|
||||||
square: '정사각형',
|
compact: '좁음',
|
||||||
full: '전체화면'
|
default: '기본',
|
||||||
},
|
loose: '넓음'
|
||||||
lyricLines: '가사 줄 수',
|
},
|
||||||
mobileUnavailable: '이 설정은 모바일에서만 사용 가능합니다'
|
mobileLayout: '모바일 레이아웃',
|
||||||
},
|
layoutOptions: {
|
||||||
themeColor: {
|
default: '기본',
|
||||||
title: '가사 테마 색상',
|
ios: 'iOS 스타일',
|
||||||
presetColors: '미리 설정된 색상',
|
android: '안드로이드 스타일'
|
||||||
customColor: '사용자 정의 색상',
|
},
|
||||||
preview: '미리보기 효과',
|
mobileCoverStyle: '커버 스타일',
|
||||||
previewText: '가사 효과',
|
coverOptions: {
|
||||||
colorNames: {
|
record: '레코드',
|
||||||
'spotify-green': 'Spotify 그린',
|
square: '정사각형',
|
||||||
'apple-blue': '애플 블루',
|
full: '전체화면'
|
||||||
'youtube-red': 'YouTube 레드',
|
},
|
||||||
orange: '활력 오렌지',
|
lyricLines: '가사 줄 수',
|
||||||
purple: '신비 퍼플',
|
mobileUnavailable: '이 설정은 모바일에서만 사용 가능합니다'
|
||||||
pink: '벚꽃 핑크'
|
},
|
||||||
},
|
themeColor: {
|
||||||
tooltips: {
|
title: '가사 테마 색상',
|
||||||
openColorPicker: '색상 선택기 열기',
|
presetColors: '미리 설정된 색상',
|
||||||
closeColorPicker: '색상 선택기 닫기'
|
customColor: '사용자 정의 색상',
|
||||||
},
|
preview: '미리보기 효과',
|
||||||
placeholder: '#1db954'
|
previewText: '가사 효과',
|
||||||
},
|
colorNames: {
|
||||||
shortcutSettings: {
|
'spotify-green': 'Spotify 그린',
|
||||||
title: '단축키 설정',
|
'apple-blue': '애플 블루',
|
||||||
shortcut: '단축키',
|
'youtube-red': 'YouTube 레드',
|
||||||
shortcutDesc: '단축키 사용자 정의',
|
orange: '활력 오렌지',
|
||||||
shortcutConflict: '단축키 충돌',
|
purple: '신비 퍼플',
|
||||||
inputPlaceholder: '클릭하여 단축키 입력',
|
pink: '벚꽃 핑크'
|
||||||
resetShortcuts: '기본값 복원',
|
},
|
||||||
disableAll: '모두 비활성화',
|
tooltips: {
|
||||||
enableAll: '모두 활성화',
|
openColorPicker: '색상 선택기 열기',
|
||||||
togglePlay: '재생/일시정지',
|
closeColorPicker: '색상 선택기 닫기'
|
||||||
prevPlay: '이전 곡',
|
},
|
||||||
nextPlay: '다음 곡',
|
placeholder: '#1db954'
|
||||||
volumeUp: '볼륨 증가',
|
},
|
||||||
volumeDown: '볼륨 감소',
|
shortcutSettings: {
|
||||||
toggleFavorite: '즐겨찾기/즐겨찾기 취소',
|
title: '단축키 설정',
|
||||||
toggleWindow: '창 표시/숨기기',
|
shortcut: '단축키',
|
||||||
scopeGlobal: '전역',
|
shortcutDesc: '단축키 사용자 정의',
|
||||||
scopeApp: '앱 내',
|
shortcutConflict: '단축키 충돌',
|
||||||
enabled: '활성화',
|
inputPlaceholder: '클릭하여 단축키 입력',
|
||||||
disabled: '비활성화',
|
resetShortcuts: '기본값 복원',
|
||||||
messages: {
|
disableAll: '모두 비활성화',
|
||||||
resetSuccess: '기본 단축키로 복원되었습니다. 저장을 잊지 마세요',
|
enableAll: '모두 활성화',
|
||||||
conflict: '충돌하는 단축키가 있습니다. 다시 설정하세요',
|
togglePlay: '재생/일시정지',
|
||||||
saveSuccess: '단축키 설정이 저장되었습니다',
|
prevPlay: '이전 곡',
|
||||||
saveError: '단축키 저장 실패, 다시 시도하세요',
|
nextPlay: '다음 곡',
|
||||||
cancelEdit: '수정이 취소되었습니다',
|
volumeUp: '볼륨 증가',
|
||||||
disableAll: '모든 단축키가 비활성화되었습니다. 저장을 잊지 마세요',
|
volumeDown: '볼륨 감소',
|
||||||
enableAll: '모든 단축키가 활성화되었습니다. 저장을 잊지 마세요'
|
toggleFavorite: '즐겨찾기/즐겨찾기 취소',
|
||||||
}
|
toggleWindow: '창 표시/숨기기',
|
||||||
},
|
scopeGlobal: '전역',
|
||||||
remoteControl: {
|
scopeApp: '앱 내',
|
||||||
title: '원격 제어',
|
enabled: '활성화',
|
||||||
enable: '원격 제어 활성화',
|
disabled: '비활성화',
|
||||||
port: '서비스 포트',
|
messages: {
|
||||||
allowedIps: '허용된 IP 주소',
|
resetSuccess: '기본 단축키로 복원되었습니다. 저장을 잊지 마세요',
|
||||||
addIp: 'IP 추가',
|
conflict: '충돌하는 단축키가 있습니다. 다시 설정하세요',
|
||||||
emptyListHint: '빈 목록은 모든 IP 액세스를 허용함을 의미합니다',
|
saveSuccess: '단축키 설정이 저장되었습니다',
|
||||||
saveSuccess: '원격 제어 설정이 저장되었습니다',
|
saveError: '단축키 저장 실패, 다시 시도하세요',
|
||||||
accessInfo: '원격 제어 액세스 주소:'
|
cancelEdit: '수정이 취소되었습니다',
|
||||||
},
|
disableAll: '모든 단축키가 비활성화되었습니다. 저장을 잊지 마세요',
|
||||||
cookie: {
|
enableAll: '모든 단축키가 활성화되었습니다. 저장을 잊지 마세요'
|
||||||
title: 'Cookie 설정',
|
}
|
||||||
description: '넷이즈 클라우드 뮤직의 Cookie를 입력하세요:',
|
},
|
||||||
placeholder: '완전한 Cookie를 붙여넣으세요...',
|
remoteControl: {
|
||||||
help: {
|
title: '원격 제어',
|
||||||
format: 'Cookie는 일반적으로 "MUSIC_U="로 시작합니다',
|
enable: '원격 제어 활성화',
|
||||||
source: '브라우저 개발자 도구의 네트워크 요청에서 얻을 수 있습니다',
|
port: '서비스 포트',
|
||||||
storage: 'Cookie 설정 후 자동으로 로컬 저장소에 저장됩니다'
|
allowedIps: '허용된 IP 주소',
|
||||||
},
|
addIp: 'IP 추가',
|
||||||
action: {
|
emptyListHint: '빈 목록은 모든 IP 액세스를 허용함을 의미합니다',
|
||||||
save: 'Cookie 저장',
|
saveSuccess: '원격 제어 설정이 저장되었습니다',
|
||||||
paste: '붙여넣기',
|
accessInfo: '원격 제어 액세스 주소:'
|
||||||
clear: '지우기'
|
},
|
||||||
},
|
cookie: {
|
||||||
validation: {
|
title: 'Cookie 설정',
|
||||||
required: 'Cookie를 입력하세요',
|
description: '넷이즈 클라우드 뮤직의 Cookie를 입력하세요:',
|
||||||
format: 'Cookie 형식이 올바르지 않을 수 있습니다. MUSIC_U가 포함되어 있는지 확인하세요'
|
placeholder: '완전한 Cookie를 붙여넣으세요...',
|
||||||
},
|
help: {
|
||||||
message: {
|
format: 'Cookie는 일반적으로 "MUSIC_U="로 시작합니다',
|
||||||
saveSuccess: 'Cookie 저장 성공',
|
source: '브라우저 개발자 도구의 네트워크 요청에서 얻을 수 있습니다',
|
||||||
saveError: 'Cookie 저장 실패',
|
storage: 'Cookie 설정 후 자동으로 로컬 저장소에 저장됩니다'
|
||||||
pasteSuccess: '붙여넣기 성공',
|
},
|
||||||
pasteError: '붙여넣기 실패, 수동으로 복사하세요'
|
action: {
|
||||||
},
|
save: 'Cookie 저장',
|
||||||
info: {
|
paste: '붙여넣기',
|
||||||
length: '현재 길이: {length} 문자'
|
clear: '지우기'
|
||||||
}
|
},
|
||||||
}
|
validation: {
|
||||||
|
required: 'Cookie를 입력하세요',
|
||||||
|
format: 'Cookie 형식이 올바르지 않을 수 있습니다. MUSIC_U가 포함되어 있는지 확인하세요'
|
||||||
|
},
|
||||||
|
message: {
|
||||||
|
saveSuccess: 'Cookie 저장 성공',
|
||||||
|
saveError: 'Cookie 저장 실패',
|
||||||
|
pasteSuccess: '붙여넣기 성공',
|
||||||
|
pasteError: '붙여넣기 실패, 수동으로 복사하세요'
|
||||||
|
},
|
||||||
|
info: {
|
||||||
|
length: '현재 길이: {length} 문자'
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
@@ -79,6 +79,21 @@ export default {
|
|||||||
autoPlayDesc: '重新打开应用时是否自动继续播放',
|
autoPlayDesc: '重新打开应用时是否自动继续播放',
|
||||||
showStatusBar: '是否显示状态栏控制功能',
|
showStatusBar: '是否显示状态栏控制功能',
|
||||||
showStatusBarContent: '可以在您的mac状态栏显示音乐控制功能(重启后生效)'
|
showStatusBarContent: '可以在您的mac状态栏显示音乐控制功能(重启后生效)'
|
||||||
|
showStatusBarContent: '可以在您的mac状态栏显示音乐控制功能(重启后生效)',
|
||||||
|
|
||||||
|
fallbackParser: 'GD音乐台(music.gdstudio.xyz)设置',
|
||||||
|
fallbackParserDesc: 'GD音乐台将自动尝试多个音乐平台进行解析,无需额外配置。优先级高于其他解析方式,但是请求可能较慢。感谢(music.gdstudio.xyz)\n',
|
||||||
|
parserGD: 'GD 音乐台 (内置)',
|
||||||
|
parserCustom: '自定义 API',
|
||||||
|
|
||||||
|
// 自定义API相关的提示
|
||||||
|
customApi: {
|
||||||
|
importConfig: '导入 JSON 配置',
|
||||||
|
currentSource: '当前音源',
|
||||||
|
notImported: '尚未导入自定义音源。',
|
||||||
|
importSuccess: '成功导入音源: {name}',
|
||||||
|
importFailed: '导入失败: {message}',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
application: {
|
application: {
|
||||||
closeAction: '关闭行为',
|
closeAction: '关闭行为',
|
||||||
|
|||||||
@@ -79,6 +79,19 @@ export default {
|
|||||||
autoPlayDesc: '重新開啟應用程式時是否自動繼續播放',
|
autoPlayDesc: '重新開啟應用程式時是否自動繼續播放',
|
||||||
showStatusBar: '是否顯示狀態列控制功能',
|
showStatusBar: '是否顯示狀態列控制功能',
|
||||||
showStatusBarContent: '可以在您的mac狀態列顯示音樂控制功能(重啟後生效)'
|
showStatusBarContent: '可以在您的mac狀態列顯示音樂控制功能(重啟後生效)'
|
||||||
|
showStatusBarContent: '可以在您的mac狀態列顯示音樂控制功能(重啟後生效)',
|
||||||
|
fallbackParser: '備用解析服務 (GD音樂台)',
|
||||||
|
fallbackParserDesc: '當勾選「GD音樂台」且常規音源無法播放時,將使用此服務嘗試解析。',
|
||||||
|
parserGD: 'GD 音樂台 (內建)',
|
||||||
|
parserCustom: '自訂 API',
|
||||||
|
|
||||||
|
customApi: {
|
||||||
|
importConfig: '匯入 JSON 設定',
|
||||||
|
currentSource: '目前音源',
|
||||||
|
notImported: '尚未匯入自訂音源。',
|
||||||
|
importSuccess: '成功匯入音源:{name}',
|
||||||
|
importFailed: '匯入失敗:{message}',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
application: {
|
application: {
|
||||||
closeAction: '關閉行為',
|
closeAction: '關閉行為',
|
||||||
|
|||||||
@@ -275,6 +275,39 @@ export function initializeFileManager() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 处理导入自定义API插件的请求
|
||||||
|
ipcMain.handle('import-custom-api-plugin', async () => {
|
||||||
|
const result = await dialog.showOpenDialog({
|
||||||
|
title: '选择自定义音源配置文件',
|
||||||
|
filters: [{ name: 'JSON Files', extensions: ['json'] }],
|
||||||
|
properties: ['openFile']
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.canceled || result.filePaths.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = result.filePaths[0];
|
||||||
|
try {
|
||||||
|
const fileContent = fs.readFileSync(filePath, 'utf-8');
|
||||||
|
|
||||||
|
// 基础验证,确保它是个合法的JSON并且包含关键字段
|
||||||
|
const pluginData = JSON.parse(fileContent);
|
||||||
|
if (!pluginData.name || !pluginData.apiUrl) {
|
||||||
|
throw new Error('无效的插件文件,缺少 name 或 apiUrl 字段。');
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: pluginData.name,
|
||||||
|
content: fileContent // 返回完整的JSON字符串
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('读取或解析插件文件失败:', error);
|
||||||
|
// 向渲染进程抛出错误,以便UI可以显示提示
|
||||||
|
throw new Error(`文件读取或解析失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -28,4 +28,7 @@
|
|||||||
"contentZoomFactor": 1,
|
"contentZoomFactor": 1,
|
||||||
"autoTheme": false,
|
"autoTheme": false,
|
||||||
"manualTheme": "light"
|
"manualTheme": "light"
|
||||||
|
"manualTheme": "light",
|
||||||
|
"customApiPlugin": "",
|
||||||
|
"customApiPluginName": "",
|
||||||
}
|
}
|
||||||
|
|||||||
Vendored
+1
@@ -21,6 +21,7 @@ interface API {
|
|||||||
onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => void;
|
onDownloadComplete: (callback: (success: boolean, filePath: string) => void) => void;
|
||||||
onLanguageChanged: (callback: (locale: string) => void) => void;
|
onLanguageChanged: (callback: (locale: string) => void) => void;
|
||||||
removeDownloadListeners: () => void;
|
removeDownloadListeners: () => void;
|
||||||
|
importCustomApiPlugin: () => Promise<{ name: string; content: string } | null>;
|
||||||
invoke: (channel: string, ...args: any[]) => Promise<any>;
|
invoke: (channel: string, ...args: any[]) => Promise<any>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ const api = {
|
|||||||
sendSong: (data) => ipcRenderer.send('update-current-song', data),
|
sendSong: (data) => ipcRenderer.send('update-current-song', data),
|
||||||
unblockMusic: (id, data, enabledSources) =>
|
unblockMusic: (id, data, enabledSources) =>
|
||||||
ipcRenderer.invoke('unblock-music', id, data, enabledSources),
|
ipcRenderer.invoke('unblock-music', id, data, enabledSources),
|
||||||
|
importCustomApiPlugin: () => ipcRenderer.invoke('import-custom-api-plugin'),
|
||||||
// 歌词窗口关闭事件
|
// 歌词窗口关闭事件
|
||||||
onLyricWindowClosed: (callback: () => void) => {
|
onLyricWindowClosed: (callback: () => void) => {
|
||||||
ipcRenderer.on('lyric-window-closed', () => callback());
|
ipcRenderer.on('lyric-window-closed', () => callback());
|
||||||
@@ -54,7 +55,7 @@ const api = {
|
|||||||
return ipcRenderer.invoke(channel, ...args);
|
return ipcRenderer.invoke(channel, ...args);
|
||||||
}
|
}
|
||||||
return Promise.reject(new Error(`未授权的 IPC 通道: ${channel}`));
|
return Promise.reject(new Error(`未授权的 IPC 通道: ${channel}`));
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// 创建带类型的ipcRenderer对象,暴露给渲染进程
|
// 创建带类型的ipcRenderer对象,暴露给渲染进程
|
||||||
|
|||||||
+43
-23
@@ -10,6 +10,8 @@ import requestMusic from '@/utils/request_music';
|
|||||||
|
|
||||||
import { searchAndGetBilibiliAudioUrl } from './bilibili';
|
import { searchAndGetBilibiliAudioUrl } from './bilibili';
|
||||||
import { parseFromGDMusic } from './gdmusic';
|
import { parseFromGDMusic } from './gdmusic';
|
||||||
|
import { parseFromCustomApi } from './parseFromCustomApi';
|
||||||
|
import type { ParsedMusicResult } from './gdmusic';
|
||||||
|
|
||||||
const { addData, getData, deleteData } = musicDB;
|
const { addData, getData, deleteData } = musicDB;
|
||||||
|
|
||||||
@@ -114,7 +116,7 @@ const getBilibiliAudio = async (data: SongResult) => {
|
|||||||
* @param data 歌曲数据
|
* @param data 歌曲数据
|
||||||
* @returns 解析结果,失败时返回null
|
* @returns 解析结果,失败时返回null
|
||||||
*/
|
*/
|
||||||
const getGDMusicAudio = async (id: number, data: SongResult) => {
|
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) {
|
||||||
@@ -146,19 +148,19 @@ 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){
|
if (isElectron) {
|
||||||
const settingStore = useSettingsStore();
|
const settingStore = useSettingsStore();
|
||||||
|
|
||||||
// 如果禁用了音乐解析功能,则直接返回空结果
|
// 如果禁用了音乐解析功能,则直接返回空结果
|
||||||
if (!settingStore.setData.enableMusicUnblock) {
|
if (!settingStore.setData.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 = localStorage.getItem(`song_source_${songId}`);
|
||||||
let musicSources: any[] = [];
|
let musicSources: any[] = [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (savedSourceStr) {
|
if (savedSourceStr) {
|
||||||
// 使用自定义音源
|
// 使用自定义音源
|
||||||
@@ -168,37 +170,55 @@ export const getParsingMusicUrl = async (id: number, data: SongResult) => {
|
|||||||
// 使用全局音源设置
|
// 使用全局音源设置
|
||||||
musicSources = settingStore.setData.enabledMusicSources || [];
|
musicSources = settingStore.setData.enabledMusicSources || [];
|
||||||
console.log(`使用全局音源设置:`, musicSources);
|
console.log(`使用全局音源设置:`, musicSources);
|
||||||
if (musicSources.length > 0) {
|
|
||||||
return getUnblockMusicAudio(id, data, musicSources);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('解析音源设置失败,使用全局设置', e);
|
console.error('解析音源设置失败,回退到默认全局设置', e);
|
||||||
musicSources = settingStore.setData.enabledMusicSources || [];
|
musicSources = settingStore.setData.enabledMusicSources || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 按优先级解析
|
const quality = settingStore.setData.musicQuality || 'higher';
|
||||||
|
|
||||||
// 2.1 Bilibili解析(优先级最高)
|
// 优先级 1: 自定义 API
|
||||||
|
if (musicSources.includes('custom') && settingStore.setData.customApiPlugin) {
|
||||||
|
console.log('尝试使用 自定义API 解析...');
|
||||||
|
const customResult = await parseFromCustomApi(id, data, quality);
|
||||||
|
if (customResult) {
|
||||||
|
return customResult; // 成功则直接返回
|
||||||
|
}
|
||||||
|
console.log('自定义API解析失败,继续尝试其他音源...');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优先级 2: Bilibili
|
||||||
if (musicSources.includes('bilibili')) {
|
if (musicSources.includes('bilibili')) {
|
||||||
return await getBilibiliAudio(data);
|
console.log('尝试使用 Bilibili 解析...');
|
||||||
|
const bilibiliResult = await getBilibiliAudio(data);
|
||||||
|
if (bilibiliResult?.data?.data?.url) { // 检查返回的 URL 是否有效
|
||||||
|
return bilibiliResult;
|
||||||
|
}
|
||||||
|
console.log('Bilibili解析失败,继续尝试其他音源...');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2.2 GD音乐台解析
|
// 优先级 3: GD 音乐台
|
||||||
if (musicSources.includes('gdmusic')) {
|
if (musicSources.includes('gdmusic')) {
|
||||||
|
console.log('尝试使用 GD音乐台 解析...');
|
||||||
const gdResult = await getGDMusicAudio(id, data);
|
const gdResult = await getGDMusicAudio(id, data);
|
||||||
if (gdResult) return gdResult;
|
if (gdResult) {
|
||||||
// GD解析失败,继续下一步
|
return gdResult;
|
||||||
console.log('GD音乐台解析失败,尝试使用其他音源');
|
}
|
||||||
|
console.log('GD音乐台解析失败,继续尝试其他音源...');
|
||||||
}
|
}
|
||||||
console.log('musicSources', musicSources);
|
|
||||||
// 2.3 使用unblockMusic解析其他音源
|
// 优先级 4: UnblockMusic (migu, kugou, pyncmd)
|
||||||
if (musicSources.length > 0) {
|
const unblockSources = musicSources.filter(
|
||||||
return getUnblockMusicAudio(id, data, musicSources);
|
source => !['custom', 'bilibili', 'gdmusic'].includes(source)
|
||||||
|
);
|
||||||
|
if (unblockSources.length > 0) {
|
||||||
|
console.log('尝试使用 UnblockMusic 解析:', unblockSources);
|
||||||
|
return getUnblockMusicAudio(id, data, unblockSources);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 后备方案:使用API请求
|
// 后备方案:使用API请求
|
||||||
console.log('无可用音源或不在Electron环境中,使用API请求');
|
console.log('无可用音源或不在Electron环境中,使用API请求');
|
||||||
return requestMusic.get<any>('/music', { params: { id } });
|
return requestMusic.get<any>('/music', { params: { id } });
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,106 @@
|
|||||||
|
import axios from 'axios';
|
||||||
|
import {get} from 'lodash';
|
||||||
|
import {useSettingsStore} from '@/store';
|
||||||
|
|
||||||
|
// 从同级目录的 gdmusic.ts 导入类型,确保兼容性
|
||||||
|
import type {ParsedMusicResult} from './gdmusic';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 定义自定义API JSON插件的结构
|
||||||
|
*/
|
||||||
|
interface CustomApiPlugin {
|
||||||
|
name: string;
|
||||||
|
apiUrl: string;
|
||||||
|
method?: 'GET' | 'POST';
|
||||||
|
params: Record<string, string>;
|
||||||
|
qualityMapping?: Record<string, string>;
|
||||||
|
responseUrlPath: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从用户导入的自定义API JSON配置中解析音乐URL
|
||||||
|
*/
|
||||||
|
export const parseFromCustomApi = async (
|
||||||
|
id: number,
|
||||||
|
_songData: any,
|
||||||
|
quality: string = 'higher',
|
||||||
|
timeout: number = 10000
|
||||||
|
): Promise<ParsedMusicResult | null> => {
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const pluginString = settingsStore.setData.customApiPlugin;
|
||||||
|
|
||||||
|
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<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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -1,65 +1,87 @@
|
|||||||
<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>
|
||||||
|
|
||||||
<n-checkbox-group v-model:value="selectedSources">
|
<n-checkbox-group v-model:value="selectedSources">
|
||||||
<n-grid :cols="2" :x-gap="12" :y-gap="8">
|
<n-grid :cols="2" :x-gap="12" :y-gap="8">
|
||||||
<n-grid-item v-for="source in musicSourceOptions" :key="source.value">
|
<!-- 遍历常规音源 -->
|
||||||
|
<n-grid-item v-for="source in regularMusicSources" :key="source.value">
|
||||||
<n-checkbox :value="source.value">
|
<n-checkbox :value="source.value">
|
||||||
{{ source.label }}
|
{{ source.label }}
|
||||||
<template v-if="source.value === 'gdmusic'">
|
<n-tooltip v-if="source.value === 'gdmusic'">
|
||||||
<n-tooltip>
|
<template #trigger>
|
||||||
<template #trigger>
|
<n-icon size="16" class="ml-1 text-blue-500 cursor-help">
|
||||||
<n-icon size="16" class="ml-1 text-blue-500 cursor-help">
|
<i class="ri-information-line"></i>
|
||||||
<i class="ri-information-line"></i>
|
</n-icon>
|
||||||
</n-icon>
|
</template>
|
||||||
</template>
|
{{ t('settings.playback.gdmusicInfo') }}
|
||||||
{{ t('settings.playback.gdmusicInfo') }}
|
</n-tooltip>
|
||||||
</n-tooltip>
|
</n-checkbox>
|
||||||
</template>
|
</n-grid-item>
|
||||||
|
|
||||||
|
<!-- 单独处理自定义API选项 -->
|
||||||
|
<n-grid-item>
|
||||||
|
<n-checkbox value="custom" :disabled="!settingsStore.setData.customApiPlugin">
|
||||||
|
自定义 API
|
||||||
|
<n-tooltip v-if="!settingsStore.setData.customApiPlugin">
|
||||||
|
<template #trigger>
|
||||||
|
<n-icon size="16" class="ml-1 text-gray-400 cursor-help">
|
||||||
|
<i class="ri-question-line"></i>
|
||||||
|
</n-icon>
|
||||||
|
</template>
|
||||||
|
请先导入JSON配置文件才能启用
|
||||||
|
</n-tooltip>
|
||||||
</n-checkbox>
|
</n-checkbox>
|
||||||
</n-grid-item>
|
</n-grid-item>
|
||||||
</n-grid>
|
</n-grid>
|
||||||
</n-checkbox-group>
|
</n-checkbox-group>
|
||||||
<div v-if="selectedSources.length === 0" class="text-red-500 text-sm">
|
|
||||||
{{ t('settings.playback.musicSourcesWarning') }}
|
<!-- 分割线 -->
|
||||||
|
<div class="mt-4 border-t pt-4 border-gray-200 dark:border-gray-700"></div>
|
||||||
|
|
||||||
|
<!-- 自定义API导入区域 -->
|
||||||
|
<div>
|
||||||
|
<h3 class="text-base font-medium mb-2">自定义 API 设置</h3>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<n-button @click="importPlugin" size="small"> 导入 JSON 配置 </n-button>
|
||||||
|
<p v-if="settingsStore.setData.customApiPluginName" class="text-sm">
|
||||||
|
当前: <span class="font-semibold">{{ settingsStore.setData.customApiPluginName }}</span>
|
||||||
|
</p>
|
||||||
|
<p v-else class="text-sm text-gray-500">尚未导入</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- GD音乐台设置 -->
|
|
||||||
<div
|
|
||||||
v-if="selectedSources.includes('gdmusic')"
|
|
||||||
class="mt-4 border-t pt-4 border-gray-200 dark:border-gray-700"
|
|
||||||
>
|
|
||||||
<h3 class="text-base font-medium mb-2">GD音乐台(music.gdstudio.xyz)设置</h3>
|
|
||||||
<p class="text-sm text-gray-500 dark:text-gray-400 mb-2">
|
|
||||||
GD音乐台将自动尝试多个音乐平台进行解析,无需额外配置。优先级高于其他解析方式,但是请求可能较慢。感谢(music.gdstudio.xyz)
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</n-space>
|
</n-space>
|
||||||
</n-modal>
|
</n-modal>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { defineEmits, defineProps, ref, watch } from 'vue';
|
import { useMessage } from 'naive-ui';
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
import { useI18n } from 'vue-i18n';
|
import { useI18n } from 'vue-i18n';
|
||||||
|
import { useSettingsStore } from '@/store';
|
||||||
|
|
||||||
import { type Platform } from '@/types/music';
|
import { type Platform } from '@/types/music';
|
||||||
|
|
||||||
|
// 扩展 Platform 类型以包含 'custom'
|
||||||
|
type ExtendedPlatform = Platform | 'custom';
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
show: {
|
show: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
sources: {
|
sources: {
|
||||||
type: Array as () => Platform[],
|
type: Array as () => ExtendedPlatform[],
|
||||||
default: () => ['migu', 'kugou', 'pyncmd', 'bilibili']
|
default: () => ['migu', 'kugou', 'pyncmd', 'bilibili']
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -67,10 +89,13 @@ const props = defineProps({
|
|||||||
const emit = defineEmits(['update:show', 'update:sources']);
|
const emit = defineEmits(['update:show', 'update:sources']);
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const message = useMessage();
|
||||||
const visible = ref(props.show);
|
const visible = ref(props.show);
|
||||||
const selectedSources = ref<Platform[]>(props.sources);
|
const selectedSources = ref<ExtendedPlatform[]>(props.sources);
|
||||||
|
|
||||||
const musicSourceOptions = ref([
|
// 将常规音源和自定义音源分开定义
|
||||||
|
const regularMusicSources = ref([
|
||||||
{ label: 'MG', value: 'migu' },
|
{ label: 'MG', value: 'migu' },
|
||||||
{ label: 'KG', value: 'kugou' },
|
{ label: 'KG', value: 'kugou' },
|
||||||
{ label: 'pyncmd', value: 'pyncmd' },
|
{ label: 'pyncmd', value: 'pyncmd' },
|
||||||
@@ -78,44 +103,68 @@ const musicSourceOptions = ref([
|
|||||||
{ label: 'GD音乐台', value: 'gdmusic' }
|
{ label: 'GD音乐台', value: 'gdmusic' }
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const importPlugin = async () => {
|
||||||
|
try {
|
||||||
|
const result = await window.api.importCustomApiPlugin();
|
||||||
|
if (result && result.name && result.content) {
|
||||||
|
settingsStore.setCustomApiPlugin(result);
|
||||||
|
message.success(`成功导入音源: ${result.name}`);
|
||||||
|
// 导入成功后,如果用户还没勾选,则自动勾选上
|
||||||
|
if (!selectedSources.value.includes('custom')) {
|
||||||
|
selectedSources.value.push('custom');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
message.error(`导入失败: ${error.message}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听自定义插件内容的变化。如果用户清除了插件,要确保 'custom' 选项被取消勾选
|
||||||
|
watch(() => settingsStore.setData.customApiPlugin, (newPluginContent) => {
|
||||||
|
if (!newPluginContent) {
|
||||||
|
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;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = () => {
|
const handleCancel = () => {
|
||||||
// 取消时还原为props传入的初始值
|
// 取消时还原为props传入的初始值
|
||||||
selectedSources.value = [...props.sources];
|
selectedSources.value = [...props.sources];
|
||||||
visible.value = false;
|
visible.value = false;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -78,10 +78,14 @@ export const isBilibiliIdMatch = (id1: string | number, id2: string | number): b
|
|||||||
// 提取公共函数:获取B站视频URL
|
// 提取公共函数:获取B站视频URL
|
||||||
|
|
||||||
export const getSongUrl = async (
|
export const getSongUrl = async (
|
||||||
id: string | number,
|
id: string | number,
|
||||||
songData: SongResult,
|
songData: SongResult,
|
||||||
isDownloaded: boolean = false
|
isDownloaded: boolean = false
|
||||||
) => {
|
) => {
|
||||||
|
const numericId = typeof id === 'string' ? parseInt(id, 10) : id;
|
||||||
|
const settingsStore = useSettingsStore();
|
||||||
|
const { message } = createDiscreteApi(['message']); // 引入 message API 用于提示
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (songData.playMusicUrl) {
|
if (songData.playMusicUrl) {
|
||||||
return songData.playMusicUrl;
|
return songData.playMusicUrl;
|
||||||
@@ -92,8 +96,8 @@ export const getSongUrl = async (
|
|||||||
if (!songData.playMusicUrl && songData.bilibiliData.bvid && songData.bilibiliData.cid) {
|
if (!songData.playMusicUrl && songData.bilibiliData.bvid && songData.bilibiliData.cid) {
|
||||||
try {
|
try {
|
||||||
songData.playMusicUrl = await getBilibiliAudioUrl(
|
songData.playMusicUrl = await getBilibiliAudioUrl(
|
||||||
songData.bilibiliData.bvid,
|
songData.bilibiliData.bvid,
|
||||||
songData.bilibiliData.cid
|
songData.bilibiliData.cid
|
||||||
);
|
);
|
||||||
return songData.playMusicUrl;
|
return songData.playMusicUrl;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -104,14 +108,48 @@ export const getSongUrl = async (
|
|||||||
return songData.playMusicUrl || '';
|
return songData.playMusicUrl || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
const numericId = typeof id === 'string' ? parseInt(id, 10) : id;
|
|
||||||
|
|
||||||
// 检查是否有自定义音源设置
|
// ==================== 自定义API最优先 ====================
|
||||||
|
// 检查用户是否在全局设置中启用了 'custom' 音源
|
||||||
|
const globalSources = settingsStore.setData.enabledMusicSources || [];
|
||||||
|
const useCustomApiGlobally = globalSources.includes('custom');
|
||||||
|
|
||||||
|
// 检查歌曲是否有专属的 'custom' 音源设置
|
||||||
const songId = String(id);
|
const songId = String(id);
|
||||||
const savedSource = localStorage.getItem(`song_source_${songId}`);
|
const savedSourceStr = localStorage.getItem(`song_source_${songId}`);
|
||||||
|
let useCustomApiForSong = false;
|
||||||
|
if (savedSourceStr) {
|
||||||
|
try {
|
||||||
|
const songSources = JSON.parse(savedSourceStr);
|
||||||
|
useCustomApiForSong = songSources.includes('custom');
|
||||||
|
} catch (e) { /* ignore parsing error */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果全局或歌曲专属设置中启用了自定义API,则最优先尝试
|
||||||
|
if ( (useCustomApiGlobally || useCustomApiForSong) && settingsStore.setData.customApiPlugin) {
|
||||||
|
console.log(`优先级 1: 尝试使用自定义API解析歌曲 ${id}...`);
|
||||||
|
try {
|
||||||
|
// 直接从 api 目录导入 parseFromCustomApi 函数
|
||||||
|
const { parseFromCustomApi } = await import('@/api/parseFromCustomApi');
|
||||||
|
const customResult = await parseFromCustomApi(numericId, cloneDeep(songData), settingsStore.setData.musicQuality || 'higher');
|
||||||
|
|
||||||
|
if (customResult && customResult.data && customResult.data.data && customResult.data.data.url) {
|
||||||
|
console.log('自定义API解析成功!');
|
||||||
|
if (isDownloaded) return customResult.data.data as any;
|
||||||
|
return customResult.data.data.url;
|
||||||
|
} else {
|
||||||
|
// 自定义API失败,给出提示,然后继续走默认流程
|
||||||
|
console.log('自定义API解析失败,将使用默认降级流程...');
|
||||||
|
message.warning('自定义API解析失败,正在尝试使用内置音源...'); // 给用户一个提示
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('调用自定义API时发生错误:', error);
|
||||||
|
message.error('自定义API请求出错,正在尝试使用内置音源...');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果自定义API失败或未启用,则执行【原有】的解析流程
|
||||||
// 如果有自定义音源设置,直接使用getParsingMusicUrl获取URL
|
// 如果有自定义音源设置,直接使用getParsingMusicUrl获取URL
|
||||||
if (savedSource && songData.source !== 'bilibili') {
|
if (savedSourceStr && songData.source !== 'bilibili') {
|
||||||
try {
|
try {
|
||||||
console.log(`使用自定义音源解析歌曲 ID: ${songId}`);
|
console.log(`使用自定义音源解析歌曲 ID: ${songId}`);
|
||||||
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
|
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
|
||||||
@@ -129,28 +167,33 @@ export const getSongUrl = async (
|
|||||||
|
|
||||||
// 正常获取URL流程
|
// 正常获取URL流程
|
||||||
const { data } = await getMusicUrl(numericId, isDownloaded);
|
const { data } = await getMusicUrl(numericId, isDownloaded);
|
||||||
let url = '';
|
if (data && data.data && data.data[0]) {
|
||||||
let songDetail = null;
|
const songDetail = data.data[0];
|
||||||
try {
|
const hasNoUrl = !songDetail.url;
|
||||||
if (data.data[0].freeTrialInfo || !data.data[0].url) {
|
const isTrial = !!songDetail.freeTrialInfo;
|
||||||
|
|
||||||
|
if (hasNoUrl || isTrial) {
|
||||||
|
console.log(`官方URL无效 (无URL: ${hasNoUrl}, 试听: ${isTrial}),进入内置备用解析...`);
|
||||||
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
|
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
|
||||||
url = res.data.data.url;
|
if (isDownloaded) return res?.data?.data as any;
|
||||||
songDetail = res.data.data;
|
return res?.data?.data?.url || null;
|
||||||
} else {
|
|
||||||
songDetail = data.data[0] as any;
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
|
||||||
console.error('error', error);
|
console.log('官方API解析成功!');
|
||||||
url = data.data[0].url || '';
|
if (isDownloaded) return songDetail as any;
|
||||||
|
return songDetail.url;
|
||||||
}
|
}
|
||||||
if (isDownloaded) {
|
|
||||||
return songDetail;
|
console.log('官方API返回数据结构异常,进入内置备用解析...');
|
||||||
}
|
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
|
||||||
url = url || data.data[0].url;
|
if (isDownloaded) return res?.data?.data as any;
|
||||||
return url;
|
return res?.data?.data?.url || null;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('error', error);
|
console.error('官方API请求失败,进入内置备用解析流程:', error);
|
||||||
return null;
|
const res = await getParsingMusicUrl(numericId, cloneDeep(songData));
|
||||||
|
if (isDownloaded) return res?.data?.data as any;
|
||||||
|
return res?.data?.data?.url || null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -64,6 +64,17 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
// 初始化 setData
|
// 初始化 setData
|
||||||
setData.value = getInitialSettings();
|
setData.value = getInitialSettings();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存导入的自定义API插件
|
||||||
|
* @param plugin 包含name和content的对象
|
||||||
|
*/
|
||||||
|
const setCustomApiPlugin = (plugin: { name: string; content: string }) => {
|
||||||
|
setSetData({
|
||||||
|
customApiPlugin: plugin.content,
|
||||||
|
customApiPluginName: plugin.name
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const toggleTheme = () => {
|
const toggleTheme = () => {
|
||||||
if (setData.value.autoTheme) {
|
if (setData.value.autoTheme) {
|
||||||
// 如果是自动模式,切换到手动模式并设置相反的主题
|
// 如果是自动模式,切换到手动模式并设置相反的主题
|
||||||
@@ -209,5 +220,7 @@ export const useSettingsStore = defineStore('settings', () => {
|
|||||||
initializeSettings,
|
initializeSettings,
|
||||||
initializeTheme,
|
initializeTheme,
|
||||||
initializeSystemFonts
|
initializeSystemFonts
|
||||||
|
initializeSystemFonts,
|
||||||
|
setCustomApiPlugin,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user