diff --git a/src/App.vue b/src/App.vue index fd75d99b..916176a7 100644 --- a/src/App.vue +++ b/src/App.vue @@ -32,6 +32,7 @@ import { normalizeThemeMaterialAccent } from '@/utils/glassColor' import { configureApexChartsTheme } from '@/utils/apexCharts' import { useGlobalOfflineStatus, type ConnectionFailureReason } from '@/composables/useOfflineStatus' import { useSystemRestartStatus } from '@/composables/useSystemRestart' +import { loadMediaSources } from '@/composables/useMediaSources' import { useAppActivityLifecycle } from '@/composables/useAppActivityLifecycle' import { useGlassWallpaperTransaction } from '@/composables/useGlassWallpaperTransaction' import { @@ -913,6 +914,7 @@ async function initializeAuthenticatedState() { globalLoadingStateManager.setLoadingState('global-settings', true) await globalSettingsStore.initialize() await globalSettingsStore.loadUserSettings() + await loadMediaSources() } finally { globalLoadingStateManager.setLoadingState('global-settings', false) } diff --git a/src/api/types.ts b/src/api/types.ts index 58e352e2..a2d88ee4 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -17,6 +17,13 @@ export enum MediaSource { export type MediaDataSource = `${MediaSource}` | (string & {}) +/** 后端返回的媒体来源注册描述,供各类来源选择器共享。 */ +export interface MediaSourceInfo { + name: string + media_source: MediaDataSource + media_types: string[] +} + // 手动刮削选项 export interface ManualScrapeOptions { // 媒体数据源 diff --git a/src/components/dialog/AddSubtitleDownloadDialog.vue b/src/components/dialog/AddSubtitleDownloadDialog.vue index 5b32e050..74d40f79 100644 --- a/src/components/dialog/AddSubtitleDownloadDialog.vue +++ b/src/components/dialog/AddSubtitleDownloadDialog.vue @@ -8,6 +8,7 @@ import { useI18n } from 'vue-i18n' import MediaIdSelector from '../misc/MediaIdSelector.vue' import { useGlobalSettingsStore } from '@/stores' import { isMediaDataSource, isValidMediaSourceId } from '@/utils/mediaId' +import { useMediaSources } from '@/composables/useMediaSources' // 多语言支持 const { t } = useI18n() @@ -58,6 +59,8 @@ const normalizedMediaId = computed(() => selectedMediaId.value?.trim() || undefi const hasValidMediaIdentity = computed( () => Boolean(normalizedMediaId.value) && isValidMediaSourceId(normalizedMediaId.value, mediaSource.value), ) +const { mediaSourceItems: getMediaSourceItems } = useMediaSources() +const customMediaSourceItems = getMediaSourceItems('media') const mediaSourceItems = computed<{ title: string; value: MediaDataSource }[]>(() => { const labels: Partial> = { @@ -75,7 +78,10 @@ const mediaSourceItems = computed<{ title: string; value: MediaDataSource }[]>(( migu: 'Migu Video', tencentvideodiscover: 'Tencent Video', } - return Object.values(MediaSource).map(value => ({ title: labels[value] ?? value, value })) + return [ + ...Object.values(MediaSource).map(value => ({ title: labels[value] ?? value, value })), + ...customMediaSourceItems.value, + ] }) // 当前数据源对应的原生ID标签。 diff --git a/src/components/dialog/CacheReidentifyDialog.vue b/src/components/dialog/CacheReidentifyDialog.vue index 781b939a..15f88110 100644 --- a/src/components/dialog/CacheReidentifyDialog.vue +++ b/src/components/dialog/CacheReidentifyDialog.vue @@ -2,6 +2,7 @@ import { useI18n } from 'vue-i18n' import type { MediaDataSource, MusicEntityType } from '@/api/types' import { isMediaDataSource, isMusicMediaSource } from '@/utils/mediaId' +import { useMediaSources } from '@/composables/useMediaSources' const { t } = useI18n() @@ -41,6 +42,9 @@ const mediaSource = ref( const mediaId = ref() const musicType = ref>(props.musicType) const isMusicSelection = computed(() => isMusicMediaSource(mediaSource.value)) +const { mediaSourceItems: getMediaSourceItems } = useMediaSources() +const customMediaSourceItems = getMediaSourceItems('media') +const customMusicSourceItems = getMediaSourceItems('music') const musicEntityItems = computed(() => [ { title: t('setting.cache.musicType.recording'), value: 'recording' }, { title: t('setting.cache.musicType.album'), value: 'album' }, @@ -53,6 +57,8 @@ const mediaSourceItems = computed<{ title: string; value: MediaDataSource }[]>(( { title: t('setting.cache.recognitionSource.musicbrainz'), value: 'musicbrainz' }, { title: t('setting.cache.recognitionSource.theaudiodb'), value: 'theaudiodb' }, { title: t('setting.cache.recognitionSource.doubanmusic'), value: 'doubanmusic' }, + ...customMediaSourceItems.value, + ...customMusicSourceItems.value, ]) const mediaIdLabel = computed(() => { diff --git a/src/components/dialog/ReorganizeDialog.vue b/src/components/dialog/ReorganizeDialog.vue index bf37732a..830a6535 100644 --- a/src/components/dialog/ReorganizeDialog.vue +++ b/src/components/dialog/ReorganizeDialog.vue @@ -24,6 +24,7 @@ import { useI18n } from 'vue-i18n' import { useDisplay } from 'vuetify' import { useGlobalSettingsStore } from '@/stores' import { isMusicMediaSource, isValidMediaSourceId } from '@/utils/mediaId' +import { useMediaSources } from '@/composables/useMediaSources' // 国际化 const { t } = useI18n() @@ -44,6 +45,10 @@ const props = defineProps({ const globalSettingsStore = useGlobalSettingsStore() const globalSettings = globalSettingsStore.globalSettings +const { mediaSourceItems: getMediaSourceItems } = useMediaSources() +const customMediaSourceItems = getMediaSourceItems('media') +const customMusicSourceItems = getMediaSourceItems('music') + const mediaSourceItems = computed<{ title: string; value: MediaDataSource | null }[]>(() => [ { title: t('dialog.reorganize.auto'), value: null }, { title: t('setting.cache.recognitionSource.themoviedb'), value: 'themoviedb' }, @@ -53,6 +58,8 @@ const mediaSourceItems = computed<{ title: string; value: MediaDataSource | null { title: t('setting.cache.recognitionSource.musicbrainz'), value: 'musicbrainz' }, { title: t('setting.cache.recognitionSource.theaudiodb'), value: 'theaudiodb' }, { title: t('setting.cache.recognitionSource.doubanmusic'), value: 'doubanmusic' }, + ...customMediaSourceItems.value, + ...customMusicSourceItems.value, ]) /** 获取后台设置中的默认识别数据源,未知值兼容回退到 TheMovieDb。 */ diff --git a/src/components/dialog/ScrapeDialog.vue b/src/components/dialog/ScrapeDialog.vue index 3eae8d95..f233a6f9 100644 --- a/src/components/dialog/ScrapeDialog.vue +++ b/src/components/dialog/ScrapeDialog.vue @@ -4,6 +4,7 @@ import { useGlobalSettingsStore } from '@/stores' import { useI18n } from 'vue-i18n' import MediaIdSelector from '../misc/MediaIdSelector.vue' import { isMusicMediaSource, isValidMediaSourceId } from '@/utils/mediaId' +import { useMediaSources } from '@/composables/useMediaSources' const { t } = useI18n() @@ -24,6 +25,10 @@ const emit = defineEmits<{ (event: 'update:modelValue', value: boolean): void }>() +const { mediaSourceItems: getMediaSourceItems } = useMediaSources() +const customMediaSourceItems = getMediaSourceItems('media') +const customMusicSourceItems = getMediaSourceItems('music') + const mediaSourceItems = computed<{ title: string; value: MediaDataSource }[]>(() => [ { title: t('setting.cache.recognitionSource.themoviedb'), value: 'themoviedb' }, { title: t('setting.cache.recognitionSource.douban'), value: 'douban' }, @@ -32,6 +37,8 @@ const mediaSourceItems = computed<{ title: string; value: MediaDataSource }[]>(( { title: t('setting.cache.recognitionSource.musicbrainz'), value: 'musicbrainz' }, { title: t('setting.cache.recognitionSource.theaudiodb'), value: 'theaudiodb' }, { title: t('setting.cache.recognitionSource.doubanmusic'), value: 'doubanmusic' }, + ...customMediaSourceItems.value, + ...customMusicSourceItems.value, ]) const globalSettingsStore = useGlobalSettingsStore() diff --git a/src/components/dialog/SearchBarDialog.vue b/src/components/dialog/SearchBarDialog.vue index bf955229..2d7d3fbc 100644 --- a/src/components/dialog/SearchBarDialog.vue +++ b/src/components/dialog/SearchBarDialog.vue @@ -1,6 +1,6 @@