Add support for Piped accounts and subscriptions

This commit is contained in:
Arkadiusz Fal
2021-11-15 00:06:01 +01:00
parent a70d4f3b38
commit 0e3effd512
23 changed files with 253 additions and 81 deletions

View File

@@ -70,7 +70,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
func configure() {
configure {
if !self.account.sid.isEmpty {
if !self.account.username.isEmpty {
$0.headers["Cookie"] = self.cookieHeader
}
$0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
@@ -160,7 +160,7 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
}
private var cookieHeader: String {
"SID=\(account.sid)"
"SID=\(account.username)"
}
var popular: Resource? {
@@ -185,8 +185,18 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
}
func channelSubscription(_ id: String) -> Resource? {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions")).child(id)
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
.child(channelID)
.request(.post)
.onCompletion { _ in onCompletion() }
}
func unsubscribe(_ channelID: String, onCompletion: @escaping () -> Void) {
resource(baseURL: account.url, path: basePathAppending("auth/subscriptions"))
.child(channelID)
.request(.delete)
.onCompletion { _ in onCompletion() }
}
func channel(_ id: String) -> Resource {
@@ -202,7 +212,11 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
}
var playlists: Resource? {
resource(baseURL: account.url, path: basePathAppending("auth/playlists"))
if account.isNil || account.anonymous {
return nil
}
return resource(baseURL: account.url, path: basePathAppending("auth/playlists"))
}
func playlist(_ id: String) -> Resource? {

View File

@@ -4,6 +4,8 @@ import Siesta
import SwiftyJSON
final class PipedAPI: Service, ObservableObject, VideosAPI {
static var authorizedEndpoints = ["subscriptions", "subscribe", "unsubscribe"]
@Published var account: Account!
var anonymousAccount: Account {
@@ -27,10 +29,16 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
}
func configure() {
invalidateConfiguration()
configure {
$0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
}
configure(whenURLMatches: { url in self.needsAuthorization(url) }) {
$0.headers["Authorization"] = self.account.token
}
configureTransformer(pathPattern("channel/*")) { (content: Entity<JSON>) -> Channel? in
PipedAPI.extractChannel(from: content.json)
}
@@ -54,6 +62,38 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
configureTransformer(pathPattern("suggestions")) { (content: Entity<JSON>) -> [String] in
content.json.arrayValue.map(String.init)
}
configureTransformer(pathPattern("subscriptions")) { (content: Entity<JSON>) -> [Channel] in
content.json.arrayValue.map { PipedAPI.extractChannel(from: $0)! }
}
configureTransformer(pathPattern("feed")) { (content: Entity<JSON>) -> [Video] in
content.json.arrayValue.map { PipedAPI.extractVideo(from: $0)! }
}
if account.token.isNil {
updateToken()
}
}
func needsAuthorization(_ url: URL) -> Bool {
PipedAPI.authorizedEndpoints.contains { url.absoluteString.contains($0) }
}
@discardableResult func updateToken() -> Request {
account.token = nil
return login.request(
.post,
json: ["username": account.username, "password": account.password]
)
.onSuccess { response in
self.account.token = response.json.dictionaryValue["token"]?.string ?? ""
self.configure()
}
}
var login: Resource {
resource(baseURL: account.url, path: "login")
}
func channel(_ id: String) -> Resource {
@@ -88,15 +128,34 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
resource(baseURL: account.instance.apiURL, path: "streams/\(id)")
}
var signedIn: Bool { false }
var signedIn: Bool {
!account.anonymous && !(account.token?.isEmpty ?? true)
}
var subscriptions: Resource? {
resource(baseURL: account.instance.apiURL, path: "subscriptions")
}
var feed: Resource? {
resource(baseURL: account.instance.apiURL, path: "feed")
.withParam("authToken", account.token)
}
var subscriptions: Resource? { nil }
var feed: Resource? { nil }
var home: Resource? { nil }
var popular: Resource? { nil }
var playlists: Resource? { nil }
func channelSubscription(_: String) -> Resource? { nil }
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
resource(baseURL: account.instance.apiURL, path: "subscribe")
.request(.post, json: ["channelId": channelID])
.onCompletion { _ in onCompletion() }
}
func unsubscribe(_ channelID: String, onCompletion: @escaping () -> Void = {}) {
resource(baseURL: account.instance.apiURL, path: "unsubscribe")
.request(.post, json: ["channelId": channelID])
.onCompletion { _ in onCompletion() }
}
func playlist(_: String) -> Resource? { nil }
func playlistVideo(_: String, _: String) -> Resource? { nil }
@@ -211,13 +270,15 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
}
let author = details["uploaderName"]?.stringValue ?? details["uploader"]!.stringValue
let published = (details["uploadedDate"] ?? details["uploadDate"])?.stringValue ??
(details["uploaded"]!.double! / 1000).formattedAsRelativeTime()!
return Video(
videoID: PipedAPI.extractID(from: content),
title: details["title"]!.stringValue,
author: author,
length: details["duration"]!.doubleValue,
published: details["uploadedDate"]?.stringValue ?? details["uploadDate"]!.stringValue,
published: published,
views: details["views"]!.intValue,
description: PipedAPI.extractDescription(from: content),
channel: Channel(id: channelId, name: author),

View File

@@ -20,7 +20,8 @@ protocol VideosAPI {
var popular: Resource? { get }
var playlists: Resource? { get }
func channelSubscription(_ id: String) -> Resource?
func subscribe(_ channelID: String, onCompletion: @escaping () -> Void)
func unsubscribe(_ channelID: String, onCompletion: @escaping () -> Void)
func playlist(_ id: String) -> Resource?
func playlistVideo(_ playlistID: String, _ videoID: String) -> Resource?

View File

@@ -8,7 +8,11 @@ enum VideosApp: String, CaseIterable {
}
var supportsAccounts: Bool {
self == .invidious
true
}
var accountsUsePassword: Bool {
self == .piped
}
var supportsPopular: Bool {