mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 10:44:06 +00:00
Channel pages
This commit is contained in:
@@ -154,6 +154,11 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
content.json.arrayValue.map(self.extractVideo)
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("channels/*/playlists"), requestMethods: [.get]) { (content: Entity<JSON>) -> [ContentItem] in
|
||||
let playlists = (content.json.dictionaryValue["playlists"]?.arrayValue ?? []).compactMap { self.extractChannelPlaylist(from: $0) }
|
||||
return ContentItem.array(of: playlists)
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("playlists/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> ChannelPlaylist in
|
||||
self.extractChannelPlaylist(from: content.json)
|
||||
}
|
||||
@@ -287,8 +292,11 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
.onCompletion { _ in onCompletion() }
|
||||
}
|
||||
|
||||
func channel(_ id: String) -> Resource {
|
||||
resource(baseURL: account.url, path: basePathAppending("channels/\(id)"))
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data _: String?) -> Resource {
|
||||
if contentType == .playlists {
|
||||
return resource(baseURL: account.url, path: basePathAppending("channels/\(id)/playlists"))
|
||||
}
|
||||
return resource(baseURL: account.url, path: basePathAppending("channels/\(id)"))
|
||||
}
|
||||
|
||||
func channelByName(_: String) -> Resource? {
|
||||
@@ -518,9 +526,12 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
return Channel(
|
||||
id: json["authorId"].stringValue,
|
||||
name: json["author"].stringValue,
|
||||
bannerURL: json["authorBanners"].arrayValue.first?.dictionaryValue["url"]?.url,
|
||||
thumbnailURL: URL(string: thumbnailURL),
|
||||
description: json["description"].stringValue,
|
||||
subscriptionsCount: json["subCount"].int,
|
||||
subscriptionsText: json["subCountText"].string,
|
||||
totalViews: json["totalViews"].int,
|
||||
videos: json.dictionaryValue["latestVideos"]?.arrayValue.map(extractVideo) ?? []
|
||||
)
|
||||
}
|
||||
@@ -532,7 +543,8 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
title: details["title"]?.stringValue ?? "",
|
||||
thumbnailURL: details["playlistThumbnail"]?.url,
|
||||
channel: extractChannel(from: json),
|
||||
videos: details["videos"]?.arrayValue.compactMap(extractVideo) ?? []
|
||||
videos: details["videos"]?.arrayValue.compactMap(extractVideo) ?? [],
|
||||
videosCount: details["videoCount"]?.int
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -40,6 +40,10 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
self.extractChannel(from: content.json)
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("channels/tabs*")) { (content: Entity<JSON>) -> [ContentItem] in
|
||||
(content.json.dictionaryValue["content"]?.arrayValue ?? []).compactMap { self.extractContentItem(from: $0) }
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("c/*")) { (content: Entity<JSON>) -> Channel? in
|
||||
self.extractChannel(from: content.json)
|
||||
}
|
||||
@@ -147,8 +151,13 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
resource(baseURL: account.url, path: "login")
|
||||
}
|
||||
|
||||
func channel(_ id: String) -> Resource {
|
||||
resource(baseURL: account.url, path: "channel/\(id)")
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data: String?) -> Resource {
|
||||
if contentType == .videos {
|
||||
return resource(baseURL: account.url, path: "channel/\(id)")
|
||||
}
|
||||
|
||||
return resource(baseURL: account.url, path: "channels/tabs")
|
||||
.withParam("data", data)
|
||||
}
|
||||
|
||||
func channelByName(_ name: String) -> Resource? {
|
||||
@@ -160,7 +169,7 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
}
|
||||
|
||||
func channelVideos(_ id: String) -> Resource {
|
||||
channel(id)
|
||||
channel(id, contentType: .videos)
|
||||
}
|
||||
|
||||
func channelPlaylist(_ id: String) -> Resource? {
|
||||
@@ -385,12 +394,25 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
attributes["avatar"]?.url ??
|
||||
attributes["thumbnail"]?.url
|
||||
|
||||
let tabs = attributes["tabs"]?.arrayValue.compactMap { tab in
|
||||
let name = tab["name"].string
|
||||
let data = tab["data"].string
|
||||
if let name, let data, let type = Channel.ContentType(rawValue: name) {
|
||||
return Channel.Tab(contentType: type, data: data)
|
||||
}
|
||||
|
||||
return nil
|
||||
} ?? [Channel.Tab]()
|
||||
|
||||
return Channel(
|
||||
id: id,
|
||||
name: name,
|
||||
bannerURL: attributes["bannerUrl"]?.url,
|
||||
thumbnailURL: thumbnailURL,
|
||||
subscriptionsCount: subscriptionsCount,
|
||||
videos: videos
|
||||
verified: attributes["verified"]?.bool,
|
||||
videos: videos,
|
||||
tabs: tabs
|
||||
)
|
||||
}
|
||||
|
||||
|
@@ -6,7 +6,7 @@ protocol VideosAPI {
|
||||
var account: Account! { get }
|
||||
var signedIn: Bool { get }
|
||||
|
||||
func channel(_ id: String) -> Resource
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data: String?) -> Resource
|
||||
func channelByName(_ name: String) -> Resource?
|
||||
func channelByUsername(_ username: String) -> Resource?
|
||||
func channelVideos(_ id: String) -> Resource
|
||||
@@ -70,6 +70,10 @@ protocol VideosAPI {
|
||||
}
|
||||
|
||||
extension VideosAPI {
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data: String? = nil) -> Resource {
|
||||
channel(id, contentType: contentType, data: data)
|
||||
}
|
||||
|
||||
func loadDetails(
|
||||
_ item: PlayerQueueItem,
|
||||
failureHandler: ((RequestError) -> Void)? = nil,
|
||||
|
@@ -4,29 +4,56 @@ import Foundation
|
||||
import SwiftyJSON
|
||||
|
||||
struct Channel: Identifiable, Hashable {
|
||||
enum ContentType: String, Identifiable {
|
||||
case videos
|
||||
case playlists
|
||||
case livestreams
|
||||
case shorts
|
||||
case channels
|
||||
|
||||
var id: String {
|
||||
rawValue
|
||||
}
|
||||
|
||||
var contentItemType: ContentItem.ContentType {
|
||||
switch self {
|
||||
case .videos:
|
||||
return .video
|
||||
case .playlists:
|
||||
return .playlist
|
||||
case .livestreams:
|
||||
return .video
|
||||
case .shorts:
|
||||
return .video
|
||||
case .channels:
|
||||
return .channel
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Tab: Identifiable, Hashable {
|
||||
var contentType: ContentType
|
||||
var data: String
|
||||
|
||||
var id: String {
|
||||
contentType.id
|
||||
}
|
||||
}
|
||||
|
||||
var id: String
|
||||
var name: String
|
||||
var bannerURL: URL?
|
||||
var thumbnailURL: URL?
|
||||
var description = ""
|
||||
|
||||
var subscriptionsCount: Int?
|
||||
var subscriptionsText: String?
|
||||
|
||||
var totalViews: Int?
|
||||
var verified: Bool? // swiftlint:disable discouraged_optional_boolean
|
||||
|
||||
var videos = [Video]()
|
||||
|
||||
private var subscriptionsCount: Int?
|
||||
private var subscriptionsText: String?
|
||||
|
||||
init(
|
||||
id: String,
|
||||
name: String,
|
||||
thumbnailURL: URL? = nil,
|
||||
subscriptionsCount: Int? = nil,
|
||||
subscriptionsText: String? = nil,
|
||||
videos: [Video] = []
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.thumbnailURL = thumbnailURL
|
||||
self.subscriptionsCount = subscriptionsCount
|
||||
self.subscriptionsText = subscriptionsText
|
||||
self.videos = videos
|
||||
}
|
||||
var tabs = [Tab]()
|
||||
|
||||
var detailsLoaded: Bool {
|
||||
!subscriptionsString.isNil
|
||||
@@ -40,7 +67,17 @@ struct Channel: Identifiable, Hashable {
|
||||
return subscriptionsText
|
||||
}
|
||||
|
||||
var totalViewsString: String? {
|
||||
guard let totalViews, totalViews > 0 else { return nil }
|
||||
|
||||
return totalViews.formattedAsAbbreviation()
|
||||
}
|
||||
|
||||
func hash(into hasher: inout Hasher) {
|
||||
hasher.combine(id)
|
||||
}
|
||||
|
||||
var contentItem: ContentItem {
|
||||
ContentItem(channel: self)
|
||||
}
|
||||
}
|
||||
|
@@ -30,6 +30,14 @@ struct ContentItem: Identifiable {
|
||||
videos.map { ContentItem(video: $0) }
|
||||
}
|
||||
|
||||
static func array(of playlists: [ChannelPlaylist]) -> [ContentItem] {
|
||||
playlists.map { ContentItem(playlist: $0) }
|
||||
}
|
||||
|
||||
static func array(of channels: [Channel]) -> [ContentItem] {
|
||||
channels.map { ContentItem(channel: $0) }
|
||||
}
|
||||
|
||||
static func < (lhs: ContentItem, rhs: ContentItem) -> Bool {
|
||||
lhs.contentType < rhs.contentType
|
||||
}
|
||||
|
@@ -138,6 +138,7 @@ final class NavigationModel: ObservableObject {
|
||||
#endif
|
||||
|
||||
hideKeyboard()
|
||||
presentingChannel = false
|
||||
let presentingPlayer = player.presentingPlayer
|
||||
player.hide()
|
||||
|
||||
|
Reference in New Issue
Block a user