mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 10:44:06 +00:00
Use Siesta framework
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
132
Model/InvidiousAPI.swift
Normal 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)!
|
||||
}
|
||||
}
|
@@ -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()
|
||||
}
|
||||
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
19
Model/Store.swift
Normal 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
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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)
|
||||
}
|
||||
}
|
@@ -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)®ion=\(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 = []
|
||||
}
|
||||
}
|
@@ -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
|
||||
|
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user