Skip to content

Commit

Permalink
Fixed channel extraction.
Browse files Browse the repository at this point in the history
  • Loading branch information
b5i committed Jul 29, 2024
1 parent b5ad965 commit b154b23
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 20 deletions.
22 changes: 14 additions & 8 deletions Sources/YouTubeKit/BaseStructs/YTChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import Foundation

/// Struct representing a channel.
public struct YTChannel: YTSearchResult, YouTubeChannel {
public init(id: Int? = nil, name: String? = nil, channelId: String, handle: String? = nil, thumbnails: [YTThumbnail] = [], subscriberCount: String? = nil, badges: [String] = []) {
public init(id: Int? = nil, name: String? = nil, channelId: String, handle: String? = nil, thumbnails: [YTThumbnail] = [], subscriberCount: String? = nil, badges: [String] = [], videoCount: String? = nil) {
self.id = id
self.name = name
self.channelId = channelId
self.handle = handle
self.channelId = channelId
self.thumbnails = thumbnails
self.subscriberCount = subscriberCount
self.badges = badges
self.videoCount = videoCount
}

public static func canBeDecoded(json: JSON) -> Bool {
Expand All @@ -31,13 +32,15 @@ public struct YTChannel: YTSearchResult, YouTubeChannel {
/// Inititalize a new ``YTSearchResultType/Channel-swift.struct`` instance to put the informations in it.
var channel = YTChannel(channelId: channelId)
channel.name = json["title"]["simpleText"].string
channel.handle = json["subscriberCountText"]["simpleText"].string

if json["navigationEndpoint"]["browseEndpoint"]["canonicalBaseUrl"].stringValue.contains("/c/") { // special channel json with no handle
channel.subscriberCount = json["subscriberCountText"]["simpleText"].string
channel.videoCount = json["videoCountText"]["runs"].array?.map {$0["text"].stringValue}.reduce("", +) ?? json["videoCountText"]["simpleText"].string
} else {
channel.handle = json["subscriberCountText"]["simpleText"].string
channel.subscriberCount = json["videoCountText"]["simpleText"].string
}
YTThumbnail.appendThumbnails(json: json["thumbnail"], thumbnailList: &channel.thumbnails)

/// There's an error in YouTube's API
channel.subscriberCount = json["videoCountText"]["simpleText"].string


if let badgesList = json["ownerBadges"].array {
for badge in badgesList {
if let badgeName = badge["metadataBadgeRenderer"]["style"].string {
Expand Down Expand Up @@ -86,6 +89,9 @@ public struct YTChannel: YTSearchResult, YouTubeChannel {
/// Usually like "BADGE_STYLE_TYPE_VERIFIED"
public var badges: [String] = []

/// String representing the video count of the channel. Might not be present if the channel handle should be displayed instead.
public var videoCount: String?

///Not necessary here because of prepareJSON() method
/*
enum CodingKeys: String, CodingKey {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,13 +125,33 @@ public struct ChannelInfosResponse: YouTubeResponse {
public var requestParams: [RequestTypes : String] = [:]

/// Count of subscriber of the channel.
public var subscribersCount: String?
@available(*, deprecated, renamed: "subscriberCount")
public var subscribersCount: String? {
get {
return self.subscriberCount
} set {
self.subscriberCount = newValue
}
}

/// Count of subscriber of the channel.
public var subscriberCount: String?

/// Boolean indicating if the user from the provided cookies is subscribed to the channel -> only when cookies are given see ``YouTubeModel/cookies``.
public var subscribeStatus: Bool?

/// Count of videos that the channel posted.
public var videosCount: String?
@available(*, deprecated, renamed: "videoCount")
public var videosCount: String? {
get {
return self.videoCount
} set {
self.videoCount = newValue
}
}

/// Count of videos that the channel posted.
public var videoCount: String?

/// A short description of the channel.
public var shortDescription: String?
Expand Down Expand Up @@ -216,9 +236,9 @@ public struct ChannelInfosResponse: YouTubeResponse {

self.subscribeStatus = channelInfos["subscribeButton"]["subscribeButtonRenderer"]["subscribed"].bool

self.subscribersCount = channelInfos["subscriberCountText"]["simpleText"].string
self.subscriberCount = channelInfos["subscriberCountText"]["simpleText"].string

self.videosCount = channelInfos["videosCountText"]["runs"].arrayValue.map({$0["text"].stringValue}).joined()
self.videoCount = channelInfos["videosCountText"]["runs"].arrayValue.map({$0["text"].stringValue}).joined()
}

private mutating func extractChannelInfosFromPageHeaderRenderer(json: JSON) {
Expand Down Expand Up @@ -259,9 +279,11 @@ public struct ChannelInfosResponse: YouTubeResponse {

//self.subscribeStatus = channelInfos["subscribeButton"]["subscribeButtonRenderer"]["subscribed"].bool TODO: broken at the moment

self.subscribersCount = metadataRows.arrayValue.count > 1 ? metadataRows.arrayValue[1]["metadataParts"].arrayValue.first?["text"]["content"].string : nil
// TODO: implement badges extraction via dynamicTextViewModel -> text -> attachmentRuns

self.subscriberCount = !metadataRows.arrayValue.isEmpty ? metadataRows.arrayValue.last!["metadataParts"].arrayValue.first?["text"]["content"].string : nil

self.videosCount = metadataRows.arrayValue.count > 1 ? metadataRows.arrayValue[1]["metadataParts"].arrayValue.count > 1 ? metadataRows.arrayValue[1]["metadataParts"].arrayValue[1]["text"]["content"].string : nil : nil
self.videoCount = !metadataRows.arrayValue.isEmpty ? metadataRows.arrayValue.last!["metadataParts"].arrayValue.count > 1 ? metadataRows.arrayValue.last!["metadataParts"].arrayValue[1]["text"]["content"].string : nil : nil

self.shortDescription = metadata["description"].string

Expand Down Expand Up @@ -814,6 +836,16 @@ public struct ChannelInfosResponse: YouTubeResponse {
}
}

/// Copy properties from another ``ChannelInfosResponse`` to the current ``ChannelInfosResponse`` instance.
/// - Parameter otherResponse: the ``ChannelInfosResponse`` where the infos will be taken of.
public mutating func copyProperties(of channel: YTChannel) {
self.avatarThumbnails = channel.thumbnails
self.name = channel.name
self.handle = channel.handle
self.videoCount = channel.videoCount
self.subscriberCount = channel.subscriberCount
}

/// Copy properties from another ``ChannelInfosResponse`` to the current ``ChannelInfosResponse`` instance.
/// - Parameter otherResponse: the ``ChannelInfosResponse`` where the infos will be taken of.
public mutating func copyProperties(of otherResponse: ChannelInfosResponse) {
Expand All @@ -824,8 +856,8 @@ public struct ChannelInfosResponse: YouTubeResponse {
self.name = otherResponse.name
self.handle = otherResponse.handle
self.subscribeStatus = otherResponse.subscribeStatus
self.subscribersCount = otherResponse.subscribersCount
self.videosCount = otherResponse.videosCount
self.subscriberCount = otherResponse.subscriberCount
self.videoCount = otherResponse.videoCount
}

/// Put the `channelContent` into ``ChannelInfosResponse/channelContentStore`` with `category` as key.
Expand Down
45 changes: 41 additions & 4 deletions Tests/YouTubeKitTests/YouTubeKitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -541,8 +541,45 @@ final class YouTubeKitTests: XCTestCase {
func testSearchResponseContinuation() async throws {
let TEST_NAME = "Test: testSearchResponseContinuation() -> "

var searchResult = try await SearchResponse.sendThrowingRequest(youtubeModel: YTM, data: [.query: "fred again"])

var searchResult = try await SearchResponse.sendThrowingRequest(youtubeModel: YTM, data: [.query: "hugodécrypte"])

// hugodécrypte has special channel json containing the video count of his channel

if let firstChannel = searchResult.results.first(where: {$0 as? YTChannel != nil}) as? YTChannel {
XCTAssertNotNil(firstChannel.name)
XCTAssertNotNil(firstChannel.subscriberCount)
XCTAssertNotNil(firstChannel.videoCount)
XCTAssertNotEqual(firstChannel.thumbnails.count, 0)
}

searchResult = try await SearchResponse.sendThrowingRequest(youtubeModel: YTM, data: [.query: "fred again"])

if let firstChannel = searchResult.results.first(where: {$0 as? YTChannel != nil}) as? YTChannel {
XCTAssertNotNil(firstChannel.name)
XCTAssertNotNil(firstChannel.handle)
XCTAssertNotNil(firstChannel.subscriberCount)
XCTAssertNotEqual(firstChannel.thumbnails.count, 0)
}

if let firstVideo = searchResult.results.first(where: {$0 as? YTVideo != nil}) as? YTVideo {
XCTAssertNotNil(firstVideo.title)
XCTAssertNotNil(firstVideo.channel)
XCTAssertNotNil(firstVideo.thumbnails)
XCTAssertNotNil(firstVideo.timeLength)
XCTAssertNotNil(firstVideo.viewCount)
XCTAssertNotNil(firstVideo.timePosted)
}

if let firstPlaylist = searchResult.results.first(where: {$0 as? YTPlaylist != nil}) as? YTPlaylist {
XCTAssertNotNil(firstPlaylist.title)
XCTAssertNotNil(firstPlaylist.channel)
XCTAssertNotEqual(firstPlaylist.thumbnails.count, 0)
XCTAssertNotNil(firstPlaylist.videoCount)
XCTAssertNotNil(firstPlaylist.privacy)
XCTAssertNotNil(firstPlaylist.timePosted)
XCTAssertNotEqual(firstPlaylist.frontVideos.count, 0)
}

guard let continuationToken = searchResult.continuationToken else { XCTFail(TEST_NAME + "continuationToken is not defined"); return }
guard let visitorData = searchResult.visitorData else { XCTFail(TEST_NAME + "visitorData is not defined"); return }
let continuationResult = try await SearchResponse.Continuation.sendThrowingRequest(youtubeModel: YTM, data: [
Expand Down Expand Up @@ -683,11 +720,11 @@ final class YouTubeKitTests: XCTestCase {

let mainRequestResult = try await channel.fetchInfosThrowing(youtubeModel: YTM)

XCTAssertNotNil(mainRequestResult.videosCount, TEST_NAME + "Checking if mainRequestResult.videosCount is not nil")
XCTAssertNotNil(mainRequestResult.videoCount, TEST_NAME + "Checking if mainRequestResult.videosCount is not nil")

/// Testing ChannelContent fetching, Videos, Shorts, Directs and Playlists

///Videos
/// Videos
var videoRequestResult = try await mainRequestResult.getChannelContentThrowing(forType: .videos, youtubeModel: YTM)

XCTAssertEqual(videoRequestResult.name, videoResult.channel?.name, TEST_NAME + "Checking if videoRequestResult.name is equal to videoResult.channel.name")
Expand Down

0 comments on commit b154b23

Please sign in to comment.