Skip to content

Commit

Permalink
Added AccountPlaylistsResponse.
Browse files Browse the repository at this point in the history
  • Loading branch information
b5i committed Nov 27, 2024
1 parent d8e12e9 commit 821b187
Show file tree
Hide file tree
Showing 7 changed files with 99 additions and 3 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ Here is a list of the default requests supported by YouTubeKit, all the informat
### Account
- [AccountInfosResponse](https://github.com/b5i/YouTubeKit/blob/9db43b336ceba6c11981dee2c7e9afad49ebda22/Sources/YouTubeKit/YouTubeResponseTypes/AuthenticatedResponses/AccountResponses/AccountInfosResponse.swift#L10C19-L10C19) to get the informations about a YouTube account (using the cookies).
- [AccountLibraryResponse](https://github.com/b5i/YouTubeKit/blob/9db43b336ceba6c11981dee2c7e9afad49ebda22/Sources/YouTubeKit/YouTubeResponseTypes/AuthenticatedResponses/AccountResponses/AccountLibraryResponse.swift#L10C15-L10C37) to get the library of an account.
- [AccountPlaylistsResponse]() to get the playlists of an account.
- [AccountSubscriptionsFeedResponse]() to get the feed of the account's subscriptions.
- [AccountSubscriptionsResponse]() to get the list of channel that the account is subscribed to.
- [HistoryResponse](https://github.com/b5i/YouTubeKit/blob/62433f5af39b19e83b248188c9c5cd39ac7c280e/Sources/YouTubeKit/YouTubeResponseTypes/AuthenticatedResponses/HistoryActions/HistoryResponse.swift#L13) and [RemoveVideoFromHistroryResponse](https://github.com/b5i/YouTubeKit/blob/62433f5af39b19e83b248188c9c5cd39ac7c280e/Sources/YouTubeKit/YouTubeResponseTypes/AuthenticatedResponses/HistoryActions/RemoveVideoFromHistroryResponse.swift#L10) to get the history of an account and remove some videos of it.
Expand Down
2 changes: 2 additions & 0 deletions Sources/YouTubeKit/HeaderTypes+RawRepresentable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ extension HeaderTypes: RawRepresentable {
return "userAccountHeaders"
case .usersLibraryHeaders:
return "usersLibraryHeaders"
case .usersPlaylistsHeaders:
return "usersPlaylistsHeaders"
case .usersAllPlaylistsHeaders:
return "usersAllPlaylistsHeaders"
case .createPlaylistHeaders:
Expand Down
5 changes: 4 additions & 1 deletion Sources/YouTubeKit/HeaderTypes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ public enum HeaderTypes: Codable, Sendable {
/// Get a user's library.
case usersLibraryHeaders

/// Get all the playlists of a user. Differs from `usersAllPlaylistsHeaders` because it doesn't include the info whether a certain video is already in the playlist or not.
case usersPlaylistsHeaders

/// Get a user's subscriptions.
case usersSubscriptionsHeaders

Expand All @@ -80,7 +83,7 @@ public enum HeaderTypes: Codable, Sendable {
/// Get a users's subscriptions feed continuation.
case usersSubscriptionsFeedContinuationHeaders

/// Get all playlists where a video could be added, also includes the info whether the video is already in the playlist or not.
/// Get all playlists where a **video** could be added, also includes the info whether the video is already in the playlist or not.
/// - Parameter browseId: The video's id to check, should be taken from ``YTVideo/videoId``.
case usersAllPlaylistsHeaders

Expand Down
32 changes: 32 additions & 0 deletions Sources/YouTubeKit/YouTubeModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,8 @@ public class YouTubeModel {
return userAccountInfosHeaders()
case .usersLibraryHeaders:
return usersLibraryHeaders()
case .usersPlaylistsHeaders:
return usersPlaylistsHeaders()
case .usersAllPlaylistsHeaders:
return usersAllPlaylistsHeaders()
case .createPlaylistHeaders:
Expand Down Expand Up @@ -784,6 +786,36 @@ public class YouTubeModel {
}
}

/// Get a all the playlists of a YouTube channel.
func usersPlaylistsHeaders() -> HeadersList {
if let headers = self.customHeaders[.usersPlaylistsHeaders] {
return headers
} else {
return HeadersList(
url: URL(string: "https://www.youtube.com/youtubei/v1/browse")!,
method: .POST,
headers: [
.init(name: "Accept", content: "*/*"),
.init(name: "Accept-Encoding", content: "gzip, deflate, br"),
.init(name: "Host", content: "www.youtube.com"),
.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.1 Safari/605.1.15"),
.init(name: "Accept-Language", content: "\(self.selectedLocale);q=0.9"),
.init(name: "Origin", content: "https://www.youtube.com/"),
.init(name: "Referer", content: "https://www.youtube.com/"),
.init(name: "Content-Type", content: "application/json"),
.init(name: "X-Origin", content: "https://www.youtube.com")
],
addQueryAfterParts: [],
httpBody: [
"{\"context\":{\"client\":{\"hl\":\"\(self.selectedLocaleLanguageCode)\",\"gl\":\"\(self.selectedLocaleCountryCode.uppercased())\",\"deviceMake\":\"Apple\",\"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.20221220.09.00\",\"osName\":\"Macintosh\",\"osVersion\":\"10_15_7\",\"platform\":\"DESKTOP\",\"clientFormFactor\":\"UNKNOWN_FORM_FACTOR\",\"userInterfaceTheme\":\"USER_INTERFACE_THEME_DARK\",\"timeZone\":\"Europe/Zurich\",\"browserName\":\"Safari\",\"browserVersion\":\"16.2\",\"acceptHeader\":\"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\",\"utcOffsetMinutes\":60,\"mainAppWebInfo\":{\"webDisplayMode\":\"WEB_DISPLAY_MODE_BROWSER\",\"isWebNativeShareAvailable\":true}},\"user\":{\"lockedSafetyMode\":false},\"request\":{\"useSsl\":true,\"internalExperimentFlags\":[],\"consistencyTokenJars\":[]}},\"browseId\":\"FEplaylist_aggregation\"}"
],
parameters: [
.init(name: "prettyPrint", content: "false")
]
)
}
}

/// Get all playlists where a video could be added, also includes the info whether the video is already in the playlist or not.
func usersAllPlaylistsHeaders() -> HeadersList {
if let headers = self.customHeaders[.usersAllPlaylistsHeaders] {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// AccountPlaylistsResponse.swift
// YouTubeKit
//
// Created by Antoine Bollengier on 27.11.2024.
// Copyright © 2024 Antoine Bollengier (github.com/b5i). All rights reserved.
//

import Foundation

public struct AccountPlaylistsResponse: AuthenticatedResponse {
public static let headersType: HeaderTypes = .usersPlaylistsHeaders

public static let parametersValidationList: ValidationList = [:]

public var isDisconnected: Bool = true

public var results: [YTPlaylist] = []

public static func decodeJSON(json: JSON) -> AccountPlaylistsResponse {
var toReturn = AccountPlaylistsResponse()

guard !(json["responseContext"]["mainAppWebResponseContext"]["loggedOut"].bool ?? true) else { return toReturn }

toReturn.isDisconnected = false

for tab in json["contents"]["twoColumnBrowseResultsRenderer"]["tabs"].arrayValue {
guard tab["tabRenderer"]["selected"].bool == true else { continue }

for playlistJSON in tab["tabRenderer"]["content"]["richGridRenderer"]["contents"].arrayValue {
if let playlist = YTPlaylist.decodeLockupJSON(json: playlistJSON["richItemRenderer"]["content"]["lockupViewModel"]) {
toReturn.results.append(playlist)
}
}
}

return toReturn
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,11 @@ public struct VideoInfosResponse: YouTubeResponse {
public var aspectRatio: Double?

/// Array of formats used to download the video, they usually contain both audio and video data and the download speed is higher than the ``VideoInfosResponse/downloadFormats``.
@available(*, deprecated, message: "This property is unstable for the moment.")
//@available(*, deprecated, message: "This property is unstable for the moment.")
public var defaultFormats: [any DownloadFormat]

/// Array of formats used to download the video, usually sorted from highest video quality to lowest followed by audio formats.
@available(*, deprecated, message: "This property is unstable for the moment.")
//@available(*, deprecated, message: "This property is unstable for the moment.")
public var downloadFormats: [any DownloadFormat]

public init(
Expand Down
18 changes: 18 additions & 0 deletions Tests/YouTubeKitTests/YouTubeKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,24 @@ final class YouTubeKitTests: XCTestCase {
XCTAssertNotEqual(response.playlists.count, 0, TEST_NAME + "Checking if account's playlists have been extracted.")
}

func testAccountPlaylists() async throws {
guard cookies != "" else { return }
let TEST_NAME = "Test: testAccountPlaylists() -> "
YTM.cookies = cookies

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

guard !response.isDisconnected else { XCTFail(TEST_NAME + "Checking if cookies were defined"); return }

XCTAssertNotEqual(response.results.count, 0, TEST_NAME + "Checking if account's playlists have been extracted.")

let firstPlaylist = response.results.first!

XCTAssertNotNil(firstPlaylist.title, TEST_NAME + "Checking if the title of the first playlist has been extracted.")
XCTAssertNotEqual(firstPlaylist.thumbnails.count, 0, TEST_NAME + "Checking if the thumbnails of the first playlist have been extracted.")
XCTAssertNotNil(firstPlaylist.playlistId, TEST_NAME + "Checking if the playlistId of the first playlist has been extracted.")
}

func testPlaylistActions() async throws {
guard cookies != "" else { return }
let TEST_NAME = "Test: testPlaylistActions() -> "
Expand Down

0 comments on commit 821b187

Please sign in to comment.