mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 10:44:06 +00:00
@@ -109,17 +109,22 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
content.json.arrayValue.map(self.extractChannel)
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("channels/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> Channel in
|
||||
self.extractChannel(from: content.json)
|
||||
configureTransformer(pathPattern("channels/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> ChannelPage in
|
||||
self.extractChannelPage(from: content.json, forceNotLast: true)
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("channels/*/videos"), requestMethods: [.get]) { (content: Entity<JSON>) -> ChannelPage in
|
||||
self.extractChannelPage(from: content.json)
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("channels/*/latest"), requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
|
||||
content.json.dictionaryValue["videos"]?.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)
|
||||
["latest", "playlists", "streams", "shorts", "channels", "videos"].forEach { type in
|
||||
configureTransformer(pathPattern("channels/*/\(type)"), requestMethods: [.get]) { (content: Entity<JSON>) -> ChannelPage in
|
||||
self.extractChannelPage(from: content.json)
|
||||
}
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("playlists/*"), requestMethods: [.get]) { (content: Entity<JSON>) -> ChannelPlaylist in
|
||||
@@ -266,11 +271,18 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
.onCompletion { _ in onCompletion() }
|
||||
}
|
||||
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data _: String?) -> Resource {
|
||||
if contentType == .playlists {
|
||||
return resource(baseURL: account.url, path: basePathAppending("channels/\(id)/playlists"))
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data _: String?, page: String?) -> Resource {
|
||||
if page.isNil, contentType == .videos {
|
||||
return resource(baseURL: account.url, path: basePathAppending("channels/\(id)"))
|
||||
}
|
||||
return resource(baseURL: account.url, path: basePathAppending("channels/\(id)"))
|
||||
|
||||
var resource = resource(baseURL: account.url, path: basePathAppending("channels/\(id)/\(contentType.invidiousID)"))
|
||||
|
||||
if let page, !page.isEmpty {
|
||||
resource = resource.withParam("continuation", page)
|
||||
}
|
||||
|
||||
return resource
|
||||
}
|
||||
|
||||
func channelByName(_: String) -> Resource? {
|
||||
@@ -504,6 +516,14 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
thumbnailURL = "\(accountUrlComponents.scheme ?? "https"):\(thumbnailURL)"
|
||||
}
|
||||
|
||||
let tabs = json["tabs"].arrayValue.compactMap { name in
|
||||
if let name = name.string, let type = Channel.ContentType.from(name) {
|
||||
return Channel.Tab(contentType: type, data: "")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return Channel(
|
||||
app: .invidious,
|
||||
id: json["authorId"].stringValue,
|
||||
@@ -514,7 +534,8 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
subscriptionsCount: json["subCount"].int,
|
||||
subscriptionsText: json["subCountText"].string,
|
||||
totalViews: json["totalViews"].int,
|
||||
videos: json.dictionaryValue["latestVideos"]?.arrayValue.map(extractVideo) ?? []
|
||||
videos: json.dictionaryValue["latestVideos"]?.arrayValue.map(extractVideo) ?? [],
|
||||
tabs: tabs
|
||||
)
|
||||
}
|
||||
|
||||
@@ -552,6 +573,33 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
}
|
||||
}
|
||||
|
||||
private static var contentItemsKeys = ["items", "videos", "latestVideos", "playlists", "relatedChannels"]
|
||||
|
||||
private func extractChannelPage(from json: JSON, forceNotLast: Bool = false) -> ChannelPage {
|
||||
let nextPage = json.dictionaryValue["continuation"]?.string
|
||||
var contentItems = [ContentItem]()
|
||||
|
||||
var items = [ContentItem]()
|
||||
|
||||
if let key = Self.contentItemsKeys.first(where: { json.dictionaryValue.keys.contains($0) }),
|
||||
let items = json.dictionaryValue[key]
|
||||
{
|
||||
contentItems = extractContentItems(from: items)
|
||||
}
|
||||
|
||||
var last = false
|
||||
if !forceNotLast {
|
||||
last = nextPage?.isEmpty ?? true
|
||||
}
|
||||
|
||||
return ChannelPage(
|
||||
results: contentItems,
|
||||
channel: extractChannel(from: json),
|
||||
nextPage: nextPage,
|
||||
last: last
|
||||
)
|
||||
}
|
||||
|
||||
private func extractStreams(from json: JSON) -> [Stream] {
|
||||
let hls = extractHLSStreams(from: json)
|
||||
if json["liveNow"].boolValue {
|
||||
@@ -668,4 +716,33 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func extractContentItems(from json: JSON) -> [ContentItem] {
|
||||
json.arrayValue.compactMap { extractContentItem(from: $0) }
|
||||
}
|
||||
|
||||
private func extractContentItem(from json: JSON) -> ContentItem? {
|
||||
let type = json.dictionaryValue["type"]?.string
|
||||
|
||||
if type == "channel" {
|
||||
return ContentItem(channel: extractChannel(from: json))
|
||||
} else if type == "playlist" {
|
||||
return ContentItem(playlist: extractChannelPlaylist(from: json))
|
||||
} else if type == "video" {
|
||||
return ContentItem(video: extractVideo(from: json))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
extension Channel.ContentType {
|
||||
var invidiousID: String {
|
||||
switch self {
|
||||
case .livestreams:
|
||||
return "streams"
|
||||
default:
|
||||
return rawValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -284,7 +284,7 @@ final class PeerTubeAPI: Service, ObservableObject, VideosAPI {
|
||||
.onCompletion { _ in onCompletion() }
|
||||
}
|
||||
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data _: String?) -> Resource {
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data _: String?, page _: String?) -> Resource {
|
||||
if contentType == .playlists {
|
||||
return resource(baseURL: account.url, path: basePathAppending("channels/\(id)/playlists"))
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ import SwiftyJSON
|
||||
final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
static var disallowedVideoCodecs = ["av01"]
|
||||
static var authorizedEndpoints = ["subscriptions", "subscribe", "unsubscribe", "user/playlists"]
|
||||
static var contentItemsKeys = ["items", "content", "relatedStreams"]
|
||||
|
||||
@Published var account: Account!
|
||||
|
||||
@@ -40,8 +41,25 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
$0.headers["Authorization"] = self.account.token
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("channel/*")) { (content: Entity<JSON>) -> Channel? in
|
||||
self.extractChannel(from: content.json)
|
||||
configureTransformer(pathPattern("channel/*")) { (content: Entity<JSON>) -> ChannelPage in
|
||||
let nextPage = content.json.dictionaryValue["nextpage"]?.string
|
||||
let channel = self.extractChannel(from: content.json)
|
||||
return ChannelPage(
|
||||
results: self.extractContentItems(from: self.contentItemsDictionary(from: content.json)),
|
||||
channel: channel,
|
||||
nextPage: nextPage,
|
||||
last: nextPage.isNil
|
||||
)
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("/nextpage/channel/*")) { (content: Entity<JSON>) -> ChannelPage in
|
||||
let nextPage = content.json.dictionaryValue["nextpage"]?.string
|
||||
return ChannelPage(
|
||||
results: self.extractContentItems(from: self.contentItemsDictionary(from: content.json)),
|
||||
channel: self.extractChannel(from: content.json),
|
||||
nextPage: nextPage,
|
||||
last: nextPage.isNil
|
||||
)
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("channels/tabs*")) { (content: Entity<JSON>) -> [ContentItem] in
|
||||
@@ -159,13 +177,23 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
resource(baseURL: account.url, path: "login")
|
||||
}
|
||||
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data: String?) -> Resource {
|
||||
if contentType == .videos {
|
||||
return resource(baseURL: account.url, path: "channel/\(id)")
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data: String?, page: String?) -> Resource {
|
||||
let path = page.isNil ? "channel" : "nextpage/channel"
|
||||
|
||||
var channel: Siesta.Resource
|
||||
|
||||
if contentType == .videos || data.isNil {
|
||||
channel = resource(baseURL: account.url, path: "\(path)/\(id)")
|
||||
} else {
|
||||
channel = resource(baseURL: account.url, path: "channels/tabs")
|
||||
.withParam("data", data)
|
||||
}
|
||||
|
||||
return resource(baseURL: account.url, path: "channels/tabs")
|
||||
.withParam("data", data)
|
||||
if let page, !page.isEmpty {
|
||||
channel = channel.withParam("nextpage", page)
|
||||
}
|
||||
|
||||
return channel
|
||||
}
|
||||
|
||||
func channelByName(_ name: String) -> Resource? {
|
||||
@@ -700,4 +728,14 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
return Chapter(title: title, image: image, start: start)
|
||||
}
|
||||
}
|
||||
|
||||
private func contentItemsDictionary(from content: JSON) -> JSON {
|
||||
if let key = Self.contentItemsKeys.first(where: { content.dictionaryValue.keys.contains($0) }),
|
||||
let items = content.dictionaryValue[key]
|
||||
{
|
||||
return items
|
||||
}
|
||||
|
||||
return .null
|
||||
}
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ protocol VideosAPI {
|
||||
|
||||
static func withAnonymousAccountForInstanceURL(_ url: URL) -> Self
|
||||
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data: String?) -> Resource
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data: String?, page: String?) -> Resource
|
||||
func channelByName(_ name: String) -> Resource?
|
||||
func channelByUsername(_ username: String) -> Resource?
|
||||
func channelVideos(_ id: String) -> Resource
|
||||
@@ -72,8 +72,8 @@ protocol VideosAPI {
|
||||
}
|
||||
|
||||
extension VideosAPI {
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data: String? = nil) -> Resource {
|
||||
channel(id, contentType: contentType, data: data)
|
||||
func channel(_ id: String, contentType: Channel.ContentType, data: String? = nil, page: String? = nil) -> Resource {
|
||||
channel(id, contentType: contentType, data: data, page: page)
|
||||
}
|
||||
|
||||
func loadDetails(
|
||||
|
Reference in New Issue
Block a user