diff --git a/src/api/tmdb.ts b/src/api/tmdb.ts
index 6eb5d6b..36f2f19 100644
--- a/src/api/tmdb.ts
+++ b/src/api/tmdb.ts
@@ -461,12 +461,22 @@ export interface TmdbSearchMultiResult extends TmdbMovie {
known_for?: any[]
}
-/** TMDB discover result - union of movie and tv with optional media_type. */
-export type TmdbDiscoverItem =
- | TmdbMovie
- | TmdbTvShow
- | (TmdbMovie & { media_type: string })
- | (TmdbTvShow & { media_type: string })
+/** TMDB discover result - permissive interface for movie / tv / multi. */
+export interface TmdbDiscoverItem {
+ id: number
+ title?: string
+ name?: string
+ overview?: string
+ poster_path?: string | null
+ backdrop_path?: string | null
+ release_date?: string
+ first_air_date?: string
+ vote_average?: number
+ vote_count?: number
+ adult?: boolean
+ original_language?: string
+ media_type?: string
+}
/* ────────────────────────────────────────────────────────────── */
/* Fetcher */
diff --git a/src/components/detail/DetailHero.tsx b/src/components/detail/DetailHero.tsx
index c0e6883..da38715 100644
--- a/src/components/detail/DetailHero.tsx
+++ b/src/components/detail/DetailHero.tsx
@@ -277,7 +277,7 @@ export default function DetailHero({
)}
diff --git a/src/components/detail/DetailMainSections.tsx b/src/components/detail/DetailMainSections.tsx
index aa2e0b6..2920be3 100644
--- a/src/components/detail/DetailMainSections.tsx
+++ b/src/components/detail/DetailMainSections.tsx
@@ -10,6 +10,7 @@ import type {
TmdbKeyword,
TmdbReview,
TmdbVideo,
+ TmdbCastMember,
} from '../../api/tmdb'
import type { CinemetaMeta } from '../../api/cinemeta'
import type { TvmazeShow } from '../../api/tvmaze'
@@ -66,8 +67,8 @@ interface Props {
wikiTitle: string | null
region: string
watchProviders: TmdbWatchProviders | null | undefined
- cast: unknown[]
- crew: unknown[]
+ cast: TmdbCastMember[]
+ crew: TmdbCastMember[]
keywords: TmdbKeyword[]
reviews: TmdbReview[]
videos: TmdbVideo[] | undefined
@@ -76,7 +77,7 @@ interface Props {
libraryMap: Map | undefined
overview: string
overviewSource: string | null
- sources: { Id?: string }[]
+ sources: { Id?: string | null }[]
activeSourceId: string | null
onSourceChange: (id: string | null) => void
}
diff --git a/src/components/detail/VersionsSelector.tsx b/src/components/detail/VersionsSelector.tsx
index ee92bb2..fea7930 100644
--- a/src/components/detail/VersionsSelector.tsx
+++ b/src/components/detail/VersionsSelector.tsx
@@ -22,7 +22,7 @@ export default function VersionsSelector({ item, selectedSourceId, onChange }: P
{sources.map(src => {
const summary = summarizeSource(src)
- const id = src.Id || src.id || ''
+ const id = src.Id || ''
const active = id === selectedSourceId
return (
diff --git a/src/hooks/use-prebuffer.ts b/src/hooks/use-prebuffer.ts
index be29e87..230e701 100644
--- a/src/hooks/use-prebuffer.ts
+++ b/src/hooks/use-prebuffer.ts
@@ -41,7 +41,7 @@ export function usePrebuffer(item: BaseItemDto | null | undefined, armed: boolea
playbackInfoDto: {
UserId: jellyfinClient.getAuthState()!.userId,
MaxStreamingBitrate: 140_000_000,
- DeviceProfile: browserDeviceProfile(),
+ DeviceProfile: browserDeviceProfile() as any,
AutoOpenLiveStream: true,
EnableDirectPlay: true,
EnableDirectStream: true,
diff --git a/src/lib/downloads.ts b/src/lib/downloads.ts
index 570da87..026bc06 100644
--- a/src/lib/downloads.ts
+++ b/src/lib/downloads.ts
@@ -49,7 +49,7 @@ export async function startDownload(args: {
}
}
- const blob = new Blob(chunks)
+ const blob = new Blob(chunks as BlobPart[])
const objectUrl = URL.createObjectURL(blob)
store.update(dl.id, {
status: 'done',
diff --git a/src/lib/trakt.ts b/src/lib/trakt.ts
index 44bb1ae..ecd4ccf 100644
--- a/src/lib/trakt.ts
+++ b/src/lib/trakt.ts
@@ -250,7 +250,9 @@ export async function addToTraktWatchlist(item: BaseItemDto): Promise {
if (!token) return false
const body = await buildScrobbleBody(item, 0)
if (!body) return false
- const payload = item.Type === 'Movie' ? { movies: [{ ids: body.movie.ids }] } : { shows: [{ ids: body.show.ids }] }
+ const payload = item.Type === 'Movie'
+ ? { movies: [{ ids: (body as { movie: { ids: Record } }).movie.ids }] }
+ : { shows: [{ ids: (body as { show: { ids: Record } }).show.ids }] }
try {
const res = await fetch(`${BASE}/sync/watchlist`, {
method: 'POST',
@@ -269,7 +271,9 @@ export async function removeFromTraktWatchlist(item: BaseItemDto): Promise } }).movie.ids }] }
+ : { shows: [{ ids: (body as { show: { ids: Record } }).show.ids }] }
try {
const res = await fetch(`${BASE}/sync/watchlist/remove`, {
method: 'POST',
diff --git a/src/pages/PersonPage.tsx b/src/pages/PersonPage.tsx
index ec5c68e..3a7b822 100644
--- a/src/pages/PersonPage.tsx
+++ b/src/pages/PersonPage.tsx
@@ -28,7 +28,10 @@ export default function PersonPage() {
const filmography = useMemo(() => {
const cast = (person?.combined_credits?.cast || []).map(c => ({ ...c, _kind: 'cast' as const }))
const crew = (person?.combined_credits?.crew || []).map(c => ({ ...c, _kind: 'crew' as const }))
- let merged: ((TmdbCombinedCreditCast | TmdbCombinedCreditCrew) & { _kind: 'cast' | 'crew' })[] = [
+ type MergedCredit =
+ | (TmdbCombinedCreditCast & { _kind: 'cast' })
+ | (TmdbCombinedCreditCrew & { _kind: 'crew' })
+ let merged: MergedCredit[] = [
...cast,
...crew,
]
@@ -37,7 +40,7 @@ export default function PersonPage() {
if (filter === 'Acting') {
merged = merged.filter(c => c._kind === 'cast')
} else {
- merged = merged.filter(c => c._kind === 'crew' && c.department === filter)
+ merged = merged.filter((c): c is MergedCredit & { _kind: 'crew' } => c._kind === 'crew' && c.department === filter)
}
}
@@ -260,7 +263,7 @@ export default function PersonPage() {
{title}
- {c.character ? c.character : c.job}
+ {c.character ? c.character : (c as TmdbCombinedCreditCrew).job}
{year && (
<>
·
diff --git a/src/pages/PlayerPage.tsx b/src/pages/PlayerPage.tsx
index 96c5984..e164416 100644
--- a/src/pages/PlayerPage.tsx
+++ b/src/pages/PlayerPage.tsx
@@ -345,7 +345,7 @@ export default function PlayerPage() {
const fromQueue = searchParams.get('resume') === 'true'
if (showResumePromptPref && !fromQueue && pos > threshold) {
setResumePromptOpen(true)
- resumePromptShownRef.current = item.Id
+ resumePromptShownRef.current = item.Id ?? null
}
}, [item?.Id, searchParams, showResumePromptPref])
@@ -986,7 +986,8 @@ export default function PlayerPage() {
if (resolvedSource?.SupportsDirectPlay) {
const el = (playerRef.current as { el?: HTMLElement } | null)?.el
const video = el?.querySelector('video') as HTMLVideoElement | null
- const native = video.audioTracks as { length: number; [i: number]: { enabled: boolean } } | undefined
+ if (!video) return
+ const native = video.audioTracks as { length: number; [i: number]: { enabled: boolean; language?: string } } | undefined
if (native && native.length > 1) {
const target = audioTracks.find(t => t.Index === jfIndex)
const targetLang = (target?.Language || '').toLowerCase()
diff --git a/src/pages/TmdbDetailPage.tsx b/src/pages/TmdbDetailPage.tsx
index b5231a6..83b852b 100644
--- a/src/pages/TmdbDetailPage.tsx
+++ b/src/pages/TmdbDetailPage.tsx
@@ -8,6 +8,7 @@ import { useTmdbDetailEnrichment } from '../hooks/use-tmdb-detail'
import { useLibraryByTmdbId } from '../hooks/use-jellyfin'
import { useFanartMovie, useFanartTv } from '../hooks/use-external'
import { getTmdbImageUrl, pickTmdbLogo } from '../api/tmdb'
+import type { TmdbMovie, TmdbTvShow } from '../api/tmdb'
import { pickBestFanartImage } from '../api/fanart'
import { mapTmdbToJf } from '../lib/tmdb-mapping'
import ContentRow from '../components/ui/ContentRow'
@@ -262,7 +263,7 @@ export default function TmdbDetailPage({ tmdbId, kind }: Props) {
>
)}
- {kind === 'tv' && data.number_of_seasons > 0 && (
+ {kind === 'tv' && (data.number_of_seasons ?? 0) > 0 && (
<>
@@ -287,7 +288,7 @@ export default function TmdbDetailPage({ tmdbId, kind }: Props) {
)}
-
+
{matchedLocal && (