From 80e40ed92821244b53b33b59a60b6817e96dc754 Mon Sep 17 00:00:00 2001 From: Morris Richman <81453549+Mcrich23@users.noreply.github.com> Date: Thu, 15 Feb 2024 11:53:29 -0800 Subject: [PATCH] Made videosAndTime in HistoryResponse iterable --- .../HistoryActions/HistoryResponse.swift | 34 +++++++++++++++---- .../ChannelInfos/ChannelInfosResponse.swift | 2 +- Tests/YouTubeKitTests/YouTubeKitTests.swift | 2 +- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Sources/YouTubeKit/YouTubeResponseTypes/AuthenticatedResponses/HistoryActions/HistoryResponse.swift b/Sources/YouTubeKit/YouTubeResponseTypes/AuthenticatedResponses/HistoryActions/HistoryResponse.swift index d9bc2ff..f17885d 100644 --- a/Sources/YouTubeKit/YouTubeResponseTypes/AuthenticatedResponses/HistoryActions/HistoryResponse.swift +++ b/Sources/YouTubeKit/YouTubeResponseTypes/AuthenticatedResponses/HistoryActions/HistoryResponse.swift @@ -25,9 +25,9 @@ public struct HistoryResponse: AuthenticatedResponse { /// /// Example: /// ```swift - /// var videosAndTime = [("Today", [A few videos]), ("Yesterday", [A few videos too])] + /// var videosAndTime = [HistoryBlock(groupTitle: "Today", videosArray: [A few videos]), (groupTitle: "Yesterday", videosArray: [A few videos too])] /// ``` - public var videosAndTime: [(groupTitle: String, videosArray: [(YTVideo, suppressToken: String?)])] = [] + public var videosAndTime: [HistoryBlock] = [] /// Title of the playlist. public var title: String? @@ -46,10 +46,10 @@ public struct HistoryResponse: AuthenticatedResponse { for videoGroup in tabJSON["content"]["sectionListRenderer"]["contents"].arrayValue.map({$0["itemSectionRenderer"]}) { let title = videoGroup["header"]["itemSectionHeaderRenderer"]["title"]["runs"].array?.map({$0["text"].stringValue}).joined() ?? videoGroup["header"]["itemSectionHeaderRenderer"]["title"]["simpleText"].stringValue - var toAppend: (String , [(YTVideo, String?)]) = (title, []) + var toAppend: HistoryBlock = .init(groupTitle: title, videosArray: []) for videoJSON in videoGroup["contents"].arrayValue { if let video = YTVideo.decodeJSON(json: videoJSON["videoRenderer"]) { - toAppend.1.append((video, videoJSON["videoRenderer"]["menu"]["menuRenderer"]["topLevelButtons"].array?.first?["buttonRenderer"]["serviceEndpoint"]["feedbackEndpoint"]["feedbackToken"].string)) + toAppend.videosArray.append(.init(video: video, suppressToken: videoJSON["videoRenderer"]["menu"]["menuRenderer"]["topLevelButtons"].array?.first?["buttonRenderer"]["serviceEndpoint"]["feedbackEndpoint"]["feedbackToken"].string)) } } toReturn.videosAndTime.append(toAppend) @@ -73,7 +73,7 @@ public struct HistoryResponse: AuthenticatedResponse { public var continuationToken: String? /// Array of videos. - public var videosAndTime: [(String, [(YTVideo, suppressToken: String?)])] = [] + public var videosAndTime: [HistoryBlock] = [] public static func decodeData(data: Data) -> HistoryResponse.Continuation { let json = JSON(data) @@ -84,10 +84,10 @@ public struct HistoryResponse: AuthenticatedResponse { guard let continuationItemsArray = continationAction["appendContinuationItemsAction"]["continuationItems"].array else { continue } for videoGroup in continuationItemsArray { let title = videoGroup["header"]["itemSectionHeaderRenderer"]["title"]["runs"].array?.map({$0["text"].stringValue}).joined() ?? videoGroup["header"]["itemSectionHeaderRenderer"]["title"]["simpleText"].stringValue - var toAppend: (String , [(YTVideo, String?)]) = (title, []) + var toAppend: HistoryBlock = .init(groupTitle: title, videosArray: []) for videoJSON in videoGroup["contents"].arrayValue { if let video = YTVideo.decodeJSON(json: videoJSON["videoRenderer"]) { - toAppend.1.append((video, videoJSON["videoRenderer"]["menu"]["menuRenderer"]["topLevelButtons"].array?.first?["buttonRenderer"]["serviceEndpoint"]["feedbackEndpoint"]["feedbackToken"].string)) + toAppend.videosArray.append(.init(video: video, suppressToken: videoJSON["videoRenderer"]["menu"]["menuRenderer"]["topLevelButtons"].array?.first?["buttonRenderer"]["serviceEndpoint"]["feedbackEndpoint"]["feedbackToken"].string)) } } toReturn.videosAndTime.append(toAppend) @@ -96,4 +96,24 @@ public struct HistoryResponse: AuthenticatedResponse { return toReturn } } + /// Struct representing a block of history, containing a title and an array of YTVideos. + public struct HistoryBlock: Hashable, Identifiable { + public var id: Int { return groupTitle.hashValue } + + /// Ttitle of the group, usually represent a part of the time in the history like "Today", "Yesterday" or "February 15". + public let groupTitle: String + + /// An array of the videos that have been watched in the part of time indicated by the HistoryResponse/HistoryBlock/groupTitle. + public var videosArray: [VideoWithToken] + } + + /// Struct representing a video and the token that should be used to suppress it from the history. + public struct VideoWithToken: Hashable, Identifiable { + public var id: Int { return video.hashValue + (suppressToken?.hashValue ?? 0) } + + public let video: YTVideo + + /// Token that can be used to remove the video from the history, using for example HistoryResponse/removeVideo(withSuppressToken:youtubeModel:). + public let suppressToken: String? + } } diff --git a/Sources/YouTubeKit/YouTubeResponseTypes/ChannelInfos/ChannelInfosResponse.swift b/Sources/YouTubeKit/YouTubeResponseTypes/ChannelInfos/ChannelInfosResponse.swift index 0611735..d9bf8c0 100644 --- a/Sources/YouTubeKit/YouTubeResponseTypes/ChannelInfos/ChannelInfosResponse.swift +++ b/Sources/YouTubeKit/YouTubeResponseTypes/ChannelInfos/ChannelInfosResponse.swift @@ -149,7 +149,7 @@ public struct ChannelInfosResponse: YouTubeResponse { toReturn.name = channelInfos["title"].string if let handle = channelInfos["channelHandleText"]["runs"].array?.first?["text"].string, !handle.isEmpty { - toReturn.handle = channelInfos["channelHandleText"]["runs"].array?.first?["text"].string + toReturn.handle = handle } else { toReturn.handle = channelInfos["navigationEndpoint"]["browseEndpoint"]["canonicalBaseUrl"].string?.replacingOccurrences(of: "/", with: "") // Need to remove the first slash because the string is like "/@ChannelHandle" } diff --git a/Tests/YouTubeKitTests/YouTubeKitTests.swift b/Tests/YouTubeKitTests/YouTubeKitTests.swift index 8fd147d..9fb9b69 100644 --- a/Tests/YouTubeKitTests/YouTubeKitTests.swift +++ b/Tests/YouTubeKitTests/YouTubeKitTests.swift @@ -634,7 +634,7 @@ final class YouTubeKitTests: XCTestCase { XCTAssertNotNil(historyResponse.title, TEST_NAME + "Checking if historyResponse.title has been extracted.") XCTAssertNotEqual(historyResponse.videosAndTime.count, 0, TEST_NAME + "Checking if historyResponse.videosAndTime is not empty.") - guard let firstVideoToken = historyResponse.videosAndTime.first?.1.first?.suppressToken else { XCTFail(TEST_NAME + "Could not find a video with a suppressToken in the history"); return } + guard let firstVideoToken = historyResponse.videosAndTime.first?.videosArray.first?.suppressToken else { XCTFail(TEST_NAME + "Could not find a video with a suppressToken in the history"); return } let deleteFromHistoryError = await historyResponse.removeVideo(withSuppressToken: firstVideoToken, youtubeModel: YTM)