mirror of
https://github.com/yattee/yattee.git
synced 2025-08-06 10:44:06 +00:00
Comments (fixes #4)
This commit is contained in:
@@ -30,7 +30,6 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
signedIn = false
|
||||
|
||||
configure()
|
||||
validate()
|
||||
}
|
||||
|
||||
func validate() {
|
||||
@@ -257,6 +256,8 @@ final class InvidiousAPI: Service, ObservableObject, VideosAPI {
|
||||
.withParam("q", query.lowercased())
|
||||
}
|
||||
|
||||
func comments(_: Video.ID, page _: String?) -> Resource? { nil }
|
||||
|
||||
private func searchQuery(_ query: String) -> String {
|
||||
var searchQuery = query
|
||||
|
||||
|
@@ -71,6 +71,13 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
content.json.arrayValue.map { PipedAPI.extractVideo(from: $0)! }
|
||||
}
|
||||
|
||||
configureTransformer(pathPattern("comments/*")) { (content: Entity<JSON>) -> CommentsPage in
|
||||
let comments = content.json.dictionaryValue["comments"]?.arrayValue.map { PipedAPI.extractComment(from: $0)! } ?? []
|
||||
let nextPage = content.json.dictionaryValue["nextpage"]?.stringValue
|
||||
|
||||
return CommentsPage(comments: comments, nextPage: nextPage)
|
||||
}
|
||||
|
||||
if account.token.isNil {
|
||||
updateToken()
|
||||
}
|
||||
@@ -80,9 +87,14 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
PipedAPI.authorizedEndpoints.contains { url.absoluteString.contains($0) }
|
||||
}
|
||||
|
||||
@discardableResult func updateToken() -> Request {
|
||||
func updateToken() {
|
||||
guard !account.anonymous else {
|
||||
return
|
||||
}
|
||||
|
||||
account.token = nil
|
||||
return login.request(
|
||||
|
||||
login.request(
|
||||
.post,
|
||||
json: ["username": account.username, "password": account.password]
|
||||
)
|
||||
@@ -161,6 +173,17 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
func playlistVideo(_: String, _: String) -> Resource? { nil }
|
||||
func playlistVideos(_: String) -> Resource? { nil }
|
||||
|
||||
func comments(_ id: Video.ID, page: String?) -> Resource? {
|
||||
let path = page.isNil ? "comments/\(id)" : "nextpage/comments/\(id)"
|
||||
let resource = resource(baseURL: account.url, path: path)
|
||||
|
||||
if page.isNil {
|
||||
return resource
|
||||
}
|
||||
|
||||
return resource.withParam("nextpage", page)
|
||||
}
|
||||
|
||||
private func pathPattern(_ path: String) -> String {
|
||||
"**\(path)"
|
||||
}
|
||||
@@ -395,4 +418,23 @@ final class PipedAPI: Service, ObservableObject, VideosAPI {
|
||||
.arrayValue
|
||||
.filter { $0.dictionaryValue["format"] == "MPEG_4" } ?? []
|
||||
}
|
||||
|
||||
private static func extractComment(from content: JSON) -> Comment? {
|
||||
let details = content.dictionaryValue
|
||||
let author = details["author"]?.stringValue ?? ""
|
||||
let commentorUrl = details["commentorUrl"]?.stringValue
|
||||
let channelId = commentorUrl?.components(separatedBy: "/")[2] ?? ""
|
||||
return Comment(
|
||||
id: details["commentId"]?.stringValue ?? UUID().uuidString,
|
||||
author: author,
|
||||
authorAvatarURL: details["thumbnail"]?.stringValue ?? "",
|
||||
time: details["commentedTime"]?.stringValue ?? "",
|
||||
pinned: details["pinned"]?.boolValue ?? false,
|
||||
hearted: details["hearted"]?.boolValue ?? false,
|
||||
likeCount: details["likeCount"]?.intValue ?? 0,
|
||||
text: details["commentText"]?.stringValue ?? "",
|
||||
repliesPage: details["repliesPage"]?.stringValue,
|
||||
channel: Channel(id: channelId, name: author)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -31,6 +31,8 @@ protocol VideosAPI {
|
||||
|
||||
func loadDetails(_ item: PlayerQueueItem, completionHandler: @escaping (PlayerQueueItem) -> Void)
|
||||
func shareURL(_ item: ContentItem, frontendHost: String?, time: CMTime?) -> URL?
|
||||
|
||||
func comments(_ id: Video.ID, page: String?) -> Resource?
|
||||
}
|
||||
|
||||
extension VideosAPI {
|
||||
|
@@ -38,4 +38,8 @@ enum VideosApp: String, CaseIterable {
|
||||
var hasFrontendURL: Bool {
|
||||
self == .piped
|
||||
}
|
||||
|
||||
var supportsComments: Bool {
|
||||
self == .piped
|
||||
}
|
||||
}
|
||||
|
16
Model/Comment.swift
Normal file
16
Model/Comment.swift
Normal file
@@ -0,0 +1,16 @@
|
||||
struct Comment: Identifiable, Equatable {
|
||||
let id: String
|
||||
let author: String
|
||||
let authorAvatarURL: String
|
||||
let time: String
|
||||
let pinned: Bool
|
||||
let hearted: Bool
|
||||
var likeCount: Int
|
||||
let text: String
|
||||
let repliesPage: String?
|
||||
let channel: Channel
|
||||
|
||||
var hasReplies: Bool {
|
||||
!(repliesPage?.isEmpty ?? true)
|
||||
}
|
||||
}
|
79
Model/CommentsModel.swift
Normal file
79
Model/CommentsModel.swift
Normal file
@@ -0,0 +1,79 @@
|
||||
import Defaults
|
||||
import Foundation
|
||||
import SwiftyJSON
|
||||
|
||||
final class CommentsModel: ObservableObject {
|
||||
@Published var all = [Comment]()
|
||||
@Published var replies = [Comment]()
|
||||
|
||||
@Published var nextPage: String?
|
||||
@Published var firstPage = true
|
||||
|
||||
@Published var loaded = false
|
||||
|
||||
var accounts: AccountsModel!
|
||||
var player: PlayerModel!
|
||||
|
||||
static var enabled: Bool {
|
||||
!Defaults[.commentsInstanceID].isNil
|
||||
}
|
||||
|
||||
var nextPageAvailable: Bool {
|
||||
!(nextPage?.isEmpty ?? true)
|
||||
}
|
||||
|
||||
func load(page: String? = nil) {
|
||||
guard Self.enabled else {
|
||||
return
|
||||
}
|
||||
|
||||
loaded = false
|
||||
clear()
|
||||
|
||||
guard let instance = InstancesModel.find(Defaults[.commentsInstanceID]),
|
||||
!player.currentVideo.isNil
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
firstPage = page.isNil || page!.isEmpty
|
||||
|
||||
PipedAPI(account: instance.anonymousAccount).comments(player.currentVideo!.videoID, page: page)?
|
||||
.load()
|
||||
.onSuccess { [weak self] response in
|
||||
if let page: CommentsPage = response.typedContent() {
|
||||
self?.all = page.comments
|
||||
self?.nextPage = page.nextPage
|
||||
}
|
||||
}
|
||||
.onCompletion { [weak self] _ in
|
||||
self?.loaded = true
|
||||
}
|
||||
}
|
||||
|
||||
func loadNextPage() {
|
||||
load(page: nextPage)
|
||||
}
|
||||
|
||||
func loadReplies(page: String) {
|
||||
guard !player.currentVideo.isNil else {
|
||||
return
|
||||
}
|
||||
|
||||
replies = []
|
||||
|
||||
accounts.api.comments(player.currentVideo!.videoID, page: page)?.load().onSuccess { response in
|
||||
if let page: CommentsPage = response.typedContent() {
|
||||
self.replies = page.comments
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func clear() {
|
||||
all = []
|
||||
replies = []
|
||||
firstPage = true
|
||||
nextPage = nil
|
||||
loaded = false
|
||||
}
|
||||
}
|
6
Model/CommentsPage.swift
Normal file
6
Model/CommentsPage.swift
Normal file
@@ -0,0 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
struct CommentsPage {
|
||||
var comments = [Comment]()
|
||||
var nextPage: String?
|
||||
}
|
@@ -43,6 +43,7 @@ final class PlayerModel: ObservableObject {
|
||||
@Published var restoredSegments = [Segment]()
|
||||
|
||||
var accounts: AccountsModel
|
||||
var comments: CommentsModel
|
||||
|
||||
var composition = AVMutableComposition()
|
||||
var loadedCompositionAssets = [AVMediaType]()
|
||||
@@ -67,8 +68,9 @@ final class PlayerModel: ObservableObject {
|
||||
#endif
|
||||
}}
|
||||
|
||||
init(accounts: AccountsModel? = nil, instances _: InstancesModel? = nil) {
|
||||
init(accounts: AccountsModel? = nil, comments: CommentsModel? = nil) {
|
||||
self.accounts = accounts ?? AccountsModel()
|
||||
self.comments = comments ?? CommentsModel()
|
||||
|
||||
addItemDidPlayToEndTimeObserver()
|
||||
addFrequentTimeObserver()
|
||||
@@ -138,6 +140,7 @@ final class PlayerModel: ObservableObject {
|
||||
playerError = nil
|
||||
resetSegments()
|
||||
sponsorBlock.loadSegments(videoID: video.videoID, categories: Defaults[.sponsorBlockCategories])
|
||||
comments.load()
|
||||
|
||||
if let url = stream.singleAssetURL {
|
||||
logger.info("playing stream with one asset\(stream.kind == .hls ? " (HLS)" : ""): \(url)")
|
||||
|
@@ -37,6 +37,7 @@ extension PlayerModel {
|
||||
}
|
||||
|
||||
func playItem(_ item: PlayerQueueItem, video: Video? = nil, at time: TimeInterval? = nil) {
|
||||
comments.clear()
|
||||
currentItem = item
|
||||
|
||||
if !time.isNil {
|
||||
|
Reference in New Issue
Block a user