Comments (fixes #4)

This commit is contained in:
Arkadiusz Fal
2021-12-04 20:35:41 +01:00
parent eb537676e6
commit 19a3f08336
29 changed files with 688 additions and 68 deletions

View File

@@ -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

View File

@@ -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)
)
}
}

View File

@@ -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 {

View File

@@ -38,4 +38,8 @@ enum VideosApp: String, CaseIterable {
var hasFrontendURL: Bool {
self == .piped
}
var supportsComments: Bool {
self == .piped
}
}

16
Model/Comment.swift Normal file
View 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
View 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
View File

@@ -0,0 +1,6 @@
import Foundation
struct CommentsPage {
var comments = [Comment]()
var nextPage: String?
}

View File

@@ -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)")

View File

@@ -37,6 +37,7 @@ extension PlayerModel {
}
func playItem(_ item: PlayerQueueItem, video: Video? = nil, at time: TimeInterval? = nil) {
comments.clear()
currentItem = item
if !time.isNil {