fix(subscribe): preserve source identity across season flows

This commit is contained in:
jxxghp
2026-07-25 08:51:30 +08:00
parent 0ee9e466c3
commit b3e596c761
8 changed files with 217 additions and 36 deletions
@@ -131,6 +131,8 @@ function episodeGroupItemProps(item: { title: string; subtitle: string }) {
// 查询所有剧集组
async function getEpisodeGroups() {
// 兼容未记录主来源的旧 TMDB 订阅;明确为其他来源时不使用辅助 TMDB ID 查询剧集组。
if (subscribeForm.value.media_source && subscribeForm.value.media_source !== 'themoviedb') return
if (!subscribeForm.value.tmdbid) {
console.warn('tmdbid is not set or is empty')
return
+12 -12
View File
@@ -6,7 +6,12 @@ import noImage from '@images/no-image.jpeg'
import NoDataFound from '@/components/states/NoDataFound.vue'
import { useI18n } from 'vue-i18n'
import { useGlobalSettingsStore } from '@/stores'
import type { SeasonSubscribeModes, SubscribeMode } from '@/composables/useMediaSubscribe'
import {
getMediaSubscribeId,
getMediaSubscribeIdentity,
type SeasonSubscribeModes,
type SubscribeMode,
} from '@/composables/useMediaSubscribe'
import { getDisplayImageUrl } from '@/utils/imageUtils'
import { useDisplay } from 'vuetify'
@@ -67,7 +72,9 @@ const isRefreshed = ref(false)
const episodeGroups = ref<{ [key: string]: any }[]>([])
// 当前选择剧集组
const episodeGroup = ref(props.initialEpisodeGroup ?? '')
const episodeGroup = ref(
getMediaSubscribeIdentity(props.media)?.source === 'themoviedb' ? (props.initialEpisodeGroup ?? '') : '',
)
// 剧集组横向轨道
const episodeGroupRail = ref<HTMLElement | null>(null)
@@ -166,19 +173,12 @@ const episodeGroupOptions = computed<EpisodeGroupOption[]>(() => {
// 获得mediaid
function getMediaId() {
if (props.media?.media_id && (props.media?.source || props.media?.mediaid_prefix)) {
const source = props.media.mediaid_prefix || props.media.source
return `${source === 'themoviedb' ? 'tmdb' : source}:${props.media.media_id}`
}
if (props.media?.tmdb_id) return `tmdb:${props.media?.tmdb_id}`
else if (props.media?.douban_id) return `douban:${props.media?.douban_id}`
else if (props.media?.bangumi_id) return `bangumi:${props.media?.bangumi_id}`
else if (props.media?.anilist_id) return `anilist:${props.media?.anilist_id}`
return ''
return getMediaSubscribeId(props.media)
}
// 查询所有剧集组
async function getEpisodeGroups() {
if (getMediaSubscribeIdentity(props.media)?.source !== 'themoviedb') return
if (!props.media?.tmdb_id) {
console.warn('tmdbid is not set or is empty')
return
@@ -213,7 +213,7 @@ async function getMediaSeasons() {
// 查询剧集组的剧集
async function getGroupSeasons() {
if (!episodeGroup.value) return
if (getMediaSubscribeIdentity(props.media)?.source !== 'themoviedb' || !episodeGroup.value) return
isRefreshed.value = false
try {
seasonInfos.value = await api.get(`media/group/seasons/${episodeGroup.value}`)
@@ -128,6 +128,27 @@ describe('SubscribeEditDialog', () => {
expect(episodeGroupsRequested).not.toHaveBeenCalled()
})
it('skips episode groups for a non-TMDB subscription with an auxiliary TMDB ID', async () => {
const record = createSubscribe({
anilistid: 154587,
id: 810,
media_id: '154587',
media_source: 'anilist',
name: 'AniList 编辑测试剧',
season: 1,
tmdbid: 8100,
type: '电视剧',
})
const episodeGroupsRequested = vi.fn()
server.use(subscribeDetailsHandler(810, record))
useDialogOptions({ onEpisodeGroups: episodeGroupsRequested, tmdbId: 8100 })
await renderDialog({ subid: 810 })
expect(await screen.findByText('AniList 编辑测试剧 S01')).toBeInTheDocument()
expect(episodeGroupsRequested).not.toHaveBeenCalled()
})
it('shows enabled sites and stable downloader, directory, and rule options', async () => {
const activeSite = createSubscribeSite({ id: 1, is_active: true, name: '启用站点' })
const inactiveSite = createSubscribeSite({ id: 2, is_active: false, name: '停用站点' })
@@ -202,7 +223,12 @@ describe('SubscribeEditDialog', () => {
const configRequested = vi.fn()
const saved = vi.fn()
server.use(
defaultSubscribeConfigHandler(type, createSubscribe({ id: 0, show_edit_dialog: false, type }), 200, configRequested),
defaultSubscribeConfigHandler(
type,
createSubscribe({ id: 0, show_edit_dialog: false, type }),
200,
configRequested,
),
saveDefaultSubscribeConfigHandler(type, { success: true }, 200, saved),
)
useDialogOptions()
@@ -189,9 +189,13 @@ describe('SubscribeSeasonDialog', () => {
})
it.each([
['Douban', { douban_id: 'db-7303', tmdb_id: undefined }, 'douban:db-7303'],
['Bangumi', { bangumi_id: 'bgm-7304', douban_id: undefined, tmdb_id: undefined }, 'bangumi:bgm-7304'],
['AniList', { anilist_id: 154587, bangumi_id: undefined, tmdb_id: undefined }, 'anilist:154587'],
['Douban', { douban_id: 'db-7303', source: 'douban', tmdb_id: undefined }, 'douban:db-7303'],
[
'Bangumi',
{ bangumi_id: 'bgm-7304', douban_id: undefined, source: 'bangumi', tmdb_id: undefined },
'bangumi:bgm-7304',
],
['AniList', { anilist_id: 154587, bangumi_id: undefined, source: 'anilist', tmdb_id: undefined }, 'anilist:154587'],
[
'custom source',
{
@@ -231,6 +235,34 @@ describe('SubscribeSeasonDialog', () => {
expect(requested.mock.calls[0][0].searchParams.get('mediaid')).toBe(mediaId)
})
it.each([
['Douban', { douban_id: 'db-7310', source: 'douban' }, 'douban:db-7310'],
['Bangumi', { bangumi_id: 'bgm-7310', source: 'bangumi' }, 'bangumi:bgm-7310'],
['AniList', { anilist_id: 154587, source: 'anilist' }, 'anilist:154587'],
] as const)(
'keeps the %s identity and skips episode groups when an auxiliary TMDB ID exists',
async (_label, overrides, mediaId) => {
const media = createTvMedia({ ...overrides, tmdb_id: 7310 })
const seasonRequest = vi.fn()
const groupRequest = vi.fn()
const groupSeasonsRequest = vi.fn()
server.use(
mediaSeasonsHandler([createMediaSeason({ season_number: 1 })], 200, seasonRequest),
mediaNotExistsHandler([]),
mediaEpisodeGroupsHandler(7310, [], 200, groupRequest),
mediaGroupSeasonsHandler('auxiliary-group', [], 200, groupSeasonsRequest),
)
await renderDialog({ initialEpisodeGroup: 'auxiliary-group', media })
expect(await screen.findByText('第 1 季')).toBeInTheDocument()
await settleRequests()
expect(seasonRequest.mock.calls[0][0].searchParams.get('mediaid')).toBe(mediaId)
expect(groupRequest).not.toHaveBeenCalled()
expect(groupSeasonsRequest).not.toHaveBeenCalled()
},
)
it('synchronizes visible selections and modes, then emits the five-argument subscription payload', async () => {
const media = createTvMedia({ tmdb_id: 7306 })
const seasons = [