Remove more Reddit references, including code in the Account framework.
This commit is contained in:
parent
de723d9ed1
commit
cd45b88cfb
@ -1,445 +0,0 @@
|
||||
//
|
||||
// RedditFeedProvider.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 5/2/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import OAuthSwift
|
||||
import Secrets
|
||||
import RSCore
|
||||
import RSParser
|
||||
import RSWeb
|
||||
|
||||
public enum RedditFeedProviderError: LocalizedError {
|
||||
case rateLimitExceeded
|
||||
case accessFailure(Error)
|
||||
case unknown
|
||||
|
||||
public var errorDescription: String? {
|
||||
switch self {
|
||||
case .rateLimitExceeded:
|
||||
return NSLocalizedString("Reddit API rate limit has been exceeded. Please wait a short time and try again.", comment: "Rate Limit")
|
||||
case .accessFailure(let error):
|
||||
return NSLocalizedString("An attempt to access your Reddit feed(s) failed.\n\nIf this problem persists, please deactivate and reactivate the Reddit extension to fix this problem.\n\n\(error.localizedDescription)", comment: "Reddit Access")
|
||||
case .unknown:
|
||||
return NSLocalizedString("A Reddit Feed Provider error has occurred.", comment: "Unknown error")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum RedditFeedType: Int {
|
||||
case home = 0
|
||||
case popular = 1
|
||||
case all = 2
|
||||
case subreddit = 3
|
||||
}
|
||||
|
||||
public final class RedditFeedProvider: FeedProvider, RedditFeedProviderTokenRefreshOperationDelegate {
|
||||
|
||||
var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "RedditFeedProvider")
|
||||
|
||||
private static let homeURL = "https://www.reddit.com"
|
||||
private static let server = "www.reddit.com"
|
||||
private static let apiBase = "https://oauth.reddit.com"
|
||||
private static let userAgentHeaders = UserAgent.headers() as! [String: String]
|
||||
|
||||
private static let pseudoSubreddits = [
|
||||
"popular": NSLocalizedString("Popular", comment: "Popular"),
|
||||
"all": NSLocalizedString("All", comment: "All")
|
||||
]
|
||||
|
||||
private let operationQueue = MainThreadOperationQueue()
|
||||
private var parsingQueue = DispatchQueue(label: "RedditFeedProvider parse queue")
|
||||
|
||||
public var username: String?
|
||||
|
||||
var oauthTokenLastRefresh: Date?
|
||||
var oauthToken: String
|
||||
var oauthRefreshToken: String
|
||||
|
||||
var oauthSwift: OAuth2Swift?
|
||||
private var client: OAuthSwiftClient? {
|
||||
return oauthSwift?.client
|
||||
}
|
||||
|
||||
private var rateLimitRemaining: Int?
|
||||
private var rateLimitReset: Date?
|
||||
|
||||
public convenience init?(username: String) {
|
||||
guard let tokenCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthAccessToken, server: Self.server, username: username),
|
||||
let refreshTokenCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthRefreshToken, server: Self.server, username: username) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.init(oauthToken: tokenCredentials.secret, oauthRefreshToken: refreshTokenCredentials.secret)
|
||||
self.username = username
|
||||
}
|
||||
|
||||
init(oauthToken: String, oauthRefreshToken: String) {
|
||||
self.oauthToken = oauthToken
|
||||
self.oauthRefreshToken = oauthRefreshToken
|
||||
oauthSwift = Self.oauth2Swift
|
||||
oauthSwift!.client.credential.oauthToken = oauthToken
|
||||
oauthSwift!.client.credential.oauthRefreshToken = oauthRefreshToken
|
||||
}
|
||||
|
||||
public func ability(_ urlComponents: URLComponents) -> FeedProviderAbility {
|
||||
guard urlComponents.host?.hasSuffix("reddit.com") ?? false else {
|
||||
return .none
|
||||
}
|
||||
|
||||
if let username = urlComponents.user {
|
||||
if username == username {
|
||||
return .owner
|
||||
} else {
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
return .available
|
||||
}
|
||||
|
||||
public func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result<String, Error>) -> Void) {
|
||||
guard urlComponents.path.hasPrefix("/r/") else {
|
||||
completion(.failure(RedditFeedProviderError.unknown))
|
||||
return
|
||||
}
|
||||
|
||||
subreddit(urlComponents) { result in
|
||||
switch result {
|
||||
case .success(let subreddit):
|
||||
if let iconURL = subreddit.data?.iconURL, !iconURL.isEmpty {
|
||||
completion(.success(iconURL))
|
||||
} else {
|
||||
completion(.failure(RedditFeedProviderError.unknown))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func metaData(_ urlComponents: URLComponents, completion: @escaping (Result<FeedProviderFeedMetaData, Error>) -> Void) {
|
||||
let path = urlComponents.path
|
||||
|
||||
// Reddit Home
|
||||
let splitPath = path.split(separator: "/")
|
||||
if path == "" || path == "/" || (splitPath.count == 1 && RedditSort(rawValue: String(splitPath[0])) != nil) {
|
||||
let name = NSLocalizedString("Reddit Home", comment: "Reddit Home")
|
||||
let metaData = FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL)
|
||||
completion(.success(metaData))
|
||||
return
|
||||
}
|
||||
|
||||
// Subreddits
|
||||
guard splitPath.count > 1, splitPath.count < 4, splitPath[0] == "r" else {
|
||||
completion(.failure(RedditFeedProviderError.unknown))
|
||||
return
|
||||
}
|
||||
|
||||
if splitPath.count == 3 && RedditSort(rawValue: String(splitPath[2])) == nil {
|
||||
completion(.failure(RedditFeedProviderError.unknown))
|
||||
return
|
||||
}
|
||||
|
||||
let homePageURL = "https://www.reddit.com/\(splitPath[0])/\(splitPath[1])"
|
||||
|
||||
// Reddit Popular, Reddit All, etc...
|
||||
if let subredditName = Self.pseudoSubreddits[String(splitPath[1])] {
|
||||
let localized = NSLocalizedString("Reddit %@", comment: "Reddit")
|
||||
let name = NSString.localizedStringWithFormat(localized as NSString, subredditName) as String
|
||||
let metaData = FeedProviderFeedMetaData(name: name, homePageURL: homePageURL)
|
||||
completion(.success(metaData))
|
||||
return
|
||||
}
|
||||
|
||||
subreddit(urlComponents) { result in
|
||||
switch result {
|
||||
case .success(let subreddit):
|
||||
if let displayName = subreddit.data?.displayName {
|
||||
completion(.success(FeedProviderFeedMetaData(name: displayName, homePageURL: homePageURL)))
|
||||
} else {
|
||||
completion(.failure(RedditFeedProviderError.unknown))
|
||||
}
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public func refresh(_ webFeed: WebFeed, completion: @escaping (Result<Set<ParsedItem>, Error>) -> Void) {
|
||||
guard let urlComponents = URLComponents(string: webFeed.url) else {
|
||||
completion(.failure(RedditFeedProviderError.unknown))
|
||||
return
|
||||
}
|
||||
|
||||
let api: String
|
||||
if urlComponents.path.isEmpty {
|
||||
api = "/.json"
|
||||
} else {
|
||||
api = "\(urlComponents.path).json"
|
||||
}
|
||||
|
||||
let splitPath = urlComponents.path.split(separator: "/")
|
||||
let identifySubreddit: Bool
|
||||
if splitPath.count > 1 {
|
||||
if Self.pseudoSubreddits.keys.contains(String(splitPath[1])) {
|
||||
identifySubreddit = true
|
||||
} else {
|
||||
identifySubreddit = !urlComponents.path.hasPrefix("/r/")
|
||||
}
|
||||
} else {
|
||||
identifySubreddit = true
|
||||
}
|
||||
|
||||
fetch(api: api, parameters: [:], resultType: RedditLinkListing.self) { result in
|
||||
switch result {
|
||||
case .success(let linkListing):
|
||||
self.parsingQueue.async {
|
||||
let parsedItems = self.makeParsedItems(webFeed.url, identifySubreddit, linkListing)
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(parsedItems))
|
||||
}
|
||||
}
|
||||
case .failure(let error):
|
||||
if (error as? OAuthSwiftError)?.errorCode == -11 {
|
||||
completion(.success(Set<ParsedItem>()))
|
||||
} else {
|
||||
completion(.failure(RedditFeedProviderError.accessFailure(error)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func create(tokenSuccess: OAuthSwift.TokenSuccess, completion: @escaping (Result<RedditFeedProvider, Error>) -> Void) {
|
||||
let oauthToken = tokenSuccess.credential.oauthToken
|
||||
let oauthRefreshToken = tokenSuccess.credential.oauthRefreshToken
|
||||
let redditFeedProvider = RedditFeedProvider(oauthToken: oauthToken, oauthRefreshToken: oauthRefreshToken)
|
||||
|
||||
redditFeedProvider.fetch(api: "/api/v1/me", resultType: RedditMe.self) { result in
|
||||
switch result {
|
||||
case .success(let user):
|
||||
guard let username = user.name else {
|
||||
completion(.failure(RedditFeedProviderError.unknown))
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
redditFeedProvider.username = username
|
||||
try storeCredentials(username: username, oauthToken: oauthToken, oauthRefreshToken: oauthRefreshToken)
|
||||
completion(.success(redditFeedProvider))
|
||||
} catch {
|
||||
completion(.failure(error))
|
||||
}
|
||||
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static func buildURL(_ type: RedditFeedType, username: String?, subreddit: String?, sort: RedditSort) -> URL? {
|
||||
var components = URLComponents()
|
||||
components.scheme = "https"
|
||||
components.host = "www.reddit.com"
|
||||
|
||||
switch type {
|
||||
case .home:
|
||||
guard let username = username else {
|
||||
return nil
|
||||
}
|
||||
components.user = username
|
||||
components.path = "/\(sort.rawValue)"
|
||||
case .popular:
|
||||
components.path = "/r/popular/\(sort.rawValue)"
|
||||
case .all:
|
||||
components.path = "/r/all/\(sort.rawValue)"
|
||||
case .subreddit:
|
||||
guard let subreddit = subreddit else {
|
||||
return nil
|
||||
}
|
||||
components.path = "/r/\(subreddit)/\(sort.rawValue)"
|
||||
}
|
||||
|
||||
return components.url
|
||||
}
|
||||
|
||||
static func storeCredentials(username: String, oauthToken: String, oauthRefreshToken: String) throws {
|
||||
let tokenCredentials = Credentials(type: .oauthAccessToken, username: username, secret: oauthToken)
|
||||
try CredentialsManager.storeCredentials(tokenCredentials, server: Self.server)
|
||||
let tokenSecretCredentials = Credentials(type: .oauthRefreshToken, username: username, secret: oauthRefreshToken)
|
||||
try CredentialsManager.storeCredentials(tokenSecretCredentials, server: Self.server)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: OAuth1SwiftProvider
|
||||
|
||||
extension RedditFeedProvider: OAuth2SwiftProvider {
|
||||
|
||||
public static var oauth2Swift: OAuth2Swift {
|
||||
let oauth2 = OAuth2Swift(consumerKey: SecretsManager.provider.redditConsumerKey,
|
||||
consumerSecret: "",
|
||||
authorizeUrl: "https://www.reddit.com/api/v1/authorize.compact?",
|
||||
accessTokenUrl: "https://www.reddit.com/api/v1/access_token",
|
||||
responseType: "code")
|
||||
oauth2.accessTokenBasicAuthentification = true
|
||||
return oauth2
|
||||
}
|
||||
|
||||
public static var callbackURL: URL {
|
||||
return URL(string: "netnewswire://success")!
|
||||
}
|
||||
|
||||
public static var oauth2Vars: (state: String, scope: String, params: [String : String]) {
|
||||
let state = generateState(withLength: 20)
|
||||
let scope = "identity mysubreddits read"
|
||||
let params = [
|
||||
"duration" : "permanent",
|
||||
]
|
||||
return (state: state, scope: scope, params: params)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private extension RedditFeedProvider {
|
||||
|
||||
func subreddit(_ urlComponents: URLComponents, completion: @escaping (Result<RedditSubreddit, Error>) -> Void) {
|
||||
let splitPath = urlComponents.path.split(separator: "/")
|
||||
guard splitPath.count > 1 else {
|
||||
completion(.failure(RedditFeedProviderError.unknown))
|
||||
return
|
||||
}
|
||||
|
||||
let secondElement = String(splitPath[1])
|
||||
let api = "/r/\(secondElement)/about.json"
|
||||
|
||||
fetch(api: api, parameters: [:], resultType: RedditSubreddit.self, completion: completion)
|
||||
}
|
||||
|
||||
func fetch<R: Decodable>(api: String, parameters: [String: Any] = [:], resultType: R.Type, completion: @escaping (Result<R, Error>) -> Void) {
|
||||
guard let client = client else {
|
||||
completion(.failure(RedditFeedProviderError.unknown))
|
||||
return
|
||||
}
|
||||
|
||||
if let remaining = rateLimitRemaining, let reset = rateLimitReset, remaining < 1 && reset > Date() {
|
||||
completion(.failure(RedditFeedProviderError.rateLimitExceeded))
|
||||
return
|
||||
}
|
||||
|
||||
let url = "\(Self.apiBase)\(api)"
|
||||
|
||||
var expandedParameters = parameters
|
||||
expandedParameters["raw_json"] = "1"
|
||||
|
||||
client.get(url, parameters: expandedParameters, headers: Self.userAgentHeaders) { result in
|
||||
switch result {
|
||||
case .success(let response):
|
||||
|
||||
if let remaining = response.response.value(forHTTPHeaderField: "X-Ratelimit-Remaining") {
|
||||
self.rateLimitRemaining = Int(remaining)
|
||||
} else {
|
||||
self.rateLimitRemaining = nil
|
||||
}
|
||||
|
||||
if let reset = response.response.value(forHTTPHeaderField: "X-Ratelimit-Reset") {
|
||||
self.rateLimitReset = Date(timeIntervalSinceNow: Double(reset) ?? 0)
|
||||
} else {
|
||||
self.rateLimitReset = nil
|
||||
}
|
||||
|
||||
self.parsingQueue.async {
|
||||
let decoder = JSONDecoder()
|
||||
do {
|
||||
let result = try decoder.decode(resultType, from: response.data)
|
||||
DispatchQueue.main.async {
|
||||
completion(.success(result))
|
||||
}
|
||||
} catch {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case .failure(let oathError):
|
||||
self.handleFailure(error: oathError) { error in
|
||||
if let error = error {
|
||||
completion(.failure(error))
|
||||
} else {
|
||||
self.fetch(api: api, parameters: parameters, resultType: resultType, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func makeParsedItems(_ webFeedURL: String,_ identifySubreddit: Bool, _ linkListing: RedditLinkListing) -> Set<ParsedItem> {
|
||||
var parsedItems = Set<ParsedItem>()
|
||||
|
||||
guard let linkDatas = linkListing.data?.children?.compactMap({ $0.data }), !linkDatas.isEmpty else {
|
||||
return parsedItems
|
||||
}
|
||||
|
||||
for linkData in linkDatas {
|
||||
guard let permalink = linkData.permalink else { continue }
|
||||
|
||||
let parsedItem = ParsedItem(syncServiceID: nil,
|
||||
uniqueID: permalink,
|
||||
feedURL: webFeedURL,
|
||||
url: "https://www.reddit.com\(permalink)",
|
||||
externalURL: linkData.url,
|
||||
title: linkData.title,
|
||||
language: nil,
|
||||
contentHTML: linkData.renderAsHTML(identifySubreddit: identifySubreddit),
|
||||
contentText: linkData.selfText,
|
||||
summary: nil,
|
||||
imageURL: nil,
|
||||
bannerImageURL: nil,
|
||||
datePublished: linkData.createdDate,
|
||||
dateModified: nil,
|
||||
authors: makeParsedAuthors(linkData.author),
|
||||
tags: nil,
|
||||
attachments: nil)
|
||||
parsedItems.insert(parsedItem)
|
||||
}
|
||||
|
||||
return parsedItems
|
||||
}
|
||||
|
||||
func makeParsedAuthors(_ username: String?) -> Set<ParsedAuthor>? {
|
||||
guard let username = username else { return nil }
|
||||
var urlComponents = URLComponents(string: "https://www.reddit.com")
|
||||
urlComponents?.path = "/u/\(username)"
|
||||
let userURL = urlComponents?.url?.absoluteString
|
||||
return Set([ParsedAuthor(name: "u/\(username)", url: userURL, avatarURL: nil, emailAddress: nil)])
|
||||
}
|
||||
|
||||
func handleFailure(error: OAuthSwiftError, completion: @escaping (Error?) -> Void) {
|
||||
if case .tokenExpired = error {
|
||||
|
||||
let op = RedditFeedProviderTokenRefreshOperation(delegate: self)
|
||||
|
||||
op.completionBlock = { operation in
|
||||
let refreshOperation = operation as! RedditFeedProviderTokenRefreshOperation
|
||||
if let error = refreshOperation.error {
|
||||
completion(error)
|
||||
} else {
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
|
||||
operationQueue.add(op)
|
||||
|
||||
} else {
|
||||
completion(error)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,78 +0,0 @@
|
||||
//
|
||||
// RedditFeedProviderTokenRefreshOperation.swift
|
||||
//
|
||||
//
|
||||
// Created by Maurice Parker on 8/12/20.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import os.log
|
||||
import RSCore
|
||||
import OAuthSwift
|
||||
import Secrets
|
||||
|
||||
protocol RedditFeedProviderTokenRefreshOperationDelegate: AnyObject {
|
||||
var username: String? { get }
|
||||
var oauthTokenLastRefresh: Date? { get set }
|
||||
var oauthToken: String { get set }
|
||||
var oauthRefreshToken: String { get set }
|
||||
var oauthSwift: OAuth2Swift? { get }
|
||||
}
|
||||
|
||||
class RedditFeedProviderTokenRefreshOperation: MainThreadOperation {
|
||||
|
||||
var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "RedditFeedProvider")
|
||||
|
||||
public var isCanceled = false
|
||||
public var id: Int?
|
||||
public weak var operationDelegate: MainThreadOperationDelegate?
|
||||
public var name: String? = "WebViewProviderReplenishQueueOperation"
|
||||
public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock?
|
||||
|
||||
private weak var delegate: RedditFeedProviderTokenRefreshOperationDelegate?
|
||||
|
||||
var error: Error?
|
||||
|
||||
init(delegate: RedditFeedProviderTokenRefreshOperationDelegate) {
|
||||
self.delegate = delegate
|
||||
}
|
||||
|
||||
func run() {
|
||||
guard let delegate = delegate, let username = delegate.username else {
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
return
|
||||
}
|
||||
|
||||
// If another operation has recently refreshed the token, we don't need to do it again
|
||||
if let lastRefresh = delegate.oauthTokenLastRefresh, Date().timeIntervalSince(lastRefresh) < 120 {
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
return
|
||||
}
|
||||
|
||||
os_log(.debug, log: self.log, "Access token expired, attempting to renew...")
|
||||
|
||||
delegate.oauthSwift?.renewAccessToken(withRefreshToken: delegate.oauthRefreshToken) { [weak self] result in
|
||||
guard let self = self else { return }
|
||||
|
||||
switch result {
|
||||
case .success(let tokenSuccess):
|
||||
delegate.oauthToken = tokenSuccess.credential.oauthToken
|
||||
delegate.oauthRefreshToken = tokenSuccess.credential.oauthRefreshToken
|
||||
do {
|
||||
try RedditFeedProvider.storeCredentials(username: username, oauthToken: delegate.oauthToken, oauthRefreshToken: delegate.oauthRefreshToken)
|
||||
delegate.oauthTokenLastRefresh = Date()
|
||||
os_log(.debug, log: self.log, "Access token renewed.")
|
||||
} catch {
|
||||
self.error = error
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
}
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
case .failure(let oathError):
|
||||
self.error = oathError
|
||||
self.operationDelegate?.operationDidComplete(self)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
//
|
||||
// RedditGalleryData.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 7/27/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct RedditGalleryData: Codable {
|
||||
|
||||
let items: [RedditGalleryDataItem]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case items = "items"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RedditGalleryDataItem: Codable {
|
||||
|
||||
let mediaID: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case mediaID = "media_id"
|
||||
}
|
||||
|
||||
}
|
@ -1,173 +0,0 @@
|
||||
//
|
||||
// RedditLink.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 5/4/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class RedditLink: Codable {
|
||||
|
||||
let kind: String?
|
||||
let data: RedditLinkData?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case kind = "kind"
|
||||
case data = "data"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
final class RedditLinkData: Codable {
|
||||
|
||||
let title: String?
|
||||
let permalink: String?
|
||||
let url: String?
|
||||
let id: String?
|
||||
let subredditNamePrefixed: String?
|
||||
let selfHTML: String?
|
||||
let selfText: String?
|
||||
let postHint: String?
|
||||
let author: String?
|
||||
let created: Double?
|
||||
let isVideo: Bool?
|
||||
let media: RedditMedia?
|
||||
let mediaEmbed: RedditMediaEmbed?
|
||||
let preview: RedditPreview?
|
||||
let crossPostParents: [RedditLinkData]?
|
||||
let galleryData: RedditGalleryData?
|
||||
let mediaMetadata: [String: RedditMediaMetadata]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case title = "title"
|
||||
case permalink = "permalink"
|
||||
case url = "url"
|
||||
case id = "id"
|
||||
case subredditNamePrefixed = "subreddit_name_prefixed"
|
||||
case selfHTML = "selftext_html"
|
||||
case selfText = "selftext"
|
||||
case postHint = "post_hint"
|
||||
case author = "author"
|
||||
case created = "created_utc"
|
||||
case isVideo = "is_video"
|
||||
case media = "media"
|
||||
case mediaEmbed = "media_embed"
|
||||
case preview = "preview"
|
||||
case crossPostParents = "crosspost_parent_list"
|
||||
case galleryData = "gallery_data"
|
||||
case mediaMetadata = "media_metadata"
|
||||
}
|
||||
|
||||
var createdDate: Date? {
|
||||
guard let created = created else { return nil }
|
||||
return Date(timeIntervalSince1970: created)
|
||||
}
|
||||
|
||||
func renderAsHTML(identifySubreddit: Bool) -> String {
|
||||
var html = String()
|
||||
|
||||
if identifySubreddit, let subredditNamePrefixed = subredditNamePrefixed {
|
||||
html += "<h3><a href=\"https://www.reddit.com/\(subredditNamePrefixed)\">\(subredditNamePrefixed)</a></h3>"
|
||||
}
|
||||
|
||||
if let parent = crossPostParents?.first {
|
||||
html += "<blockquote>"
|
||||
if let subreddit = parent.subredditNamePrefixed {
|
||||
html += "<p><a href=\"https://www.reddit.com/\(subreddit)\">\(subreddit)</a></p>"
|
||||
}
|
||||
let parentHTML = parent.renderAsHTML(identifySubreddit: false)
|
||||
if parentHTML.isEmpty {
|
||||
html += renderURLAsHTML()
|
||||
} else {
|
||||
html += parentHTML
|
||||
}
|
||||
html += "</blockquote>"
|
||||
return html
|
||||
}
|
||||
|
||||
if let selfHTML = selfHTML {
|
||||
html += selfHTML
|
||||
}
|
||||
html += renderURLAsHTML()
|
||||
return html
|
||||
}
|
||||
|
||||
func renderURLAsHTML() -> String {
|
||||
guard let url = url else { return "" }
|
||||
|
||||
if url.hasSuffix(".gif") {
|
||||
return "<img class=\"nnw-nozoom\" src=\"\(url)\">"
|
||||
}
|
||||
|
||||
if isVideo ?? false, let videoURL = media?.video?.hlsURL {
|
||||
var html = "<figure><video "
|
||||
if let previewImageURL = preview?.images?.first?.source?.url {
|
||||
html += "poster=\"\(previewImageURL)\" "
|
||||
}
|
||||
if let width = media?.video?.width, let height = media?.video?.height {
|
||||
html += "width=\"\(width)\" height=\"\(height)\" "
|
||||
}
|
||||
html += "src=\"\(videoURL)\"></video></figure>"
|
||||
return html
|
||||
}
|
||||
|
||||
if let imageVariantURL = preview?.images?.first?.variants?.mp4?.source?.url {
|
||||
var html = "<figure><video class=\"nnwAnimatedGIF\" "
|
||||
if let previewImageURL = preview?.images?.first?.source?.url {
|
||||
html += "poster=\"\(previewImageURL)\" "
|
||||
}
|
||||
if let width = preview?.images?.first?.variants?.mp4?.source?.width, let height = preview?.images?.first?.variants?.mp4?.source?.height {
|
||||
html += "width=\"\(width)\" height=\"\(height)\" "
|
||||
}
|
||||
html += "src=\"\(imageVariantURL)\" autoplay muted loop></video></figure>"
|
||||
return html
|
||||
}
|
||||
|
||||
if let videoPreviewURL = preview?.videoPreview?.url {
|
||||
var html = "<figure><video class=\"nnwAnimatedGIF\" "
|
||||
if let previewImageURL = preview?.images?.first?.source?.url {
|
||||
html += "poster=\"\(previewImageURL)\" "
|
||||
}
|
||||
if let width = preview?.videoPreview?.width, let height = preview?.videoPreview?.height {
|
||||
html += "width=\"\(width)\" height=\"\(height)\" "
|
||||
}
|
||||
html += "src=\"\(videoPreviewURL)\" autoplay muted loop></video></figure>"
|
||||
return html
|
||||
}
|
||||
|
||||
if !url.hasPrefix("https://imgur.com"), let mediaEmbedContent = mediaEmbed?.content {
|
||||
return mediaEmbedContent
|
||||
}
|
||||
|
||||
if let imageSource = preview?.images?.first?.source, let imageURL = imageSource.url {
|
||||
var html = "<figure><img src=\"\(imageURL)\" "
|
||||
if postHint == "link" {
|
||||
html += "class=\"nnw-nozoom\" "
|
||||
}
|
||||
if let width = imageSource.width, let height = imageSource.height {
|
||||
html += "width=\"\(width)\" height=\"\(height)\" "
|
||||
}
|
||||
html += "></figure>"
|
||||
return html
|
||||
}
|
||||
|
||||
if let galleryDataItems = galleryData?.items, let mediaMetadata = mediaMetadata {
|
||||
var html = ""
|
||||
for item in galleryDataItems {
|
||||
if let mediaID = item.mediaID, let itemMetadata = mediaMetadata[mediaID], let imageURL = itemMetadata.image?.url {
|
||||
html += "<figure><img src=\"\(imageURL)\" "
|
||||
if let width = itemMetadata.image?.width, let height = itemMetadata.image?.height {
|
||||
html += "width=\"\(width)\" height=\"\(height)\" "
|
||||
}
|
||||
html += "></figure>"
|
||||
}
|
||||
}
|
||||
return html
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
//
|
||||
// RedditLinkListing.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 5/3/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct RedditLinkListing: Codable {
|
||||
|
||||
let kind: String?
|
||||
let data: RedditLinkListingData?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case kind
|
||||
case data
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RedditLinkListingData: Codable {
|
||||
|
||||
let children: [RedditLink]?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case children = "children"
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
//
|
||||
// RedditMe.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 5/4/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct RedditMe: Codable {
|
||||
|
||||
let name: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name = "name"
|
||||
}
|
||||
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
//
|
||||
// RedditMedia.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 5/4/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct RedditMedia: Codable {
|
||||
|
||||
let video: RedditVideo?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case video = "reddit_video"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RedditVideo: Codable {
|
||||
|
||||
let fallbackURL: String?
|
||||
let hlsURL: String?
|
||||
let height: Int?
|
||||
let width: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case fallbackURL = "fallback_url"
|
||||
case hlsURL = "hls_url"
|
||||
case height = "height"
|
||||
case width = "width"
|
||||
}
|
||||
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
//
|
||||
// RedditMediaEmbed.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 5/4/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct RedditMediaEmbed: Codable {
|
||||
|
||||
let content: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case content = "content"
|
||||
}
|
||||
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
//
|
||||
// RedditMediaMetadata.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 7/27/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct RedditMediaMetadata: Codable {
|
||||
|
||||
let image: RedditMediaMetadataS?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case image = "s"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RedditMediaMetadataS: Codable {
|
||||
|
||||
let url: String?
|
||||
let height: Int?
|
||||
let width: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case url = "u"
|
||||
case height = "y"
|
||||
case width = "x"
|
||||
}
|
||||
|
||||
}
|
@ -1,95 +0,0 @@
|
||||
//
|
||||
// RedditPreview.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 5/5/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct RedditPreview: Codable {
|
||||
|
||||
let images: [RedditPreviewImage]?
|
||||
let videoPreview: RedditVideoPreview?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case images = "images"
|
||||
case videoPreview = "reddit_video_preview"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RedditPreviewImage: Codable {
|
||||
|
||||
let source: RedditPreviewImageSource?
|
||||
let variants: RedditPreviewImageVariants?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case source = "source"
|
||||
case variants = "variants"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RedditPreviewImageSource: Codable {
|
||||
|
||||
let url: String?
|
||||
let width: Int?
|
||||
let height: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case url = "url"
|
||||
case width = "width"
|
||||
case height = "height"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RedditPreviewImageVariants: Codable {
|
||||
|
||||
let mp4: RedditPreviewImageVariantsMP4?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case mp4 = "mp4"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RedditPreviewImageVariantsMP4: Codable {
|
||||
|
||||
let source: RedditPreviewImageVariantsMP4Source?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case source = "source"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RedditPreviewImageVariantsMP4Source: Codable {
|
||||
|
||||
let url: String?
|
||||
let width: Int?
|
||||
let height: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case url = "url"
|
||||
case width = "width"
|
||||
case height = "height"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RedditVideoPreview: Codable {
|
||||
|
||||
let url: String?
|
||||
let width: Int?
|
||||
let height: Int?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case url = "fallback_url"
|
||||
case width = "width"
|
||||
case height = "height"
|
||||
}
|
||||
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
//
|
||||
// RedditSort.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 5/7/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
public enum RedditSort: String, CaseIterable {
|
||||
case best
|
||||
case rising
|
||||
case hot
|
||||
case new
|
||||
case top
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .best:
|
||||
return NSLocalizedString("Best", comment: "Best")
|
||||
case .rising:
|
||||
return NSLocalizedString("Rising", comment: "Rising")
|
||||
case .hot:
|
||||
return NSLocalizedString("Hot", comment: "Hot")
|
||||
case .new:
|
||||
return NSLocalizedString("New", comment: "New")
|
||||
case .top:
|
||||
return NSLocalizedString("Top", comment: "Top")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
//
|
||||
// RedditSubreddit.swift
|
||||
// Account
|
||||
//
|
||||
// Created by Maurice Parker on 5/4/20.
|
||||
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct RedditSubreddit: Codable {
|
||||
|
||||
let kind: String?
|
||||
let data: RedditSubredditData?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case kind = "kind"
|
||||
case data = "data"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct RedditSubredditData: Codable {
|
||||
|
||||
let displayName: String?
|
||||
let iconImg: String?
|
||||
let communityIcon: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case displayName = "display_name_prefixed"
|
||||
case iconImg = "icon_img"
|
||||
case communityIcon = "community_icon"
|
||||
}
|
||||
|
||||
var iconURL: String? {
|
||||
if let communityIcon = communityIcon, !communityIcon.isEmpty {
|
||||
return communityIcon
|
||||
} else {
|
||||
return iconImg
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -251,9 +251,6 @@
|
||||
51934CCE2310792F006127BE /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; };
|
||||
51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; };
|
||||
51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */; };
|
||||
5193CD58245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; };
|
||||
5193CD59245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; };
|
||||
5193CD5A245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */; };
|
||||
5195C1DA2720205F00888867 /* ShadowTableChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5195C1D92720205F00888867 /* ShadowTableChanges.swift */; };
|
||||
5195C1DC2720BD3000888867 /* MasterFeedRowIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5195C1DB2720BD3000888867 /* MasterFeedRowIdentifier.swift */; };
|
||||
519B8D332143397200FA689C /* SharingServiceDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 519B8D322143397200FA689C /* SharingServiceDelegate.swift */; };
|
||||
@ -1235,7 +1232,6 @@
|
||||
51934CC1230F5963006127BE /* InteractiveNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InteractiveNavigationController.swift; sourceTree = "<group>"; };
|
||||
51934CCD2310792F006127BE /* ActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityManager.swift; sourceTree = "<group>"; };
|
||||
51938DF1231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchTimelineFeedDelegate.swift; sourceTree = "<group>"; };
|
||||
5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "RedditFeedProvider-Extensions.swift"; sourceTree = "<group>"; };
|
||||
5195C1D92720205F00888867 /* ShadowTableChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShadowTableChanges.swift; sourceTree = "<group>"; };
|
||||
5195C1DB2720BD3000888867 /* MasterFeedRowIdentifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterFeedRowIdentifier.swift; sourceTree = "<group>"; };
|
||||
519B8D322143397200FA689C /* SharingServiceDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingServiceDelegate.swift; sourceTree = "<group>"; };
|
||||
@ -1775,7 +1771,6 @@
|
||||
515A50E5243D07A90089E588 /* ExtensionPointManager.swift */,
|
||||
84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */,
|
||||
84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */,
|
||||
5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */,
|
||||
);
|
||||
path = ExtensionPoints;
|
||||
sourceTree = "<group>";
|
||||
@ -3784,7 +3779,6 @@
|
||||
65ED3FE3235DEF6C0081F399 /* ArticleUtilities.swift in Sources */,
|
||||
65ED3FE4235DEF6C0081F399 /* NNW3OpenPanelAccessoryViewController.swift in Sources */,
|
||||
65ED3FE5235DEF6C0081F399 /* DefaultFeedsImporter.swift in Sources */,
|
||||
5193CD59245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */,
|
||||
65ED3FE6235DEF6C0081F399 /* RenameWindowController.swift in Sources */,
|
||||
65ED3FE7235DEF6C0081F399 /* SendToMicroBlogCommand.swift in Sources */,
|
||||
65ED3FE8235DEF6C0081F399 /* ArticleTheme.swift in Sources */,
|
||||
@ -3965,7 +3959,6 @@
|
||||
51C4526B226508F600C03939 /* MasterFeedViewController.swift in Sources */,
|
||||
5126EE97226CB48A00C22AFC /* SceneCoordinator.swift in Sources */,
|
||||
84CAFCB022BC8C35007694F0 /* FetchRequestOperation.swift in Sources */,
|
||||
5193CD5A245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */,
|
||||
51EF0F77227716200050506E /* FaviconGenerator.swift in Sources */,
|
||||
51938DF3231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */,
|
||||
51C4525A226508D600C03939 /* UIStoryboard-Extensions.swift in Sources */,
|
||||
@ -4193,7 +4186,6 @@
|
||||
849A97531ED9EAC0007D329B /* AddFeedController.swift in Sources */,
|
||||
5183CCE8226F68D90010922C /* AccountRefreshTimer.swift in Sources */,
|
||||
849A97831ED9EC63007D329B /* SidebarStatusBarView.swift in Sources */,
|
||||
5193CD58245E44A90092735E /* RedditFeedProvider-Extensions.swift in Sources */,
|
||||
51938DF2231AFC660055A1A0 /* SearchTimelineFeedDelegate.swift in Sources */,
|
||||
84F2D5381FC22FCC00998D64 /* TodayFeedDelegate.swift in Sources */,
|
||||
841ABA5E20145E9200980E11 /* FolderInspectorViewController.swift in Sources */,
|
||||
|
@ -11,28 +11,25 @@ import Account
|
||||
import RSCore
|
||||
|
||||
enum ExtensionPointIdentifer: Hashable {
|
||||
#if os(macOS)
|
||||
#if os(macOS)
|
||||
case marsEdit
|
||||
case microblog
|
||||
#endif
|
||||
case reddit(String)
|
||||
#endif
|
||||
|
||||
var extensionPointType: ExtensionPoint.Type {
|
||||
switch self {
|
||||
#if os(macOS)
|
||||
#if os(macOS)
|
||||
case .marsEdit:
|
||||
return SendToMarsEditCommand.self
|
||||
case .microblog:
|
||||
return SendToMicroBlogCommand.self
|
||||
#endif
|
||||
case .reddit:
|
||||
return RedditFeedProvider.self
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public var userInfo: [AnyHashable: AnyHashable] {
|
||||
switch self {
|
||||
#if os(macOS)
|
||||
#if os(macOS)
|
||||
case .marsEdit:
|
||||
return [
|
||||
"type": "marsEdit"
|
||||
@ -41,12 +38,7 @@ enum ExtensionPointIdentifer: Hashable {
|
||||
return [
|
||||
"type": "microblog"
|
||||
]
|
||||
#endif
|
||||
case .reddit(let username):
|
||||
return [
|
||||
"type": "reddit",
|
||||
"username": username
|
||||
]
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,15 +46,12 @@ enum ExtensionPointIdentifer: Hashable {
|
||||
guard let type = userInfo["type"] as? String else { return nil }
|
||||
|
||||
switch type {
|
||||
#if os(macOS)
|
||||
#if os(macOS)
|
||||
case "marsEdit":
|
||||
self = ExtensionPointIdentifer.marsEdit
|
||||
case "microblog":
|
||||
self = ExtensionPointIdentifer.microblog
|
||||
#endif
|
||||
case "reddit":
|
||||
guard let username = userInfo["username"] as? String else { return nil }
|
||||
self = ExtensionPointIdentifer.reddit(username)
|
||||
#endif
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
@ -70,16 +59,12 @@ enum ExtensionPointIdentifer: Hashable {
|
||||
|
||||
public func hash(into hasher: inout Hasher) {
|
||||
switch self {
|
||||
#if os(macOS)
|
||||
#if os(macOS)
|
||||
case .marsEdit:
|
||||
hasher.combine("marsEdit")
|
||||
case .microblog:
|
||||
hasher.combine("microblog")
|
||||
#endif
|
||||
case .reddit(let username):
|
||||
hasher.combine("reddit")
|
||||
hasher.combine(username)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -69,12 +69,8 @@ final class ExtensionPointManager: FeedProviderManagerDelegate {
|
||||
return activeExtensionPoints.values.compactMap({ return $0 as? FeedProvider })
|
||||
}
|
||||
|
||||
var isRedditEnabled: Bool {
|
||||
return activeExtensionPoints.values.contains(where: { $0 is RedditFeedProvider })
|
||||
}
|
||||
|
||||
init() {
|
||||
possibleExtensionPointTypes = [RedditFeedProvider.self]
|
||||
possibleExtensionPointTypes = []
|
||||
loadExtensionPoints()
|
||||
}
|
||||
|
||||
@ -117,19 +113,6 @@ private extension ExtensionPointManager {
|
||||
|
||||
func extensionPoint(for extensionPointType: ExtensionPoint.Type, tokenSuccess: OAuthSwift.TokenSuccess?, completion: @escaping (Result<ExtensionPoint, Error>) -> Void) {
|
||||
switch extensionPointType {
|
||||
case is RedditFeedProvider.Type:
|
||||
if let tokenSuccess = tokenSuccess {
|
||||
RedditFeedProvider.create(tokenSuccess: tokenSuccess) { result in
|
||||
switch result {
|
||||
case .success(let reddit):
|
||||
completion(.success(reddit))
|
||||
case .failure(let error):
|
||||
completion(.failure(error))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
completion(.failure(ExtensionPointManagerError.unableToCreate))
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
@ -137,8 +120,6 @@ private extension ExtensionPointManager {
|
||||
|
||||
func extensionPoint(for extensionPointID: ExtensionPointIdentifer) -> ExtensionPoint? {
|
||||
switch extensionPointID {
|
||||
case .reddit(let username):
|
||||
return RedditFeedProvider(username: username)
|
||||
#if os(macOS)
|
||||
default:
|
||||
return nil
|
||||
|
@ -1,36 +0,0 @@
|
||||
//
|
||||
// RedditFeedProvider-Extensions.swift
|
||||
// NetNewsWire
|
||||
//
|
||||
// Created by Maurice Parker on 5/2/20.
|
||||
// Copyright © 2020 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Account
|
||||
|
||||
extension RedditFeedProvider: ExtensionPoint {
|
||||
|
||||
static var isSinglton = false
|
||||
static var isDeveloperBuildRestricted = true
|
||||
static var title = NSLocalizedString("Reddit", comment: "Reddit")
|
||||
static var image = AppAssets.extensionPointReddit
|
||||
static var description: NSAttributedString = {
|
||||
return RedditFeedProvider.makeAttrString("This extension enables you to subscribe to Reddit URLs as if they were RSS feeds. It only works with \(Account.defaultLocalAccountName) or iCloud accounts.")
|
||||
}()
|
||||
|
||||
var extensionPointID: ExtensionPointIdentifer {
|
||||
guard let username = username else {
|
||||
fatalError()
|
||||
}
|
||||
return ExtensionPointIdentifer.reddit(username)
|
||||
}
|
||||
|
||||
var title: String {
|
||||
guard let username = username else {
|
||||
fatalError()
|
||||
}
|
||||
return "u/\(username)"
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user