mirror of
https://github.com/yattee/yattee.git
synced 2026-02-21 10:19:46 +00:00
Fix Piped relatedStreams decoding crash on missing fields
Malformed items in relatedStreams (e.g., missing title) no longer crash the entire JSON decode. Reuses the existing PipedVideoItem (renamed from PipedPlaylistItem) graceful-decoding wrapper for all relatedStreams arrays in PipedStreamResponse, PipedChannelResponse, and PipedNextPageResponse.
This commit is contained in:
@@ -102,7 +102,10 @@ actor PipedAPI: InstanceAPI {
|
|||||||
let endpoint = GenericEndpoint.get("/nextpage/channel/\(id)", query: ["nextpage": continuation])
|
let endpoint = GenericEndpoint.get("/nextpage/channel/\(id)", query: ["nextpage": continuation])
|
||||||
let response: PipedNextPageResponse = try await httpClient.fetch(endpoint, baseURL: instance.url)
|
let response: PipedNextPageResponse = try await httpClient.fetch(endpoint, baseURL: instance.url)
|
||||||
return ChannelVideosPage(
|
return ChannelVideosPage(
|
||||||
videos: response.relatedStreams.map { $0.toVideo(instanceURL: instance.url) },
|
videos: response.relatedStreams.compactMap { item in
|
||||||
|
if case .video(let video) = item { return video.toVideo(instanceURL: instance.url) }
|
||||||
|
return nil
|
||||||
|
},
|
||||||
continuation: response.nextpage
|
continuation: response.nextpage
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -113,7 +116,10 @@ actor PipedAPI: InstanceAPI {
|
|||||||
channelTabsCache[id] = tabs
|
channelTabsCache[id] = tabs
|
||||||
}
|
}
|
||||||
return ChannelVideosPage(
|
return ChannelVideosPage(
|
||||||
videos: response.relatedStreams?.map { $0.toVideo(instanceURL: instance.url) } ?? [],
|
videos: response.relatedStreams?.compactMap { item in
|
||||||
|
if case .video(let video) = item { return video.toVideo(instanceURL: instance.url) }
|
||||||
|
return nil
|
||||||
|
} ?? [],
|
||||||
continuation: response.nextpage
|
continuation: response.nextpage
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -557,7 +563,7 @@ private struct PipedStreamResponse: Decodable, Sendable {
|
|||||||
let livestream: Bool?
|
let livestream: Bool?
|
||||||
let videoStreams: [PipedVideoStream]?
|
let videoStreams: [PipedVideoStream]?
|
||||||
let audioStreams: [PipedAudioStream]?
|
let audioStreams: [PipedAudioStream]?
|
||||||
let relatedStreams: [PipedVideo]?
|
let relatedStreams: [PipedVideoItem]?
|
||||||
|
|
||||||
var videoId: String? {
|
var videoId: String? {
|
||||||
// Extract from thumbnail URL as fallback
|
// Extract from thumbnail URL as fallback
|
||||||
@@ -573,8 +579,13 @@ private struct PipedStreamResponse: Decodable, Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nonisolated func toVideo(instanceURL: URL, videoId: String? = nil) -> Video {
|
nonisolated func toVideo(instanceURL: URL, videoId: String? = nil) -> Video {
|
||||||
// Convert related streams, limiting to 12
|
// Convert related streams, skipping malformed items, limiting to 12
|
||||||
let related: [Video]? = relatedStreams?.prefix(12).map { $0.toVideo(instanceURL: instanceURL) }
|
let related: [Video]? = relatedStreams?.compactMap { item -> Video? in
|
||||||
|
if case .video(let video) = item {
|
||||||
|
return video.toVideo(instanceURL: instanceURL)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}.prefix(12).map { $0 }
|
||||||
|
|
||||||
let resolvedVideoId = videoId ?? self.videoId ?? ""
|
let resolvedVideoId = videoId ?? self.videoId ?? ""
|
||||||
let thumbnails: [Thumbnail] = {
|
let thumbnails: [Thumbnail] = {
|
||||||
@@ -801,7 +812,7 @@ private struct PipedChannelResponse: Decodable, Sendable {
|
|||||||
let verified: Bool?
|
let verified: Bool?
|
||||||
let avatarUrl: String?
|
let avatarUrl: String?
|
||||||
let bannerUrl: String?
|
let bannerUrl: String?
|
||||||
let relatedStreams: [PipedVideo]?
|
let relatedStreams: [PipedVideoItem]?
|
||||||
let nextpage: String?
|
let nextpage: String?
|
||||||
let tabs: [PipedChannelTab]?
|
let tabs: [PipedChannelTab]?
|
||||||
|
|
||||||
@@ -828,7 +839,7 @@ private struct PipedChannelTab: Decodable, Sendable {
|
|||||||
|
|
||||||
/// Response from `/nextpage/channel/{id}?nextpage=...` for paginated channel videos.
|
/// Response from `/nextpage/channel/{id}?nextpage=...` for paginated channel videos.
|
||||||
private struct PipedNextPageResponse: Decodable, Sendable {
|
private struct PipedNextPageResponse: Decodable, Sendable {
|
||||||
let relatedStreams: [PipedVideo]
|
let relatedStreams: [PipedVideoItem]
|
||||||
let nextpage: String?
|
let nextpage: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -917,8 +928,8 @@ private struct PipedTabPage {
|
|||||||
let continuation: String?
|
let continuation: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Item within a Piped playlist - gracefully handles malformed items.
|
/// Wrapper for PipedVideo that gracefully handles malformed items.
|
||||||
private enum PipedPlaylistItem: Decodable, Sendable {
|
private enum PipedVideoItem: Decodable, Sendable {
|
||||||
case video(PipedVideo)
|
case video(PipedVideo)
|
||||||
case unknown
|
case unknown
|
||||||
|
|
||||||
@@ -938,7 +949,7 @@ private struct PipedPlaylistResponse: Decodable, Sendable {
|
|||||||
let uploaderUrl: String?
|
let uploaderUrl: String?
|
||||||
let uploaderAvatar: String?
|
let uploaderAvatar: String?
|
||||||
let videos: Int?
|
let videos: Int?
|
||||||
let relatedStreams: [PipedPlaylistItem]?
|
let relatedStreams: [PipedVideoItem]?
|
||||||
let thumbnailUrl: String?
|
let thumbnailUrl: String?
|
||||||
let nextpage: String?
|
let nextpage: String?
|
||||||
|
|
||||||
@@ -971,7 +982,7 @@ private struct PipedPlaylistResponse: Decodable, Sendable {
|
|||||||
|
|
||||||
/// Response from `/nextpage/playlists/{id}?nextpage=...` for paginated playlist videos.
|
/// Response from `/nextpage/playlists/{id}?nextpage=...` for paginated playlist videos.
|
||||||
private struct PipedPlaylistNextPageResponse: Decodable, Sendable {
|
private struct PipedPlaylistNextPageResponse: Decodable, Sendable {
|
||||||
let relatedStreams: [PipedPlaylistItem]?
|
let relatedStreams: [PipedVideoItem]?
|
||||||
let nextpage: String?
|
let nextpage: String?
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user