2020-09-15 01:39:35 +00:00
|
|
|
// Copyright © 2020 Metabolist. All rights reserved.
|
|
|
|
|
|
|
|
import Combine
|
|
|
|
import DB
|
|
|
|
import Foundation
|
|
|
|
import Mastodon
|
|
|
|
import MastodonAPI
|
|
|
|
|
2020-09-25 05:39:06 +00:00
|
|
|
public enum Navigation {
|
2020-09-15 01:39:35 +00:00
|
|
|
case url(URL)
|
2020-09-25 05:39:06 +00:00
|
|
|
case statusList(StatusListService)
|
|
|
|
case accountStatuses(AccountStatusesService)
|
2020-09-26 06:37:30 +00:00
|
|
|
case webfingerStart
|
|
|
|
case webfingerEnd
|
2020-09-15 01:39:35 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 05:39:06 +00:00
|
|
|
public struct NavigationService {
|
2020-09-15 01:39:35 +00:00
|
|
|
private let status: Status?
|
|
|
|
private let mastodonAPIClient: MastodonAPIClient
|
|
|
|
private let contentDatabase: ContentDatabase
|
|
|
|
|
|
|
|
init(status: Status?, mastodonAPIClient: MastodonAPIClient, contentDatabase: ContentDatabase) {
|
|
|
|
self.status = status
|
|
|
|
self.mastodonAPIClient = mastodonAPIClient
|
|
|
|
self.contentDatabase = contentDatabase
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-25 05:39:06 +00:00
|
|
|
public extension NavigationService {
|
|
|
|
func item(url: URL) -> AnyPublisher<Navigation, Never> {
|
2020-09-15 01:39:35 +00:00
|
|
|
if let tag = tag(url: url) {
|
2020-09-25 05:39:06 +00:00
|
|
|
return Just(
|
|
|
|
.statusList(
|
|
|
|
StatusListService(
|
|
|
|
timeline: .tag(tag),
|
|
|
|
mastodonAPIClient: mastodonAPIClient,
|
|
|
|
contentDatabase: contentDatabase)))
|
|
|
|
.eraseToAnyPublisher()
|
2020-09-15 01:39:35 +00:00
|
|
|
} else if let accountID = accountID(url: url) {
|
2020-09-25 05:39:06 +00:00
|
|
|
return Just(.accountStatuses(accountStatusesService(id: accountID))).eraseToAnyPublisher()
|
2020-09-15 01:39:35 +00:00
|
|
|
} else if mastodonAPIClient.instanceURL.host == url.host, let statusID = url.statusID {
|
2020-09-25 05:39:06 +00:00
|
|
|
return Just(
|
|
|
|
.statusList(
|
|
|
|
StatusListService(
|
|
|
|
statusID: statusID,
|
|
|
|
mastodonAPIClient: mastodonAPIClient,
|
|
|
|
contentDatabase: contentDatabase)))
|
|
|
|
.eraseToAnyPublisher()
|
2020-09-15 01:39:35 +00:00
|
|
|
}
|
|
|
|
|
2020-09-26 06:37:30 +00:00
|
|
|
if url.shouldWebfinger {
|
|
|
|
return webfinger(url: url)
|
|
|
|
} else {
|
|
|
|
return Just(.url(url)).eraseToAnyPublisher()
|
|
|
|
}
|
2020-09-15 01:39:35 +00:00
|
|
|
}
|
2020-09-25 05:39:06 +00:00
|
|
|
|
|
|
|
func contextStatusListService(id: String) -> StatusListService {
|
|
|
|
StatusListService(statusID: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
|
|
|
}
|
|
|
|
|
|
|
|
func accountStatusesService(id: String) -> AccountStatusesService {
|
|
|
|
AccountStatusesService(id: id, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
|
|
|
}
|
|
|
|
|
2020-09-27 00:22:15 +00:00
|
|
|
func accountStatusesService(account: Account) -> AccountStatusesService {
|
|
|
|
AccountStatusesService(account: account, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
|
|
|
}
|
|
|
|
|
2020-09-25 05:39:06 +00:00
|
|
|
func statusService(status: Status) -> StatusService {
|
|
|
|
StatusService(status: status, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
|
|
|
}
|
|
|
|
|
|
|
|
func accountService(account: Account) -> AccountService {
|
|
|
|
AccountService(account: account, mastodonAPIClient: mastodonAPIClient, contentDatabase: contentDatabase)
|
|
|
|
}
|
2020-09-15 01:39:35 +00:00
|
|
|
}
|
|
|
|
|
2020-09-25 05:39:06 +00:00
|
|
|
private extension NavigationService {
|
2020-09-15 01:39:35 +00:00
|
|
|
func tag(url: URL) -> String? {
|
|
|
|
if status?.tags.first(where: { $0.url.path.lowercased() == url.path.lowercased() }) != nil {
|
|
|
|
return url.lastPathComponent
|
|
|
|
} else if
|
|
|
|
mastodonAPIClient.instanceURL.host == url.host {
|
|
|
|
return url.tag
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func accountID(url: URL) -> String? {
|
|
|
|
if let mentionID = status?.mentions.first(where: { $0.url.path.lowercased() == url.path.lowercased() })?.id {
|
|
|
|
return mentionID
|
|
|
|
} else if
|
|
|
|
mastodonAPIClient.instanceURL.host == url.host {
|
|
|
|
return url.accountID
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
2020-09-26 06:37:30 +00:00
|
|
|
|
|
|
|
func webfinger(url: URL) -> AnyPublisher<Navigation, Never> {
|
|
|
|
let navigationSubject = PassthroughSubject<Navigation, Never>()
|
|
|
|
|
|
|
|
let request = mastodonAPIClient.request(ResultsEndpoint.search(query: url.absoluteString, resolve: true))
|
|
|
|
.handleEvents(
|
|
|
|
receiveSubscription: { _ in navigationSubject.send(.webfingerStart) },
|
|
|
|
receiveCompletion: { _ in navigationSubject.send(.webfingerEnd) })
|
|
|
|
.map { results -> Navigation in
|
|
|
|
if let tag = results.hashtags.first {
|
|
|
|
return .statusList(
|
|
|
|
StatusListService(
|
|
|
|
timeline: .tag(tag.name),
|
|
|
|
mastodonAPIClient: mastodonAPIClient,
|
|
|
|
contentDatabase: contentDatabase))
|
|
|
|
} else if let account = results.accounts.first {
|
2020-09-27 00:22:15 +00:00
|
|
|
return .accountStatuses(accountStatusesService(account: account))
|
2020-09-26 06:37:30 +00:00
|
|
|
} else if let status = results.statuses.first {
|
|
|
|
return .statusList(
|
|
|
|
StatusListService(
|
|
|
|
statusID: status.id,
|
|
|
|
mastodonAPIClient: mastodonAPIClient,
|
|
|
|
contentDatabase: contentDatabase))
|
|
|
|
} else {
|
|
|
|
return .url(url)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
.replaceError(with: .url(url))
|
|
|
|
|
|
|
|
return navigationSubject.merge(with: request).eraseToAnyPublisher()
|
|
|
|
}
|
2020-09-15 01:39:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
private extension URL {
|
|
|
|
var isAccountURL: Bool {
|
|
|
|
(pathComponents.count == 2 && pathComponents[1].starts(with: "@"))
|
|
|
|
|| (pathComponents.count == 3 && pathComponents[0...1] == ["/", "users"])
|
|
|
|
}
|
|
|
|
|
|
|
|
var accountID: String? {
|
|
|
|
if let accountID = pathComponents.last, pathComponents == ["/", "web", "accounts", accountID] {
|
|
|
|
return accountID
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var statusID: String? {
|
|
|
|
guard let statusID = pathComponents.last else { return nil }
|
|
|
|
|
|
|
|
if pathComponents.count == 3, pathComponents[1].starts(with: "@") {
|
|
|
|
return statusID
|
|
|
|
} else if pathComponents == ["/", "web", "statuses", statusID] {
|
|
|
|
return statusID
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var tag: String? {
|
|
|
|
if let tag = pathComponents.last, pathComponents == ["/", "tags", tag] {
|
|
|
|
return tag
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var shouldWebfinger: Bool {
|
|
|
|
isAccountURL || accountID != nil || statusID != nil || tag != nil
|
|
|
|
}
|
|
|
|
}
|