Use Siesta framework

This commit is contained in:
Arkadiusz Fal
2021-06-28 12:43:07 +02:00
parent 8d89d7cc08
commit b840974f08
26 changed files with 365 additions and 411 deletions

View File

@@ -1,26 +0,0 @@
import Foundation
import SwiftyJSON
final class ChannelVideosProvider: DataProvider {
@Published var videos = [Video]()
var channelID: String? = ""
func load() {
guard channelID != nil else {
return
}
let searchPath = "channels/\(channelID!)"
DataProvider.request(searchPath).responseJSON { response in
switch response.result {
case let .success(value):
if let channelVideos = JSON(value).dictionaryValue["latestVideos"] {
self.videos = channelVideos.arrayValue.map { Video($0) }
}
case let .failure(error):
print(error)
}
}
}
}

View File

@@ -1,25 +0,0 @@
import Alamofire
import Foundation
// swiftlint:disable:next final_class
class DataProvider: ObservableObject {
static let instance = "https://invidious.home.arekf.net"
static func proxyURLForAsset(_ url: String) -> URL? {
guard let instanceURLComponents = URLComponents(string: DataProvider.instance),
var urlComponents = URLComponents(string: url) else { return nil }
urlComponents.scheme = instanceURLComponents.scheme
urlComponents.host = instanceURLComponents.host
return urlComponents.url
}
static func request(_ path: String, headers: HTTPHeaders? = nil) -> DataRequest {
AF.request(apiURLString(path), headers: headers)
}
static func apiURLString(_ path: String) -> String {
"\(instance)/api/v1/\(path)"
}
}

132
Model/InvidiousAPI.swift Normal file
View File

@@ -0,0 +1,132 @@
import Foundation
import Siesta
import SwiftyJSON
extension TypedContentAccessors {
var json: JSON { typedContent(ifNone: JSON.null) }
}
let SwiftyJSONTransformer =
ResponseContentTransformer(transformErrors: true) { JSON($0.content as AnyObject) }
final class InvidiousAPI: Service {
static let shared = InvidiousAPI()
static let instance = "https://invidious.home.arekf.net"
static func proxyURLForAsset(_ url: String) -> URL? {
guard let instanceURLComponents = URLComponents(string: InvidiousAPI.instance),
var urlComponents = URLComponents(string: url) else { return nil }
urlComponents.scheme = instanceURLComponents.scheme
urlComponents.host = instanceURLComponents.host
return urlComponents.url
}
init() {
SiestaLog.Category.enabled = .all
super.init(baseURL: "\(InvidiousAPI.instance)/api/v1")
configure {
$0.pipeline[.parsing].add(SwiftyJSONTransformer, contentTypes: ["*/json"])
}
configure("/auth/**") {
$0.headers["Cookie"] = self.authHeader
}
configure("**", requestMethods: [.post]) {
$0.pipeline[.parsing].removeTransformers()
}
configureTransformer("/popular", requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
content.json.arrayValue.map(Video.init)
}
configureTransformer("/trending", requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
content.json.arrayValue.map(Video.init)
}
configureTransformer("/search", requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
content.json.arrayValue.map(Video.init)
}
configureTransformer("/auth/playlists", requestMethods: [.get]) { (content: Entity<JSON>) -> [Playlist] in
content.json.arrayValue.map(Playlist.init)
}
configureTransformer("/auth/feed", requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
if let feedVideos = content.json.dictionaryValue["videos"] {
return feedVideos.arrayValue.map { Video($0) }
}
return []
}
configureTransformer("/channels/*", requestMethods: [.get]) { (content: Entity<JSON>) -> [Video] in
if let channelVideos = content.json.dictionaryValue["latestVideos"] {
return channelVideos.arrayValue.map { Video($0) }
}
return []
}
configureTransformer("/videos/*", requestMethods: [.get]) { (content: Entity<JSON>) -> Video in
Video(content.json)
}
}
var authHeader: String? = "SID=\(Profile().sid)"
var popular: Resource {
resource("/popular")
}
func trending(category: TrendingCategory, country: Country) -> Resource {
resource("/trending")
.withParam("type", category.name)
.withParam("region", country.rawValue)
}
var subscriptions: Resource {
resource("/auth/feed")
}
func channelVideos(_ id: String) -> Resource {
resource("/channels/\(id)")
}
func video(_ id: String) -> Resource {
resource("/videos/\(id)")
}
var playlists: Resource {
resource("/auth/playlists")
}
func search(_ query: String) -> Resource {
resource("/search")
.withParam("q", searchQuery(query))
}
private func searchQuery(_ query: String) -> String {
var searchQuery = query
let url = URLComponents(string: query)
if url != nil,
url!.host == "youtu.be"
{
searchQuery = url!.path.replacingOccurrences(of: "/", with: "")
}
let queryItem = url?.queryItems?.first { item in item.name == "v" }
if let id = queryItem?.value {
searchQuery = id
}
return searchQuery.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!
}
}

View File

@@ -51,13 +51,13 @@ final class PlayerState: ObservableObject {
return playerItem
}
var segmentsProvider: SponsorBlockSegmentsProvider
var segmentsProvider: SponsorBlockAPI
var timeObserver: Any?
init(_ video: Video) {
self.video = video
segmentsProvider = SponsorBlockSegmentsProvider(video.id)
segmentsProvider = SponsorBlockAPI(video.id)
segmentsProvider.load()
}

View File

@@ -1,18 +0,0 @@
import Alamofire
import Foundation
import SwiftyJSON
final class PopularVideosProvider: DataProvider {
@Published var videos = [Video]()
func load() {
DataProvider.request("popular").responseJSON { response in
switch response.result {
case let .success(value):
self.videos = JSON(value).arrayValue.map { Video($0) }
case let .failure(error):
print(error)
}
}
}
}

View File

@@ -1,35 +0,0 @@
import Foundation
import SwiftyJSON
final class SearchedVideosProvider: DataProvider {
@Published var videos = [Video]()
var currentQuery: String = ""
func load(_ query: String) {
var newQuery = query
if let url = URLComponents(string: query),
let queryItem = url.queryItems?.first(where: { item in item.name == "v" }),
let id = queryItem.value
{
newQuery = id
}
if newQuery == currentQuery {
return
}
currentQuery = newQuery
let searchPath = "search?q=\(currentQuery.addingPercentEncoding(withAllowedCharacters: .alphanumerics)!)"
DataProvider.request(searchPath).responseJSON { response in
switch response.result {
case let .success(value):
self.videos = JSON(value).arrayValue.map { Video($0) }
case let .failure(error):
print(error)
}
}
}
}

View File

@@ -2,7 +2,7 @@ import Alamofire
import Foundation
import SwiftyJSON
final class SponsorBlockSegmentsProvider: ObservableObject {
final class SponsorBlockAPI: ObservableObject {
static let categories = ["sponsor", "selfpromo", "outro", "intro", "music_offtopic", "interaction"]
@Published var video: Video?
@@ -29,7 +29,7 @@ final class SponsorBlockSegmentsProvider: ObservableObject {
private var parameters: [String: String] {
[
"videoID": id,
"categories": JSON(SponsorBlockSegmentsProvider.categories).rawString(String.Encoding.utf8)!
"categories": JSON(SponsorBlockAPI.categories).rawString(String.Encoding.utf8)!
]
}
}

19
Model/Store.swift Normal file
View File

@@ -0,0 +1,19 @@
import Foundation
import Siesta
final class Store<Data>: ResourceObserver, ObservableObject {
@Published private var all: Data?
var collection: Data { all ?? ([] as! Data) }
var item: Data? { all }
func resourceChanged(_ resource: Resource, event _: ResourceEvent) {
if let items: Data = resource.typedContent() {
replace(items)
}
}
func replace(_ items: Data) {
all = items
}
}

View File

@@ -1,23 +0,0 @@
import Alamofire
import Foundation
import SwiftyJSON
final class SubscriptionVideosProvider: DataProvider {
@Published var videos = [Video]()
let profile = Profile()
func load() {
let headers = HTTPHeaders([HTTPHeader(name: "Cookie", value: "SID=\(profile.sid)")])
DataProvider.request("auth/feed", headers: headers).responseJSON { response in
switch response.result {
case let .success(value):
if let feedVideos = JSON(value).dictionaryValue["videos"] {
self.videos = feedVideos.arrayValue.map { Video($0) }
}
case let .failure(error):
print(error)
}
}
}
}

View File

@@ -1,18 +0,0 @@
import Alamofire
import Foundation
import SwiftyJSON
final class TrendingCountriesProvider: DataProvider {
@Published var countries = [Country]()
private var query: String = ""
func load(_ query: String) {
guard query != self.query else {
return
}
self.query = query
countries = Country.search(query)
}
}

View File

@@ -1,31 +0,0 @@
import Alamofire
import Foundation
import SwiftUI
import SwiftyJSON
final class TrendingVideosProvider: DataProvider {
@Published var videos = [Video]()
var currentCategory: TrendingCategory?
var currentCountry: Country?
func load(category: TrendingCategory, country: Country) {
if category == currentCategory, country == currentCountry {
return
}
DataProvider.request("trending?type=\(category.name)&region=\(country.rawValue)").responseJSON { response in
switch response.result {
case let .success(value):
self.videos = JSON(value).arrayValue.map { Video($0) }
case let .failure(error):
print(error)
}
}
currentCategory = category
currentCountry = country
videos = []
}
}

View File

@@ -108,7 +108,7 @@ final class Video: Identifiable, ObservableObject {
private func extractFormatStreams(from streams: [JSON]) -> [Stream] {
streams.map {
AudioVideoStream(
avAsset: AVURLAsset(url: DataProvider.proxyURLForAsset($0["url"].stringValue)!),
avAsset: AVURLAsset(url: InvidiousAPI.proxyURLForAsset($0["url"].stringValue)!),
resolution: StreamResolution.from(resolution: $0["resolution"].stringValue)!,
type: .stream,
encoding: $0["encoding"].stringValue
@@ -126,8 +126,8 @@ final class Video: Identifiable, ObservableObject {
return videoAssetsURLs.map {
Stream(
audioAsset: AVURLAsset(url: DataProvider.proxyURLForAsset(audioAssetURL!["url"].stringValue)!),
videoAsset: AVURLAsset(url: DataProvider.proxyURLForAsset($0["url"].stringValue)!),
audioAsset: AVURLAsset(url: InvidiousAPI.proxyURLForAsset(audioAssetURL!["url"].stringValue)!),
videoAsset: AVURLAsset(url: InvidiousAPI.proxyURLForAsset($0["url"].stringValue)!),
resolution: StreamResolution.from(resolution: $0["resolution"].stringValue)!,
type: .adaptive,
encoding: $0["encoding"].stringValue

View File

@@ -1,25 +0,0 @@
import Alamofire
import Foundation
import SwiftyJSON
final class VideoDetailsProvider: DataProvider {
@Published var video: Video?
var id: String
init(_ id: String) {
self.id = id
super.init()
}
func load() {
DataProvider.request("videos/\(id)").responseJSON { response in
switch response.result {
case let .success(value):
self.video = Video(JSON(value))
case let .failure(error):
print(error)
}
}
}
}