Remove more Reddit references, including code in the Account framework.

This commit is contained in:
Brent Simmons 2023-06-25 15:45:36 -07:00
parent de723d9ed1
commit cd45b88cfb
16 changed files with 11 additions and 1121 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 */,

View File

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

View File

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

View File

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