Skip to content

Commit dcb2189

Browse files
committed
fix: fix tests and liked videos playlist
1 parent c5c1b3f commit dcb2189

3 files changed

Lines changed: 60 additions & 46 deletions

File tree

Sources/YouTubeKit/YouTubeModel.swift

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -432,14 +432,14 @@ public class YouTubeModel {
432432
.init(name: "Accept", content: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"),
433433
.init(name: "Accept-Encoding", content: "gzip, deflate, br"),
434434
.init(name: "Host", content: "www.youtube.com"),
435-
.init(name: "User-Agent", content: "com.google.ios.youtube/21.02.3 (iPhone16,2; U; CPU iOS 26_2_1 like Mac OS X;)"),
435+
.init(name: "User-Agent", content: "com.google.ios.youtube/21.13.6 (iPhone16,2; U; CPU iOS 26_4 like Mac OS X;)"),
436436
.init(name: "Accept-Language", content: "\(self.selectedLocale);q=0.9"),
437437
.init(name: "Origin", content: "https://www.youtube.com/"),
438438
.init(name: "Referer", content: "https://www.youtube.com/"),
439439
.init(name: "Content-Type", content: "application/json"),
440440
.init(name: "X-Origin", content: "https://www.youtube.com"),
441441
.init(name: "X-Youtube-Client-Name", content: "5"),
442-
.init(name: "X-Youtube-Client-Version", content: "21.02.3"),
442+
.init(name: "X-Youtube-Client-Version", content: "21.13.6"),
443443
],
444444
customHeaders: [
445445
"X-Goog-Visitor-Id": .visitorData
@@ -454,14 +454,14 @@ public class YouTubeModel {
454454
"context": {
455455
"client": {
456456
"clientName": "IOS",
457-
"clientVersion": "21.02.3",
457+
"clientVersion": "21.13.6",
458458
"deviceMake": "Apple",
459459
"deviceModel": "iPhone16,2",
460460
"hl": "\#(self.selectedLocaleLanguageCode)",
461461
"osName": "iPhone",
462-
"osVersion": "26.2.1.23C71",
462+
"osVersion": "26.4.23E246",
463463
"timeZone": "UTC",
464-
"userAgent": "com.google.ios.youtube/21.02.3 (iPhone16,2; U; CPU iOS 26_2_1 like Mac OS X;)",
464+
"userAgent": "com.google.ios.youtube/21.13.6 (iPhone16,2; U; CPU iOS 26_4 like Mac OS X;)",
465465
"utcOffsetMinutes": 0
466466
}
467467
},
@@ -583,7 +583,7 @@ public class YouTubeModel {
583583
.init(name: "Accept", content: "*/*"),
584584
.init(name: "Accept-Encoding", content: "gzip, deflate, br"),
585585
.init(name: "Host", content: "www.youtube.com"),
586-
.init(name: "User-Agent", content: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.2 Safari/605.1.15"),
586+
.init(name: "User-Agent", content: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.3.1 Safari/605.1.15"),
587587
.init(name: "Accept-Language", content: "\(self.selectedLocale);q=0.9"),
588588
.init(name: "Origin", content: "https://www.youtube.com/"),
589589
.init(name: "Referer", content: "https://www.youtube.com/"),
@@ -594,7 +594,7 @@ public class YouTubeModel {
594594
.init(index: 0, encode: false, content: .browseId)
595595
],
596596
httpBody: [
597-
"{\"context\":{\"client\":{\"hl\":\"\(self.selectedLocaleLanguageCode)\",\"gl\":\"\(self.selectedLocaleCountryCode.uppercased())\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/16.2 Safari/605.1.15,gzip(gfe)\",\"clientName\":\"WEB\",\"clientVersion\":\"2.20250312.04.00\",\"acceptHeader\":\"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\",\"mainAppWebInfo\":{\"webDisplayMode\":\"WEB_DISPLAY_MODE_BROWSER\",\"isWebNativeShareAvailable\":true}},\"user\":{\"lockedSafetyMode\":false},\"request\":{\"useSsl\":true,\"internalExperimentFlags\":[],\"consistencyTokenJars\":[]}},\"browseId\":\"",
597+
"{\"context\":{\"client\":{\"hl\":\"\(self.selectedLocaleLanguageCode)\",\"gl\":\"\(self.selectedLocaleCountryCode.uppercased())\",\"userAgent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.3.1 Safari/605.1.15,gzip(gfe)\",\"clientName\":\"WEB\",\"clientVersion\":\"2.20260403.01.00\",\"acceptHeader\":\"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\",\"mainAppWebInfo\":{\"webDisplayMode\":\"WEB_DISPLAY_MODE_BROWSER\",\"isWebNativeShareAvailable\":true}},\"user\":{\"lockedSafetyMode\":false},\"request\":{\"useSsl\":true,\"internalExperimentFlags\":[],\"consistencyTokenJars\":[]}},\"browseId\":\"",
598598
"\"}"
599599
],
600600
parameters: [

Sources/YouTubeKit/YouTubeResponseTypes/PlaylistInfos/PlaylistInfosResponse.swift

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -78,35 +78,49 @@ public struct PlaylistInfosResponse: ContinuableResponse {
7878
guard let thirdVideoArray = secondVideoArrayPart["itemSectionRenderer", "contents"].array else { continue }
7979

8080
for thirdVideoArrayPart in thirdVideoArray {
81-
guard let finalVideoArray = thirdVideoArrayPart["playlistVideoListRenderer", "contents"].array else { continue }
82-
let secondHeader = thirdVideoArrayPart["playlistVideoListRenderer"]
83-
84-
toReturn.userInteractions.isEditable = json["header", "playlistHeaderRenderer", "isEditable"].bool ?? secondHeader["isEditable"].bool
85-
86-
toReturn.userInteractions.canReorder = json["header", "playlistHeaderRenderer", "canReorder"].bool ?? secondHeader["canReorder"].bool
87-
88-
if toReturn.userInteractions.isEditable ?? false {
89-
toReturn.videoIdsInPlaylist = []
90-
}
91-
92-
for videoJSON in finalVideoArray {
93-
if let video = YTVideo.decodeVideoFromPlaylist(json: videoJSON["playlistVideoRenderer"]) {
94-
95-
toReturn.videoIdsInPlaylist?.append(videoJSON["playlistVideoRenderer", "setVideoId"].string)
96-
97-
toReturn.results.append(video)
98-
} else if let video = YTVideo.decodeVideoFromPlaylist(json: videoJSON["playlistVideoListRenderer"]) {
99-
100-
toReturn.videoIdsInPlaylist?.append(videoJSON["playlistVideoListRenderer", "setVideoId"].string)
101-
102-
toReturn.results.append(video)
103-
} else if videoJSON["continuationItemRenderer", "continuationEndpoint"].exists() {
104-
if let token = videoJSON["continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token"].string {
105-
toReturn.continuationToken = token
106-
} else if let commandsArray = videoJSON["continuationItemRenderer", "continuationEndpoint", "commandExecutorCommand", "commands"].array, let token = commandsArray.first(where: {$0["continuationCommand", "token"].string != nil })?["continuationCommand", "token"].string {
107-
toReturn.continuationToken = token
81+
if let finalVideoArray = thirdVideoArrayPart["playlistVideoListRenderer", "contents"].array {
82+
let secondHeader = thirdVideoArrayPart["playlistVideoListRenderer"]
83+
84+
toReturn.userInteractions.isEditable = json["header", "playlistHeaderRenderer", "isEditable"].bool ?? secondHeader["isEditable"].bool
85+
86+
toReturn.userInteractions.canReorder = json["header", "playlistHeaderRenderer", "canReorder"].bool ?? secondHeader["canReorder"].bool
87+
88+
if toReturn.userInteractions.isEditable ?? false {
89+
toReturn.videoIdsInPlaylist = []
90+
}
91+
92+
for videoJSON in finalVideoArray {
93+
if let video = YTVideo.decodeVideoFromPlaylist(json: videoJSON["playlistVideoRenderer"]) {
94+
95+
toReturn.videoIdsInPlaylist?.append(videoJSON["playlistVideoRenderer", "setVideoId"].string)
96+
97+
toReturn.results.append(video)
98+
} else if let video = YTVideo.decodeVideoFromPlaylist(json: videoJSON["playlistVideoListRenderer"]) {
99+
100+
toReturn.videoIdsInPlaylist?.append(videoJSON["playlistVideoListRenderer", "setVideoId"].string)
101+
102+
toReturn.results.append(video)
103+
} else if videoJSON["continuationItemRenderer", "continuationEndpoint"].exists() {
104+
if let token = videoJSON["continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token"].string {
105+
toReturn.continuationToken = token
106+
} else if let commandsArray = videoJSON["continuationItemRenderer", "continuationEndpoint", "commandExecutorCommand", "commands"].array, let token = commandsArray.first(where: {$0["continuationCommand", "token"].string != nil })?["continuationCommand", "token"].string {
107+
toReturn.continuationToken = token
108+
}
108109
}
109110
}
111+
} else if let video = YTVideo.decodeLockupJSON(json: thirdVideoArrayPart["lockupViewModel"]) {
112+
113+
// no setVideoId as of now :(
114+
115+
toReturn.results.append(video)
116+
} else if thirdVideoArrayPart["continuationItemRenderer", "continuationEndpoint"].exists() {
117+
if let token = thirdVideoArrayPart["continuationItemRenderer", "continuationEndpoint", "continuationCommand", "token"].string {
118+
toReturn.continuationToken = token
119+
} else if let commandsArray = thirdVideoArrayPart["continuationItemRenderer", "continuationEndpoint", "commandExecutorCommand", "commands"].array, let token = commandsArray.first(where: {$0["continuationCommand", "token"].string != nil })?["continuationCommand", "token"].string {
120+
toReturn.continuationToken = token
121+
}
122+
} else if thirdVideoArrayPart["continuationItemViewModel"].exists() {
123+
toReturn.continuationToken = thirdVideoArrayPart["continuationItemViewModel", "continuationCommand", "innertubeCommand", "continuationCommand", "token"].string
110124
}
111125
}
112126
}

Tests/YouTubeKitTests/YouTubeKitTests.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -479,18 +479,7 @@ final class YouTubeKitTests: XCTestCase {
479479
func testSearchResponseContinuation() async throws {
480480
let TEST_NAME = "Test: testSearchResponseContinuation() -> "
481481

482-
var searchResult = try await SearchResponse.sendThrowingRequest(youtubeModel: YTM, data: [.query: "Zen Emission"])
483-
484-
// Zen Emission has special channel json containing the video count of his channel
485-
486-
if let firstChannel = searchResult.results.first(where: {$0 as? YTChannel != nil}) as? YTChannel {
487-
XCTAssertNotNil(firstChannel.name)
488-
XCTAssertNotNil(firstChannel.subscriberCount)
489-
XCTAssertNotNil(firstChannel.videoCount)
490-
XCTAssertNotEqual(firstChannel.thumbnails.count, 0)
491-
}
492-
493-
searchResult = try await SearchResponse.sendThrowingRequest(youtubeModel: YTM, data: [.query: "mrbeast"])
482+
var searchResult = try await SearchResponse.sendThrowingRequest(youtubeModel: YTM, data: [.query: "mrbeast"])
494483

495484
if let firstChannel = searchResult.results.first(where: {$0 as? YTChannel != nil}) as? YTChannel {
496485
XCTAssertNotNil(firstChannel.name)
@@ -845,6 +834,7 @@ final class YouTubeKitTests: XCTestCase {
845834
guard cookies != "" else { return }
846835
let TEST_NAME = "Test: testAccountPlaylists() -> "
847836
YTM.cookies = cookies
837+
YTM.alwaysUseCookies = true
848838

849839
let response = try await AccountPlaylistsResponse.sendThrowingRequest(youtubeModel: YTM, data: [:])
850840

@@ -857,6 +847,16 @@ final class YouTubeKitTests: XCTestCase {
857847
XCTAssertNotNil(firstPlaylist.title, TEST_NAME + "Checking if the title of the first playlist has been extracted.")
858848
XCTAssertNotEqual(firstPlaylist.thumbnails.count, 0, TEST_NAME + "Checking if the thumbnails of the first playlist have been extracted.")
859849
XCTAssertNotNil(firstPlaylist.playlistId, TEST_NAME + "Checking if the playlistId of the first playlist has been extracted.")
850+
851+
// test default playlists
852+
853+
let likesPlaylist = YTPlaylist(playlistId: "VLLL")
854+
let watchLaterPlaylist = YTPlaylist(playlistId: "VLWL")
855+
856+
let likes = try await likesPlaylist.fetchVideosThrowing(youtubeModel: YTM)
857+
XCTAssertNotEqual(likes.results.count, 0, TEST_NAME + "Checking if the liked videos have been extracted.")
858+
let watchLater = try await watchLaterPlaylist.fetchVideosThrowing(youtubeModel: YTM)
859+
XCTAssertNotEqual(watchLater.results.count, 0, TEST_NAME + "Checking if the watch later videos have been extracted.")
860860
}
861861

862862
func testCreateEmptyPlaylist() async throws {

0 commit comments

Comments
 (0)