Remove Twitter integration. Fixes #3842

This commit is contained in:
Maurice Parker 2023-02-03 19:00:13 -08:00
parent 06910b1e58
commit 6c1e1ea1c6
34 changed files with 7 additions and 1666 deletions

View File

@ -1,70 +0,0 @@
//
// TwitterEntities.swift
// Account
//
// Created by Maurice Parker on 4/18/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
protocol TwitterEntity {
var indices: [Int]? { get }
func renderAsHTML() -> String
}
extension TwitterEntity {
var startIndex: Int {
if let indices = indices, indices.count > 0 {
return indices[0]
}
return 0
}
var endIndex: Int {
if let indices = indices, indices.count > 1 {
return indices[1]
}
return 0
}
}
struct TwitterEntities: Codable {
let hashtags: [TwitterHashtag]?
let urls: [TwitterURL]?
let userMentions: [TwitterMention]?
let symbols: [TwitterSymbol]?
let media: [TwitterMedia]?
enum CodingKeys: String, CodingKey {
case hashtags = "hashtags"
case urls = "urls"
case userMentions = "user_mentions"
case symbols = "symbols"
case media = "media"
}
func combineAndSort() -> [TwitterEntity] {
var entities = [TwitterEntity]()
if let hashtags = hashtags {
entities.append(contentsOf: hashtags)
}
if let urls = urls {
entities.append(contentsOf: urls)
}
if let userMentions = userMentions {
entities.append(contentsOf: userMentions)
}
if let symbols = symbols {
entities.append(contentsOf: symbols)
}
if let media = media {
entities.append(contentsOf: media)
}
return entities.sorted(by: { $0.startIndex < $1.startIndex })
}
}

View File

@ -1,28 +0,0 @@
//
// TwitterExtendedEntities.swift
// Account
//
// Created by Maurice Parker on 4/18/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct TwitterExtendedEntities: Codable {
let medias: [TwitterExtendedMedia]?
enum CodingKeys: String, CodingKey {
case medias = "media"
}
func renderAsHTML() -> String {
var html = String()
if let medias = medias {
for media in medias {
html += media.renderAsHTML()
}
}
return html
}
}

View File

@ -1,110 +0,0 @@
//
// TwitterExtendedMedia.swift
// Account
//
// Created by Maurice Parker on 4/18/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct TwitterExtendedMedia: Codable {
let idStr: String?
let indices: [Int]?
let mediaURL: String?
let httpsMediaURL: String?
let url: String?
let displayURL: String?
let type: String?
let video: TwitterVideo?
enum CodingKeys: String, CodingKey {
case idStr = "idStr"
case indices = "indices"
case mediaURL = "media_url"
case httpsMediaURL = "media_url_https"
case url = "url"
case displayURL = "display_url"
case type = "type"
case video = "video_info"
}
func renderAsHTML() -> String {
var html = String()
switch type {
case "photo":
html += renderPhotoAsHTML()
case "video":
html += renderVideoAsHTML()
case "animated_gif":
html += renderAnimatedGIFAsHTML()
default:
break
}
return html
}
}
private extension TwitterExtendedMedia {
func renderPhotoAsHTML() -> String {
if let httpsMediaURL = httpsMediaURL {
return "<figure><img src=\"\(httpsMediaURL)\"></figure>"
}
if let mediaURL = mediaURL {
return "<figure><img src=\"\(mediaURL)\"></figure>"
}
return ""
}
func renderVideoAsHTML() -> String {
guard let bestVariantURL = findBestVariant()?.url else { return "" }
var html = "<video "
if let httpsMediaURL = httpsMediaURL {
html += "poster=\"\(httpsMediaURL)\" "
} else if let mediaURL = mediaURL {
html += "poster=\"\(mediaURL)\" "
}
html += "src=\"\(bestVariantURL)\"></video>"
return html
}
func renderAnimatedGIFAsHTML() -> String {
guard let bestVariantURL = findBestVariant()?.url else { return "" }
var html = "<video class=\"nnwAnimatedGIF\" "
if let httpsMediaURL = httpsMediaURL {
html += "poster=\"\(httpsMediaURL)\" "
} else if let mediaURL = mediaURL {
html += "poster=\"\(mediaURL)\" "
}
html += "src=\"\(bestVariantURL)\" autoplay muted loop></video>"
return html
}
func findBestVariant() -> TwitterVideo.Variant? {
var best: TwitterVideo.Variant? = nil
if let variants = video?.variants {
for variant in variants {
if let currentBest = best {
if variant.bitrate ?? 0 > currentBest.bitrate ?? 0 {
best = variant
}
} else {
best = variant
}
}
}
return best
}
}

View File

@ -1,485 +0,0 @@
//
// TwitterFeedProvider.swift
// FeedProvider
//
// Created by Maurice Parker on 4/7/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import Secrets
import OAuthSwift
import RSParser
import RSWeb
public enum TwitterFeedProviderError: LocalizedError {
case rateLimitExceeded
case screenNameNotFound
case unknown
public var errorDescription: String? {
switch self {
case .rateLimitExceeded:
return NSLocalizedString("Twitter API rate limit has been exceeded. Please wait a short time and try again.", comment: "Rate Limit")
case .screenNameNotFound:
return NSLocalizedString("Unable to determine screen name.", comment: "Screen name")
case .unknown:
return NSLocalizedString("An unknown Twitter Feed Provider error has occurred.", comment: "Unknown error")
}
}
}
public enum TwitterFeedType: Int {
case homeTimeline = 0
case mentions = 1
case screenName = 2
case search = 3
}
public final class TwitterFeedProvider: FeedProvider {
private static let homeURL = "https://www.twitter.com"
private static let iconURL = "https://abs.twimg.com/favicons/twitter.ico"
private static let server = "api.twitter.com"
private static let apiBase = "https://api.twitter.com/1.1/"
private static let userAgentHeaders = UserAgent.headers() as! [String: String]
private static let dateFormat = "EEE MMM dd HH:mm:ss Z yyyy"
private static let userPaths = ["/", "/home", "/notifications"]
private static let reservedPaths = ["/search", "/explore", "/messages", "/i", "/compose", "/notifications/mentions"]
private var parsingQueue = DispatchQueue(label: "TwitterFeedProvider parse queue")
public var screenName: String
private var oauthToken: String
private var oauthTokenSecret: String
private var client: OAuthSwiftClient
private var rateLimitRemaining: Int?
private var rateLimitReset: Date?
public init?(tokenSuccess: OAuthSwift.TokenSuccess) {
guard let screenName = tokenSuccess.parameters["screen_name"] as? String else {
return nil
}
self.screenName = screenName
self.oauthToken = tokenSuccess.credential.oauthToken
self.oauthTokenSecret = tokenSuccess.credential.oauthTokenSecret
let tokenCredentials = Credentials(type: .oauthAccessToken, username: screenName, secret: oauthToken)
try? CredentialsManager.storeCredentials(tokenCredentials, server: Self.server)
let tokenSecretCredentials = Credentials(type: .oauthAccessTokenSecret, username: screenName, secret: oauthTokenSecret)
try? CredentialsManager.storeCredentials(tokenSecretCredentials, server: Self.server)
client = OAuthSwiftClient(consumerKey: SecretsManager.provider.twitterConsumerKey,
consumerSecret: SecretsManager.provider.twitterConsumerSecret,
oauthToken: oauthToken,
oauthTokenSecret: oauthTokenSecret,
version: .oauth1)
}
public init?(screenName: String) {
self.screenName = screenName
guard let tokenCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthAccessToken, server: Self.server, username: screenName),
let tokenSecretCredentials = try? CredentialsManager.retrieveCredentials(type: .oauthAccessTokenSecret, server: Self.server, username: screenName) else {
return nil
}
self.oauthToken = tokenCredentials.secret
self.oauthTokenSecret = tokenSecretCredentials.secret
client = OAuthSwiftClient(consumerKey: SecretsManager.provider.twitterConsumerKey,
consumerSecret: SecretsManager.provider.twitterConsumerSecret,
oauthToken: oauthToken,
oauthTokenSecret: oauthTokenSecret,
version: .oauth1)
}
public func ability(_ urlComponents: URLComponents) -> FeedProviderAbility {
guard urlComponents.host?.hasSuffix("twitter.com") ?? false else {
return .none
}
if let username = urlComponents.user {
if username == screenName {
return .owner
} else {
return .none
}
}
return .available
}
public func iconURL(_ urlComponents: URLComponents, completion: @escaping (Result<String, Error>) -> Void) {
if let screenName = deriveScreenName(urlComponents) {
retrieveUser(screenName: screenName) { result in
switch result {
case .success(let user):
if let avatarURL = user.avatarURL {
completion(.success(avatarURL))
} else {
completion(.failure(TwitterFeedProviderError.screenNameNotFound))
}
case .failure(let error):
completion(.failure(error))
}
}
} else {
completion(.success(Self.iconURL))
}
}
public func metaData(_ urlComponents: URLComponents, completion: @escaping (Result<FeedProviderFeedMetaData, Error>) -> Void) {
switch urlComponents.path {
case "", "/", "/home":
let name = NSLocalizedString("Twitter Timeline", comment: "Twitter Timeline")
completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL)))
case "/notifications/mentions":
let name = NSLocalizedString("Twitter Mentions", comment: "Twitter Mentions")
completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL)))
case "/search":
if let query = urlComponents.queryItems?.first(where: { $0.name == "q" })?.value {
let localized = NSLocalizedString("Twitter Search: %@", comment: "Twitter Search")
let name = NSString.localizedStringWithFormat(localized as NSString, query) as String
completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL)))
} else {
let name = NSLocalizedString("Twitter Search", comment: "Twitter Search")
completion(.success(FeedProviderFeedMetaData(name: name, homePageURL: Self.homeURL)))
}
default:
if let hashtag = deriveHashtag(urlComponents) {
completion(.success(FeedProviderFeedMetaData(name: "#\(hashtag)", homePageURL: Self.homeURL)))
} else if let listID = deriveListID(urlComponents) {
retrieveList(listID: listID) { result in
switch result {
case .success(let list):
if let userName = list.name {
var urlComponents = URLComponents(string: Self.homeURL)
urlComponents?.path = "/i/lists/\(listID)"
completion(.success(FeedProviderFeedMetaData(name: userName, homePageURL: urlComponents?.url?.absoluteString)))
} else {
completion(.failure(TwitterFeedProviderError.screenNameNotFound))
}
case .failure(let error):
completion(.failure(error))
}
}
} else if let screenName = deriveScreenName(urlComponents) {
retrieveUser(screenName: screenName) { result in
switch result {
case .success(let user):
if let userName = user.name {
var urlComponents = URLComponents(string: Self.homeURL)
urlComponents?.path = "/\(screenName)"
completion(.success(FeedProviderFeedMetaData(name: userName, homePageURL: urlComponents?.url?.absoluteString)))
} else {
completion(.failure(TwitterFeedProviderError.screenNameNotFound))
}
case .failure(let error):
completion(.failure(error))
}
}
} else {
completion(.failure(TwitterFeedProviderError.unknown))
}
}
}
public func refresh(_ webFeed: WebFeed, completion: @escaping (Result<Set<ParsedItem>, Error>) -> Void) {
guard let urlComponents = URLComponents(string: webFeed.url) else {
completion(.failure(TwitterFeedProviderError.unknown))
return
}
let api: String
var parameters = [String: Any]()
var isSearch = false
parameters["count"] = 20
switch urlComponents.path {
case "", "/", "/home":
parameters["count"] = 100
if let sinceToken = webFeed.sinceToken, let sinceID = Int(sinceToken) {
parameters["since_id"] = sinceID
}
api = "statuses/home_timeline.json"
case "/notifications/mentions":
api = "statuses/mentions_timeline.json"
case "/search":
api = "search/tweets.json"
if let query = urlComponents.queryItems?.first(where: { $0.name == "q" })?.value {
parameters["q"] = query
}
isSearch = true
default:
if let hashtag = deriveHashtag(urlComponents) {
api = "search/tweets.json"
parameters["q"] = "#\(hashtag)"
isSearch = true
} else if let listID = deriveListID(urlComponents) {
api = "lists/statuses.json"
parameters["list_id"] = listID
} else if let screenName = deriveScreenName(urlComponents) {
api = "statuses/user_timeline.json"
parameters["exclude_replies"] = true
parameters["screen_name"] = screenName
} else {
completion(.failure(TwitterFeedProviderError.unknown))
return
}
}
retrieveTweets(api: api, parameters: parameters, isSearch: isSearch) { result in
switch result {
case .success(let statuses):
if let sinceID = statuses.first?.idStr {
webFeed.sinceToken = sinceID
}
self.parsingQueue.async {
let parsedItems = self.makeParsedItems(webFeed.url, statuses)
DispatchQueue.main.async {
completion(.success(parsedItems))
}
}
case .failure(let error):
completion(.failure(error))
}
}
}
public static func buildURL(_ type: TwitterFeedType, username: String?, screenName: String?, searchField: String?) -> URL? {
var components = URLComponents()
components.scheme = "https"
components.host = "twitter.com"
switch type {
case .homeTimeline:
guard let username = username else {
return nil
}
components.user = username
case .mentions:
guard let username = username else {
return nil
}
components.user = username
components.path = "/notifications/mentions"
case .screenName:
guard let screenName = screenName else {
return nil
}
components.path = "/\(screenName)"
case .search:
guard let searchField = searchField else {
return nil
}
components.path = "/search"
components.queryItems = [URLQueryItem(name: "q", value: searchField)]
}
return components.url
}
}
// MARK: OAuth1SwiftProvider
extension TwitterFeedProvider: OAuth1SwiftProvider {
public static var callbackURL: URL {
return URL(string: "netnewswire://")!
}
public static var oauth1Swift: OAuth1Swift {
return OAuth1Swift(
consumerKey: SecretsManager.provider.twitterConsumerKey,
consumerSecret: SecretsManager.provider.twitterConsumerSecret,
requestTokenUrl: "https://api.twitter.com/oauth/request_token",
authorizeUrl: "https://api.twitter.com/oauth/authorize",
accessTokenUrl: "https://api.twitter.com/oauth/access_token"
)
}
}
// MARK: Private
private extension TwitterFeedProvider {
func deriveHashtag(_ urlComponents: URLComponents) -> String? {
let path = urlComponents.path
if path.starts(with: "/hashtag/"), let startIndex = path.index(path.startIndex, offsetBy: 9, limitedBy: path.endIndex), startIndex < path.endIndex {
return String(path[startIndex..<path.endIndex])
}
return nil
}
func deriveScreenName(_ urlComponents: URLComponents) -> String? {
let path = urlComponents.path
guard !Self.reservedPaths.contains(path) else { return nil }
if path.isEmpty || Self.userPaths.contains(path) {
return screenName
} else {
return String(path.suffix(from: path.index(path.startIndex, offsetBy: 1)))
}
}
func deriveListID(_ urlComponents: URLComponents) -> String? {
let path = urlComponents.path
guard path.starts(with: "/i/lists/") else { return nil }
return String(path.suffix(from: path.index(path.startIndex, offsetBy: 9)))
}
func retrieveUser(screenName: String, completion: @escaping (Result<TwitterUser, Error>) -> Void) {
let url = "\(Self.apiBase)users/show.json"
let parameters = ["screen_name": screenName]
client.get(url, parameters: parameters, headers: Self.userAgentHeaders) { result in
switch result {
case .success(let response):
let decoder = JSONDecoder()
do {
let user = try decoder.decode(TwitterUser.self, from: response.data)
completion(.success(user))
} catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
func retrieveList(listID: String, completion: @escaping (Result<TwitterList, Error>) -> Void) {
let url = "\(Self.apiBase)lists/show.json"
let parameters = ["list_id": listID]
client.get(url, parameters: parameters, headers: Self.userAgentHeaders) { result in
switch result {
case .success(let response):
let decoder = JSONDecoder()
do {
let collection = try decoder.decode(TwitterList.self, from: response.data)
completion(.success(collection))
} catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
}
}
func retrieveTweets(api: String, parameters: [String: Any], isSearch: Bool, completion: @escaping (Result<[TwitterStatus], Error>) -> Void) {
let url = "\(Self.apiBase)\(api)"
var expandedParameters = parameters
expandedParameters["tweet_mode"] = "extended"
if let remaining = rateLimitRemaining, let reset = rateLimitReset, remaining < 1 && reset > Date() {
completion(.failure(TwitterFeedProviderError.rateLimitExceeded))
return
}
client.get(url, parameters: expandedParameters, headers: Self.userAgentHeaders) { result in
switch result {
case .success(let response):
let decoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.locale = Locale.init(identifier: "en_US_POSIX")
dateFormatter.dateFormat = Self.dateFormat
decoder.dateDecodingStrategy = .formatted(dateFormatter)
if let remaining = response.response.value(forHTTPHeaderField: "x-rate-limit-remaining") {
self.rateLimitRemaining = Int(remaining) ?? 0
}
if let reset = response.response.value(forHTTPHeaderField: "x-rate-limit-reset") {
self.rateLimitReset = Date(timeIntervalSince1970: Double(reset) ?? 0)
}
self.parsingQueue.async {
do {
let tweets: [TwitterStatus]
if isSearch {
let searchResult = try decoder.decode(TwitterSearchResult.self, from: response.data)
if let statuses = searchResult.statuses {
tweets = statuses
} else {
tweets = [TwitterStatus]()
}
} else {
tweets = try decoder.decode([TwitterStatus].self, from: response.data)
}
DispatchQueue.main.async {
completion(.success(tweets))
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
case .failure(let error):
if error.errorCode == -11 {
// Eat these errors. They are old or invalid URL requests.
completion(.success([TwitterStatus]()))
} else {
completion(.failure(error))
}
}
}
}
func makeParsedItems(_ webFeedURL: String, _ statuses: [TwitterStatus]) -> Set<ParsedItem> {
var parsedItems = Set<ParsedItem>()
for status in statuses {
guard let idStr = status.idStr, let statusURL = status.url else { continue }
let parsedItem = ParsedItem(syncServiceID: nil,
uniqueID: idStr,
feedURL: webFeedURL,
url: statusURL,
externalURL: nil,
title: nil,
language: nil,
contentHTML: status.renderAsHTML(),
contentText: status.renderAsText(),
summary: nil,
imageURL: nil,
bannerImageURL: nil,
datePublished: status.createdAt,
dateModified: nil,
authors: makeParsedAuthors(status.user),
tags: nil,
attachments: nil)
parsedItems.insert(parsedItem)
}
return parsedItems
}
func makeUserURL(_ screenName: String) -> String {
return "https://twitter.com/\(screenName)"
}
func makeParsedAuthors(_ user: TwitterUser?) -> Set<ParsedAuthor>? {
guard let user = user else { return nil }
return Set([ParsedAuthor(name: user.name, url: user.url, avatarURL: user.avatarURL, emailAddress: nil)])
}
}

View File

@ -1,28 +0,0 @@
//
// TwitterHashtag.swift
// Account
//
// Created by Maurice Parker on 4/18/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct TwitterHashtag: Codable, TwitterEntity {
let text: String?
let indices: [Int]?
enum CodingKeys: String, CodingKey {
case text = "text"
case indices = "indices"
}
func renderAsHTML() -> String {
var html = String()
if let text = text {
html += "<a href=\"https://twitter.com/search?q=%23\(text)\">#\(text)</a>"
}
return html
}
}

View File

@ -1,17 +0,0 @@
//
// TwitterList.swift
//
//
// Created by Maurice Parker on 8/14/20.
//
import Foundation
struct TwitterList: Codable {
let name: String?
enum CodingKeys: String, CodingKey {
case name = "name"
}
}

View File

@ -1,22 +0,0 @@
//
// TwitterMedia.swift
// Account
//
// Created by Maurice Parker on 4/20/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct TwitterMedia: Codable, TwitterEntity {
let indices: [Int]?
enum CodingKeys: String, CodingKey {
case indices = "indices"
}
func renderAsHTML() -> String {
return String()
}
}

View File

@ -1,33 +0,0 @@
//
// TwitterMention.swift
// Account
//
// Created by Maurice Parker on 4/18/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct TwitterMention: Codable, TwitterEntity {
let name: String?
let indices: [Int]?
let screenName: String?
let idStr: String?
enum CodingKeys: String, CodingKey {
case name = "url"
case indices = "indices"
case screenName = "screen_name"
case idStr = "idStr"
}
func renderAsHTML() -> String {
var html = String()
if let screenName = screenName {
html += "<a href=\"https://twitter.com/\(screenName)\">@\(screenName)</a>"
}
return html
}
}

View File

@ -1,19 +0,0 @@
//
// TwitterSearchResult.swift
// Account
//
// Created by Maurice Parker on 4/18/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct TwitterSearchResult: Codable {
let statuses: [TwitterStatus]?
enum CodingKeys: String, CodingKey {
case statuses = "statuses"
}
}

View File

@ -1,169 +0,0 @@
//
// TwitterStatus.swift
// Account
//
// Created by Maurice Parker on 4/16/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
final class TwitterStatus: Codable {
let createdAt: Date?
let idStr: String?
let fullText: String?
let displayTextRange: [Int]?
let user: TwitterUser?
let truncated: Bool?
let retweeted: Bool?
let retweetedStatus: TwitterStatus?
let quotedStatus: TwitterStatus?
let entities: TwitterEntities?
let extendedEntities: TwitterExtendedEntities?
enum CodingKeys: String, CodingKey {
case createdAt = "created_at"
case idStr = "id_str"
case fullText = "full_text"
case displayTextRange = "display_text_range"
case user = "user"
case truncated = "truncated"
case retweeted = "retweeted"
case retweetedStatus = "retweeted_status"
case quotedStatus = "quoted_status"
case entities = "entities"
case extendedEntities = "extended_entities"
}
var url: String? {
guard let userURL = user?.url, let idStr = idStr else { return nil }
return "\(userURL)/status/\(idStr)"
}
func renderAsText() -> String? {
let statusToRender = retweetedStatus != nil ? retweetedStatus! : self
return statusToRender.displayText
}
func renderAsHTML(topLevel: Bool = true) -> String {
if let retweetedStatus = retweetedStatus {
return renderAsRetweetHTML(retweetedStatus)
}
if let quotedStatus = quotedStatus {
return renderAsQuoteHTML(quotedStatus, topLevel: topLevel)
}
return renderAsOriginalHTML(topLevel: topLevel)
}
}
private extension TwitterStatus {
var displayText: String? {
if let text = fullText, let displayRange = displayTextRange, displayRange.count > 1,
let startIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex),
let endIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) {
return String(text[startIndex..<endIndex])
} else {
return fullText
}
}
var displayHTML: String? {
if let text = fullText, let displayRange = displayTextRange, displayRange.count > 1, let entities = entities?.combineAndSort() {
let displayStartIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex) ?? text.startIndex
let displayEndIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex) ?? text.endIndex
var html = String()
var prevIndex = displayStartIndex
var unicodeScalarOffset = 0
for entity in entities {
// The twitter indices are messed up by characters with more than one scalar, we are going to adjust for that here.
let endIndex = text.index(text.startIndex, offsetBy: entity.endIndex, limitedBy: text.endIndex) ?? text.endIndex
if prevIndex < endIndex {
let characters = String(text[prevIndex..<endIndex])
for character in characters {
unicodeScalarOffset += character.unicodeScalars.count - 1
}
}
let offsetStartIndex = unicodeScalarOffset < entity.startIndex ? entity.startIndex - unicodeScalarOffset : entity.startIndex
let offsetEndIndex = unicodeScalarOffset < entity.endIndex ? entity.endIndex - unicodeScalarOffset : entity.endIndex
let entityStartIndex = text.index(text.startIndex, offsetBy: offsetStartIndex, limitedBy: text.endIndex) ?? text.startIndex
let entityEndIndex = text.index(text.startIndex, offsetBy: offsetEndIndex, limitedBy: text.endIndex) ?? text.endIndex
if prevIndex < entityStartIndex {
html += String(text[prevIndex..<entityStartIndex]).replacingOccurrences(of: "\n", with: "<br>")
}
// We drop off any URL which is just pointing to the quoted status. It is redundant.
if let twitterURL = entity as? TwitterURL, let expandedURL = twitterURL.expandedURL, let quotedURL = quotedStatus?.url {
if expandedURL.caseInsensitiveCompare(quotedURL) != .orderedSame {
html += entity.renderAsHTML()
}
} else {
html += entity.renderAsHTML()
}
prevIndex = entityEndIndex
}
if prevIndex < displayEndIndex {
html += String(text[prevIndex..<displayEndIndex]).replacingOccurrences(of: "\n", with: "<br>")
}
return html
} else {
return displayText
}
}
func renderAsTweetHTML(_ status: TwitterStatus, topLevel: Bool) -> String {
var html = "<div>\(status.displayHTML ?? "")</div>"
if !topLevel, let createdAt = status.createdAt, let url = status.url {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .short
html += "<a href=\"\(url)\" class=\"twitterTimestamp\">\(dateFormatter.string(from: createdAt))</a>"
}
return html
}
func renderAsOriginalHTML(topLevel: Bool) -> String {
var html = renderAsTweetHTML(self, topLevel: topLevel)
html += extendedEntities?.renderAsHTML() ?? ""
return html
}
func renderAsRetweetHTML(_ status: TwitterStatus) -> String {
var html = "<blockquote>"
if let userHTML = status.user?.renderAsHTML() {
html += userHTML
}
html += status.renderAsHTML(topLevel: false)
html += "</blockquote>"
return html
}
func renderAsQuoteHTML(_ quotedStatus: TwitterStatus, topLevel: Bool) -> String {
var html = String()
html += renderAsTweetHTML(self, topLevel: topLevel)
html += extendedEntities?.renderAsHTML() ?? ""
html += "<blockquote>"
if let userHTML = quotedStatus.user?.renderAsHTML() {
html += userHTML
}
html += quotedStatus.renderAsHTML(topLevel: false)
html += "</blockquote>"
return html
}
}

View File

@ -1,29 +0,0 @@
//
// TwitterSymbol.swift
// Account
//
// Created by Maurice Parker on 4/18/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct TwitterSymbol: Codable, TwitterEntity {
let text: String?
let indices: [Int]?
enum CodingKeys: String, CodingKey {
case text = "text"
case indices = "indices"
}
func renderAsHTML() -> String {
var html = String()
if let text = text {
html += "<a href=\"https://twitter.com/search?q=%24\(text)\">$\(text)</a>"
}
return html
}
}

View File

@ -1,33 +0,0 @@
//
// TwitterURL.swift
// Account
//
// Created by Maurice Parker on 4/18/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct TwitterURL: Codable, TwitterEntity {
let url: String?
let indices: [Int]?
let displayURL: String?
let expandedURL: String?
enum CodingKeys: String, CodingKey {
case url = "url"
case indices = "indices"
case displayURL = "display_url"
case expandedURL = "expanded_url"
}
func renderAsHTML() -> String {
var html = String()
if let expandedURL = expandedURL, let displayURL = displayURL {
html += "<a href=\"\(expandedURL)\">\(displayURL)</a>"
}
return html
}
}

View File

@ -1,45 +0,0 @@
//
// TwitterUser.swift
// Account
//
// Created by Maurice Parker on 4/16/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct TwitterUser: Codable {
let name: String?
let screenName: String?
let avatarURL: String?
enum CodingKeys: String, CodingKey {
case name = "name"
case screenName = "screen_name"
case avatarURL = "profile_image_url_https"
}
var url: String {
return "https://twitter.com/\(screenName ?? "")"
}
func renderAsHTML() -> String? {
var html = String()
html += "<div><a href=\"\(url)\">"
if let avatarURL = avatarURL {
html += "<img class=\"twitterAvatar nnw-nozoom\" src=\"\(avatarURL)\">"
}
html += "<div class=\"twitterUsername\">"
if let name = name {
html += " \(name)"
}
html += "<br><span class=\"twitterScreenName\">"
if let screenName = screenName {
html += " @\(screenName)"
}
html += "</span></div></a></div>"
return html
}
}

View File

@ -1,34 +0,0 @@
//
// TwitterVideoInfo.swift
// Account
//
// Created by Maurice Parker on 4/18/20.
// Copyright © 2020 Ranchero Software, LLC. All rights reserved.
//
import Foundation
struct TwitterVideo: Codable {
let variants: [Variant]?
enum CodingKeys: String, CodingKey {
case variants = "variants"
}
struct Variant: Codable {
let bitrate: Int?
let contentType: String?
let url: String?
enum CodingKeys: String, CodingKey {
case bitrate = "bitrate"
case contentType = "content_type"
case url = "url"
}
}
}

View File

@ -244,17 +244,13 @@ public final class WebFeed: Feed, Renamable, Hashable {
// MARK: - NotificationDisplayName
public var notificationDisplayName: String {
#if os(macOS)
if self.url.contains("twitter.com") {
return NSLocalizedString("Show notifications for new tweets", comment: "notifyNameDisplay / Twitter")
} else if self.url.contains("www.reddit.com") {
if self.url.contains("www.reddit.com") {
return NSLocalizedString("Show notifications for new posts", comment: "notifyNameDisplay / Reddit")
} else {
return NSLocalizedString("Show notifications for new articles", comment: "notifyNameDisplay / Default")
}
#else
if self.url.contains("twitter.com") {
return NSLocalizedString("Notify about new tweets", comment: "notifyNameDisplay / Twitter")
} else if self.url.contains("www.reddit.com") {
if self.url.contains("www.reddit.com") {
return NSLocalizedString("Notify about new posts", comment: "notifyNameDisplay / Reddit")
} else {
return NSLocalizedString("Notify about new articles", comment: "notifyNameDisplay / Default")

View File

@ -14,8 +14,6 @@ struct FeedlyTestSecrets: SecretsProvider {
var mercuryClientSecret = ""
var feedlyClientId = ""
var feedlyClientSecret = ""
var twitterConsumerKey = ""
var twitterConsumerSecret = ""
var redditConsumerKey = ""
var inoreaderAppId = ""
var inoreaderAppKey = ""

View File

@ -91,10 +91,6 @@ struct AppAssets {
return RSImage(named: "extensionPointReddit")!
}()
static var extensionPointTwitter: RSImage = {
return RSImage(named: "extensionPointTwitter")!
}()
static var faviconTemplateImage: RSImage = {
return RSImage(named: "faviconTemplateImage")!
}()

View File

@ -478,13 +478,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
return ExtensionPointManager.shared.isRedditEnabled
}
if item.action == #selector(showAddTwitterFeedWindow(_:)) {
guard !isDisplayingSheet && isSpecialAccountAvailable && ExtensionPointManager.shared.isTwitterEnabled else {
return false
}
return ExtensionPointManager.shared.isTwitterEnabled
}
#if !MAC_APP_STORE
if item.action == #selector(toggleWebInspectorEnabled(_:)) {
(item as! NSMenuItem).state = AppDefaults.shared.webInspectorEnabled ? .on : .off
@ -564,12 +557,6 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserInterfaceValidations,
addFeedController?.showAddFeedSheet(.redditFeed)
}
@IBAction func showAddTwitterFeedWindow(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
addFeedController = AddFeedController(hostWindow: mainWindowController!.window!)
addFeedController?.showAddFeedSheet(.twitterFeed)
}
@IBAction func showAddFolderWindow(_ sender: Any?) {
createAndShowMainWindowIfNecessary()
showAddFolderSheetOnWindow(mainWindowController!.window!)

View File

@ -1,200 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="17154" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="17154"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="AddTwitterFeedWindowController" customModule="NetNewsWire" customModuleProvider="target">
<connections>
<outlet property="accountLabel" destination="Acr-Ig-NVG" id="1gD-BE-CjH"/>
<outlet property="accountPopupButton" destination="X1H-Vv-1CJ" id="I0k-bb-XcU"/>
<outlet property="addButton" destination="dtI-Hu-rFb" id="D11-zR-dWH"/>
<outlet property="folderPopupButton" destination="6vt-DL-mVR" id="98M-xt-ZYU"/>
<outlet property="nameTextField" destination="TzV-3k-fXd" id="h4h-5v-4cY"/>
<outlet property="screenSearchTextField" destination="cEh-Wt-f5D" id="bnp-Zp-1fe"/>
<outlet property="typeDescriptionLabel" destination="f4Z-B8-HHm" id="jZ2-gz-Zr2"/>
<outlet property="typePopupButton" destination="j18-w8-wsH" id="KFC-K4-0tG"/>
<outlet property="window" destination="QvC-M9-y7g" id="7rH-S2-LF4"/>
</connections>
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Add Twitter Feed" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="QvC-M9-y7g">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="196" y="240" width="306" height="216"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1415"/>
<view key="contentView" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="306" height="216"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hXq-IS-19x">
<rect key="frame" x="119" y="13" width="88" height="32"/>
<buttonCell key="cell" type="push" title="Cancel" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Dop-HC-6Q9">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
Gw
</string>
</buttonCell>
<connections>
<action selector="cancel:" target="-2" id="tcT-tt-t99"/>
</connections>
</button>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dtI-Hu-rFb">
<rect key="frame" x="205" y="13" width="88" height="32"/>
<buttonCell key="cell" type="push" title="Add" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="6NK-Ql-drk">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
<string key="keyEquivalent" base64-UTF8="YES">
DQ
</string>
</buttonCell>
<connections>
<action selector="addFeed:" target="-2" id="Ilv-Un-eDp"/>
</connections>
</button>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="ddC-6D-Tvd">
<rect key="frame" x="40" y="177" width="41" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Type:" id="qto-IO-a1j">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="j18-w8-wsH">
<rect key="frame" x="85" y="172" width="204" height="25"/>
<popUpButtonCell key="cell" type="push" title="Home Timeline" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="uE6-1a-w5g" id="bad-PM-uqO">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="Ibj-Uy-KK7">
<items>
<menuItem title="Home Timeline" state="on" id="uE6-1a-w5g"/>
<menuItem title="Mentions" tag="1" id="177-F8-Esj"/>
<menuItem title="Screen Name" tag="2" id="DBZ-RV-FfV"/>
<menuItem title="Search" tag="3" id="0gG-oY-8yR"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="selectedType:" target="-2" id="eAs-So-odx"/>
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Acr-Ig-NVG">
<rect key="frame" x="18" y="146" width="63" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Account:" id="LFf-JL-Ahl">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="X1H-Vv-1CJ">
<rect key="frame" x="85" y="141" width="204" height="25"/>
<popUpButtonCell key="cell" type="push" title="@username" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="Tfk-aQ-RKg" id="HPE-P1-Hje">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="TmQ-5T-oaz">
<items>
<menuItem title="@username" state="on" id="Tfk-aQ-RKg"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="cEh-Wt-f5D">
<rect key="frame" x="87" y="144" width="199" height="21"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" borderStyle="bezel" drawsBackground="YES" usesSingleLineMode="YES" id="NLJ-ih-hZ8">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
<outlet property="delegate" destination="-2" id="hNy-Li-bjr"/>
</connections>
</textField>
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="f4Z-B8-HHm">
<rect key="frame" x="85" y="122" width="203" height="14"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" title="Label" usesSingleLineMode="YES" id="5AA-um-oEb">
<font key="font" metaFont="menu" size="11"/>
<color key="textColor" name="secondaryLabelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="sM9-DX-M0c">
<rect key="frame" x="35" y="94" width="46" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Name:" id="8ca-Qp-BkT">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TzV-3k-fXd" userLabel="Name Text Field">
<rect key="frame" x="87" y="91" width="199" height="21"/>
<textFieldCell key="cell" lineBreakMode="truncatingTail" selectable="YES" editable="YES" sendsActionOnEndEditing="YES" state="on" borderStyle="bezel" placeholderString="Optional" drawsBackground="YES" usesSingleLineMode="YES" id="pLP-pL-5R5">
<font key="font" metaFont="system"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="dNV-oD-vzR">
<rect key="frame" x="31" y="61" width="50" height="16"/>
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Folder:" id="Kwx-7B-CIu">
<font key="font" metaFont="systemBold"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="6vt-DL-mVR" userLabel="Folder Popup">
<rect key="frame" x="85" y="57" width="204" height="25"/>
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="tLJ-zY-CcZ" id="0cM-5q-Snl">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="OpL-Uf-woJ">
<items>
<menuItem title="Item 1" state="on" id="tLJ-zY-CcZ"/>
<menuItem title="Item 2" id="APc-af-7Um"/>
<menuItem title="Item 3" id="j09-9b-bGs"/>
</items>
</menu>
</popUpButtonCell>
</popUpButton>
</subviews>
<constraints>
<constraint firstItem="dNV-oD-vzR" firstAttribute="baseline" secondItem="6vt-DL-mVR" secondAttribute="baseline" id="14b-jN-4Y6"/>
<constraint firstItem="X1H-Vv-1CJ" firstAttribute="firstBaseline" secondItem="Acr-Ig-NVG" secondAttribute="firstBaseline" id="3Cl-Bw-Pcy"/>
<constraint firstItem="X1H-Vv-1CJ" firstAttribute="top" secondItem="j18-w8-wsH" secondAttribute="bottom" constant="10" id="48A-2f-2Wq"/>
<constraint firstAttribute="bottom" secondItem="dtI-Hu-rFb" secondAttribute="bottom" constant="20" symbolic="YES" id="6ac-2K-RnD"/>
<constraint firstItem="cEh-Wt-f5D" firstAttribute="leading" secondItem="j18-w8-wsH" secondAttribute="leading" id="73d-zR-g8z"/>
<constraint firstItem="TzV-3k-fXd" firstAttribute="leading" secondItem="cEh-Wt-f5D" secondAttribute="leading" id="Ap9-Ln-amq"/>
<constraint firstAttribute="trailing" secondItem="X1H-Vv-1CJ" secondAttribute="trailing" constant="20" id="Boa-Qw-dIK"/>
<constraint firstItem="TzV-3k-fXd" firstAttribute="leading" secondItem="sM9-DX-M0c" secondAttribute="trailing" constant="8" id="Ebw-Fa-w9o"/>
<constraint firstItem="TzV-3k-fXd" firstAttribute="top" secondItem="f4Z-B8-HHm" secondAttribute="bottom" constant="10" id="Elk-Gm-e4i"/>
<constraint firstItem="X1H-Vv-1CJ" firstAttribute="leading" secondItem="Acr-Ig-NVG" secondAttribute="trailing" constant="8" id="HwM-IS-kMa"/>
<constraint firstItem="dtI-Hu-rFb" firstAttribute="width" secondItem="hXq-IS-19x" secondAttribute="width" id="J80-aG-OjE"/>
<constraint firstItem="sM9-DX-M0c" firstAttribute="baseline" secondItem="TzV-3k-fXd" secondAttribute="baseline" id="K9a-t8-khQ"/>
<constraint firstAttribute="trailing" secondItem="f4Z-B8-HHm" secondAttribute="trailing" constant="20" id="POl-uX-qpn"/>
<constraint firstItem="f4Z-B8-HHm" firstAttribute="leading" secondItem="j18-w8-wsH" secondAttribute="leading" id="RbK-fc-c6E"/>
<constraint firstItem="hXq-IS-19x" firstAttribute="centerY" secondItem="dtI-Hu-rFb" secondAttribute="centerY" id="Sgq-Cy-rII"/>
<constraint firstItem="6vt-DL-mVR" firstAttribute="top" secondItem="TzV-3k-fXd" secondAttribute="bottom" constant="10" id="Sjo-Bv-alZ"/>
<constraint firstAttribute="trailing" secondItem="TzV-3k-fXd" secondAttribute="trailing" constant="20" symbolic="YES" id="V1s-JA-hA8"/>
<constraint firstItem="6vt-DL-mVR" firstAttribute="leading" secondItem="dNV-oD-vzR" secondAttribute="trailing" constant="8" id="WNy-vn-p8M"/>
<constraint firstItem="f4Z-B8-HHm" firstAttribute="top" secondItem="cEh-Wt-f5D" secondAttribute="bottom" constant="8" id="WiN-GE-aPh"/>
<constraint firstAttribute="trailing" secondItem="cEh-Wt-f5D" secondAttribute="trailing" constant="20" id="ZSt-ga-a8N"/>
<constraint firstItem="dtI-Hu-rFb" firstAttribute="leading" secondItem="hXq-IS-19x" secondAttribute="trailing" constant="12" symbolic="YES" id="ahD-oU-iFu"/>
<constraint firstItem="Acr-Ig-NVG" firstAttribute="leading" secondItem="EiT-Mj-1SZ" secondAttribute="leading" constant="20" id="dhv-D0-aPe"/>
<constraint firstAttribute="trailing" secondItem="j18-w8-wsH" secondAttribute="trailing" constant="20" id="eQ9-hw-PXg"/>
<constraint firstItem="j18-w8-wsH" firstAttribute="top" secondItem="EiT-Mj-1SZ" secondAttribute="top" constant="20" symbolic="YES" id="fK6-IW-NhJ"/>
<constraint firstItem="j18-w8-wsH" firstAttribute="leading" secondItem="X1H-Vv-1CJ" secondAttribute="leading" id="gSv-gG-TLd"/>
<constraint firstItem="6vt-DL-mVR" firstAttribute="leading" secondItem="TzV-3k-fXd" secondAttribute="leading" id="hMP-wG-fsP"/>
<constraint firstItem="cEh-Wt-f5D" firstAttribute="top" secondItem="j18-w8-wsH" secondAttribute="bottom" constant="10" id="hxS-Z9-dWU"/>
<constraint firstItem="j18-w8-wsH" firstAttribute="firstBaseline" secondItem="ddC-6D-Tvd" secondAttribute="firstBaseline" id="iz7-4p-NWj"/>
<constraint firstAttribute="trailing" secondItem="dtI-Hu-rFb" secondAttribute="trailing" constant="20" symbolic="YES" id="kEo-af-SUe"/>
<constraint firstItem="j18-w8-wsH" firstAttribute="leading" secondItem="ddC-6D-Tvd" secondAttribute="trailing" constant="8" id="n9D-4Y-HXk"/>
<constraint firstAttribute="trailing" secondItem="6vt-DL-mVR" secondAttribute="trailing" constant="20" id="suO-dd-E0b"/>
</constraints>
</view>
<point key="canvasLocation" x="102" y="-768"/>
</window>
</objects>
</document>

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="19162" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="19162"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21507"/>
</dependencies>
<scenes>
<!--Application-->
@ -80,12 +80,6 @@
<action selector="showAddRedditFeedWindow:" target="Ady-hI-5gd" id="Irh-Rw-mFK"/>
</connections>
</menuItem>
<menuItem title="New Twitter Feed…" id="ki4-7l-tM6">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="showAddTwitterFeedWindow:" target="Ady-hI-5gd" id="dZR-aU-O52"/>
</connections>
</menuItem>
<menuItem title="New Folder…" keyEquivalent="N" id="wkh-LX-Xp1">
<connections>
<action selector="showAddFolderWindow:" target="Ady-hI-5gd" id="GIi-wc-uYk"/>

View File

@ -48,9 +48,6 @@ class AddFeedController: AddFeedWindowControllerDelegate {
case .redditFeed:
addFeedWindowController = AddRedditFeedWindowController(folderTreeController: folderTreeController,
delegate: self)
case .twitterFeed:
addFeedWindowController = AddTwitterFeedWindowController(folderTreeController: folderTreeController,
delegate: self)
}
addFeedWindowController!.runSheetOnWindow(hostWindow)

View File

@ -12,7 +12,6 @@ import Account
enum AddFeedWindowControllerType {
case webFeed
case redditFeed
case twitterFeed
}
protocol AddFeedWindowControllerDelegate: AnyObject {

View File

@ -1,194 +0,0 @@
//
// AddTwitterFeedWindowController.swift
// NetNewsWire
//
// Created by Maurice Parker on 4/21/20.
// Copyright © 2020 Ranchero Software. All rights reserved.
//
import AppKit
import RSCore
import RSTree
import Articles
import Account
class AddTwitterFeedWindowController : NSWindowController, AddFeedWindowController {
@IBOutlet weak var typePopupButton: NSPopUpButton!
@IBOutlet weak var typeDescriptionLabel: NSTextField!
@IBOutlet weak var accountLabel: NSTextField!
@IBOutlet weak var accountPopupButton: NSPopUpButton!
@IBOutlet weak var screenSearchTextField: NSTextField!
@IBOutlet var nameTextField: NSTextField!
@IBOutlet var addButton: NSButton!
@IBOutlet var folderPopupButton: NSPopUpButton!
private weak var delegate: AddFeedWindowControllerDelegate?
private var folderTreeController: TreeController!
private var userEnteredScreenSearch: String? {
var s = screenSearchTextField.stringValue
s = s.collapsingWhitespace
if s.isEmpty {
return nil
}
return s
}
private var userEnteredTitle: String? {
var s = nameTextField.stringValue
s = s.collapsingWhitespace
if s.isEmpty {
return nil
}
return s
}
var hostWindow: NSWindow!
convenience init(folderTreeController: TreeController, delegate: AddFeedWindowControllerDelegate?) {
self.init(windowNibName: NSNib.Name("AddTwitterFeedSheet"))
self.folderTreeController = folderTreeController
self.delegate = delegate
}
func runSheetOnWindow(_ hostWindow: NSWindow) {
hostWindow.beginSheet(window!) { (returnCode: NSApplication.ModalResponse) -> Void in
}
}
override func windowDidLoad() {
let accountMenu = NSMenu()
for feedProvider in ExtensionPointManager.shared.activeFeedProviders {
if let twitterFeedProvider = feedProvider as? TwitterFeedProvider {
let accountMenuItem = NSMenuItem()
accountMenuItem.title = "@\(twitterFeedProvider.screenName)"
accountMenu.addItem(accountMenuItem)
}
}
accountPopupButton.menu = accountMenu
folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController.rootNode, restrictToSpecialAccounts: true)
if let container = AddWebFeedDefaultContainer.defaultContainer {
if let folder = container as? Folder, let account = folder.account {
FolderTreeMenu.select(account: account, folder: folder, in: folderPopupButton)
} else {
if let account = container as? Account {
FolderTreeMenu.select(account: account, folder: nil, in: folderPopupButton)
}
}
}
updateUI()
}
// MARK: Actions
@IBAction func selectedType(_ sender: Any) {
screenSearchTextField.stringValue = ""
updateUI()
}
@IBAction func cancel(_ sender: Any?) {
cancelSheet()
}
@IBAction func addFeed(_ sender: Any?) {
guard let type = TwitterFeedType(rawValue: typePopupButton.selectedItem?.tag ?? 0),
let atUsername = accountPopupButton.selectedItem?.title else { return }
let username = String(atUsername[atUsername.index(atUsername.startIndex, offsetBy: 1)..<atUsername.endIndex])
var screenSearch = userEnteredScreenSearch
if let screenName = screenSearch, type == .screenName && screenName.starts(with: "@") {
screenSearch = String(screenName[screenName.index(screenName.startIndex, offsetBy: 1)..<screenName.endIndex])
}
guard let url = TwitterFeedProvider.buildURL(type, username: username, screenName: screenSearch, searchField: screenSearch) else { return }
let container = selectedContainer()!
AddWebFeedDefaultContainer.saveDefaultContainer(container)
delegate?.addFeedWindowController(self, userEnteredURL: url, userEnteredTitle: userEnteredTitle, container: container)
}
}
extension AddTwitterFeedWindowController: NSTextFieldDelegate {
func controlTextDidChange(_ obj: Notification) {
updateUI()
}
}
private extension AddTwitterFeedWindowController {
private func updateUI() {
switch typePopupButton.selectedItem?.tag ?? 0 {
case 0:
accountLabel.isHidden = false
accountPopupButton.isHidden = false
typeDescriptionLabel.stringValue = NSLocalizedString("Tweets from everyone you follow", comment: "Home Timeline")
screenSearchTextField.isHidden = true
addButton.isEnabled = true
case 1:
accountLabel.isHidden = false
accountPopupButton.isHidden = false
typeDescriptionLabel.stringValue = NSLocalizedString("Tweets mentioning you", comment: "Mentions")
screenSearchTextField.isHidden = true
addButton.isEnabled = true
case 2:
accountLabel.isHidden = true
accountPopupButton.isHidden = true
var screenSearch = userEnteredScreenSearch
if screenSearch != nil {
if let screenName = screenSearch, screenName.starts(with: "@") {
screenSearch = String(screenName[screenName.index(screenName.startIndex, offsetBy: 1)..<screenName.endIndex])
}
typeDescriptionLabel.stringValue = NSLocalizedString("Tweets from @\(screenSearch!)", comment: "Home Timeline")
} else {
typeDescriptionLabel.stringValue = ""
}
screenSearchTextField.placeholderString = NSLocalizedString("@name", comment: "@name")
screenSearchTextField.isHidden = false
addButton.isEnabled = !screenSearchTextField.stringValue.isEmpty
default:
accountLabel.isHidden = true
accountPopupButton.isHidden = true
if !screenSearchTextField.stringValue.isEmpty {
typeDescriptionLabel.stringValue = NSLocalizedString("Tweets that contain \(screenSearchTextField.stringValue)", comment: "Home Timeline")
} else {
typeDescriptionLabel.stringValue = ""
}
screenSearchTextField.placeholderString = NSLocalizedString("Search Term or #hashtag", comment: "Search Term")
screenSearchTextField.isHidden = false
addButton.isEnabled = !screenSearchTextField.stringValue.isEmpty
}
}
func cancelSheet() {
delegate?.addFeedWindowControllerUserDidCancel(self)
}
func selectedContainer() -> Container? {
return folderPopupButton.selectedItem?.representedObject as? Container
}
}

View File

@ -1446,11 +1446,6 @@ private extension MainWindowController {
newRedditFeedItem.action = Selector(("showAddRedditFeedWindow:"))
menu.addItem(newRedditFeedItem)
let newTwitterFeedItem = NSMenuItem()
newTwitterFeedItem.title = NSLocalizedString("New Twitter Feed…", comment: "New Twitter Feed")
newTwitterFeedItem.action = Selector(("showAddTwitterFeedWindow:"))
menu.addItem(newTwitterFeedItem)
let newFolderFeedItem = NSMenuItem()
newFolderFeedItem.title = NSLocalizedString("New Folder…", comment: "New Folder")
newFolderFeedItem.action = Selector(("showAddFolderWindow:"))

View File

@ -1,25 +0,0 @@
{
"images" : [
{
"filename" : "twitter24x24.png",
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "twitter48x48.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "original"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

View File

@ -213,10 +213,6 @@
5144EA52227B8E4500D19003 /* AccountsFeedbin.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */; };
5148F44B2336DB4700F8CD8B /* MasterTimelineTitleView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */; };
5148F4552336DB7000F8CD8B /* MasterTimelineTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */; };
514A89A2244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */; };
514A89A3244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */ = {isa = PBXBuildFile; fileRef = 514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */; };
514A89A5244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */; };
514A89A6244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */; };
514B7C8323205EFB00BAC947 /* RootSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */; };
514C16CE24D2E63F009A3AFA /* Account in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16CD24D2E63F009A3AFA /* Account */; };
514C16DE24D2EF15009A3AFA /* RSTree in Frameworks */ = {isa = PBXBuildFile; productRef = 514C16DD24D2EF15009A3AFA /* RSTree */; };
@ -225,8 +221,6 @@
5154368B229404D1005E1CDF /* FaviconGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51EF0F76227716200050506E /* FaviconGenerator.swift */; };
515A50E6243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; };
515A50E7243D07A90089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; };
515A5107243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; };
515A5108243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; };
515A5148243E64BA0089E588 /* ExtensionPointEnableWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */; };
515A5149243E64BA0089E588 /* ExtensionPointEnableWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */; };
515A516E243E7F950089E588 /* ExtensionPointDetail.xib in Resources */ = {isa = PBXBuildFile; fileRef = 515A516D243E7F950089E588 /* ExtensionPointDetail.xib */; };
@ -237,7 +231,6 @@
515A5178243E90200089E588 /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; };
515A517B243E90260089E588 /* ExtensionPoint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 510C43F6243D035C009F70C3 /* ExtensionPoint.swift */; };
515A517C243E90260089E588 /* ExtensionPointManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A50E5243D07A90089E588 /* ExtensionPointManager.swift */; };
515A5180243E90260089E588 /* TwitterFeedProvider-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */; };
515A5181243E90260089E588 /* ExtensionPointIdentifer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 515A5176243E90200089E588 /* ExtensionPointIdentifer.swift */; };
515D4FCA23257CB500EE1167 /* Node-Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 849A97971ED9EFAA007D329B /* Node-Extensions.swift */; };
515D4FCC2325815A00EE1167 /* SafariExt.js in Resources */ = {isa = PBXBuildFile; fileRef = 515D4FCB2325815A00EE1167 /* SafariExt.js */; };
@ -1233,11 +1226,8 @@
5144EA50227B8E4500D19003 /* AccountsFeedbin.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsFeedbin.xib; sourceTree = "<group>"; };
5148F44A2336DB4700F8CD8B /* MasterTimelineTitleView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MasterTimelineTitleView.xib; sourceTree = "<group>"; };
5148F4542336DB7000F8CD8B /* MasterTimelineTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterTimelineTitleView.swift; sourceTree = "<group>"; };
514A8980244FD63F0085E65D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Mac/Base.lproj/AddTwitterFeedSheet.xib; sourceTree = SOURCE_ROOT; };
514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = AddTwitterFeedWindowController.swift; path = AddFeed/AddTwitterFeedWindowController.swift; sourceTree = "<group>"; };
514B7C8223205EFB00BAC947 /* RootSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootSplitViewController.swift; sourceTree = "<group>"; };
515A50E5243D07A90089E588 /* ExtensionPointManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointManager.swift; sourceTree = "<group>"; };
515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TwitterFeedProvider-Extensions.swift"; sourceTree = "<group>"; };
515A5147243E64BA0089E588 /* ExtensionPointEnableWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointEnableWindowController.swift; sourceTree = "<group>"; };
515A516D243E7F950089E588 /* ExtensionPointDetail.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ExtensionPointDetail.xib; sourceTree = "<group>"; };
515A5170243E802B0089E588 /* ExtensionPointDetailViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionPointDetailViewController.swift; sourceTree = "<group>"; };
@ -1841,7 +1831,6 @@
515A50E5243D07A90089E588 /* ExtensionPointManager.swift */,
84A1500420048DDF0046AD9A /* SendToMarsEditCommand.swift */,
84A14FF220048CA70046AD9A /* SendToMicroBlogCommand.swift */,
515A5106243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift */,
5193CD57245E44A90092735E /* RedditFeedProvider-Extensions.swift */,
);
path = ExtensionPoints;
@ -2380,8 +2369,6 @@
51A052CD244FB9D6006C2024 /* AddFeedWIndowController.swift */,
51333D392468615D00EB5C91 /* AddRedditFeedSheet.xib */,
51333D1524685D2E00EB5C91 /* AddRedditFeedWindowController.swift */,
514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */,
514A89A4244FD6640085E65D /* AddTwitterFeedWindowController.swift */,
848363002262A3BC00DA1D35 /* AddWebFeedSheet.xib */,
849A97521ED9EAC0007D329B /* AddWebFeedWindowController.swift */,
51EC114B2149FE3300B296E3 /* FolderTreeMenu.swift */,
@ -3386,7 +3373,6 @@
65ED405C235DEF6C0081F399 /* ImportOPMLSheet.xib in Resources */,
51DEE81926FBFF84006DAA56 /* Promenade.nnwtheme in Resources */,
65ED405D235DEF6C0081F399 /* SidebarKeyboardShortcuts.plist in Resources */,
514A89A3244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */,
51D0214726ED617100FF2E0F /* core.css in Resources */,
5103A9F5242258C600410853 /* AccountsAddCloudKit.xib in Resources */,
65ED405E235DEF6C0081F399 /* DefaultFeeds.opml in Resources */,
@ -3513,7 +3499,6 @@
49F40DF82335B71000552BF4 /* newsfoot.js in Resources */,
51333D3B2468615D00EB5C91 /* AddRedditFeedSheet.xib in Resources */,
BDCB516724282C8A00102A80 /* AccountsNewsBlur.xib in Resources */,
514A89A2244FD63F0085E65D /* AddTwitterFeedSheet.xib in Resources */,
5103A9982421643300410853 /* blank.html in Resources */,
515A516E243E7F950089E588 /* ExtensionPointDetail.xib in Resources */,
84BAE64921CEDAF20046DB56 /* CrashReporterWindow.xib in Resources */,
@ -3935,7 +3920,6 @@
65ED3FEC235DEF6C0081F399 /* RSHTMLMetadata+Extension.swift in Sources */,
518C3194237B00DA004D740F /* DetailIconSchemeHandler.swift in Sources */,
65ED3FED235DEF6C0081F399 /* SendToMarsEditCommand.swift in Sources */,
514A89A6244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */,
65ED3FEE235DEF6C0081F399 /* UserNotificationManager.swift in Sources */,
653813182680E152007A082C /* AccountType+Helpers.swift in Sources */,
65ED3FEF235DEF6C0081F399 /* ScriptingObjectContainer.swift in Sources */,
@ -4008,7 +3992,6 @@
65ED4028235DEF6C0081F399 /* ExtractedArticle.swift in Sources */,
65ED4029235DEF6C0081F399 /* DeleteCommand.swift in Sources */,
65ED402A235DEF6C0081F399 /* AddWebFeedWindowController.swift in Sources */,
515A5108243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */,
65ED402B235DEF6C0081F399 /* ImportOPMLWindowController.swift in Sources */,
65ED402C235DEF6C0081F399 /* TimelineTableView.swift in Sources */,
178A9F9E2549449F00AB7E9D /* AddAccountsView.swift in Sources */,
@ -4179,7 +4162,6 @@
512E094D2268B8AB00BDCFDD /* DeleteCommand.swift in Sources */,
5110C37D2373A8D100A9C04F /* InspectorIconHeaderView.swift in Sources */,
51F85BFB2275D85000C787DC /* Array-Extensions.swift in Sources */,
515A5180243E90260089E588 /* TwitterFeedProvider-Extensions.swift in Sources */,
51C452AC22650FD200C03939 /* AppNotifications.swift in Sources */,
51EF0F7E2277A57D0050506E /* MasterTimelineAccessibilityCellLayout.swift in Sources */,
51A1699B235E10D700EB091F /* AccountInspectorViewController.swift in Sources */,
@ -4269,7 +4251,6 @@
849C78922362AB04009A71E4 /* ExportOPMLWindowController.swift in Sources */,
84CC88181FE59CBF00644329 /* SmartFeedsController.swift in Sources */,
849A97661ED9EB96007D329B /* SidebarViewController.swift in Sources */,
514A89A5244FD6640085E65D /* AddTwitterFeedWindowController.swift in Sources */,
849A97641ED9EB96007D329B /* SidebarOutlineView.swift in Sources */,
5127B238222B4849006D641D /* DetailKeyboardDelegate.swift in Sources */,
8405DD9922153B6B008CE1BF /* TimelineContainerView.swift in Sources */,
@ -4335,7 +4316,6 @@
5144EA51227B8E4500D19003 /* AccountsFeedbinWindowController.swift in Sources */,
84AD1EBC2032AF5C00BC20B7 /* SidebarOutlineDataSource.swift in Sources */,
845A29241FC9255E007B49E3 /* SidebarCellAppearance.swift in Sources */,
515A5107243D0CCD0089E588 /* TwitterFeedProvider-Extensions.swift in Sources */,
845EE7B11FC2366500854A1F /* StarredFeedDelegate.swift in Sources */,
848F6AE51FC29CFB002D422E /* FaviconDownloader.swift in Sources */,
511B9806237DCAC90028BCAA /* UserInfoKey.swift in Sources */,
@ -4547,14 +4527,6 @@
name = MainInterface.storyboard;
sourceTree = "<group>";
};
514A897F244FD63F0085E65D /* AddTwitterFeedSheet.xib */ = {
isa = PBXVariantGroup;
children = (
514A8980244FD63F0085E65D /* Base */,
);
name = AddTwitterFeedSheet.xib;
sourceTree = "<group>";
};
51C0307F257D815A00609262 /* UnifiedWindow.storyboard */ = {
isa = PBXVariantGroup;
children = (

View File

@ -13,8 +13,6 @@ public protocol SecretsProvider {
var mercuryClientSecret: String { get }
var feedlyClientId: String { get }
var feedlyClientSecret: String { get }
var twitterConsumerKey: String { get }
var twitterConsumerSecret: String { get }
var redditConsumerKey: String { get }
var inoreaderAppId: String { get }
var inoreaderAppKey: String { get }

View File

@ -281,30 +281,6 @@ blockquote {
border-top: 1px solid var(--header-table-border-color);
}
/* Twitter */
.twitterAvatar {
vertical-align: middle;
border-radius: 4px;
height: 1.7em;
width: 1.7em;
}
.twitterUsername {
line-height: 1.2;
margin-left: 4px;
display: inline-block;
vertical-align: middle;
}
.twitterScreenName {
font-size: 66%;
}
.twitterTimestamp {
font-size: 66%;
}
/* Newsfoot theme for light mode (default) */
.newsfoot-footnote-popover {
background: #ccc;

View File

@ -15,7 +15,6 @@ enum ExtensionPointIdentifer: Hashable {
case marsEdit
case microblog
#endif
case twitter(String)
case reddit(String)
var extensionPointType: ExtensionPoint.Type {
@ -26,8 +25,6 @@ enum ExtensionPointIdentifer: Hashable {
case .microblog:
return SendToMicroBlogCommand.self
#endif
case .twitter:
return TwitterFeedProvider.self
case .reddit:
return RedditFeedProvider.self
}
@ -45,11 +42,6 @@ enum ExtensionPointIdentifer: Hashable {
"type": "microblog"
]
#endif
case .twitter(let screenName):
return [
"type": "twitter",
"screenName": screenName
]
case .reddit(let username):
return [
"type": "reddit",
@ -68,9 +60,6 @@ enum ExtensionPointIdentifer: Hashable {
case "microblog":
self = ExtensionPointIdentifer.microblog
#endif
case "twitter":
guard let screenName = userInfo["screenName"] as? String else { return nil }
self = ExtensionPointIdentifer.twitter(screenName)
case "reddit":
guard let username = userInfo["username"] as? String else { return nil }
self = ExtensionPointIdentifer.reddit(username)
@ -87,9 +76,6 @@ enum ExtensionPointIdentifer: Hashable {
case .microblog:
hasher.combine("microblog")
#endif
case .twitter(let screenName):
hasher.combine("twitter")
hasher.combine(screenName)
case .reddit(let username):
hasher.combine("reddit")
hasher.combine(username)

View File

@ -69,16 +69,12 @@ final class ExtensionPointManager: FeedProviderManagerDelegate {
return activeExtensionPoints.values.compactMap({ return $0 as? FeedProvider })
}
var isTwitterEnabled: Bool {
return activeExtensionPoints.values.contains(where: { $0 is TwitterFeedProvider })
}
var isRedditEnabled: Bool {
return activeExtensionPoints.values.contains(where: { $0 is RedditFeedProvider })
}
init() {
possibleExtensionPointTypes = [TwitterFeedProvider.self, RedditFeedProvider.self]
possibleExtensionPointTypes = [RedditFeedProvider.self]
loadExtensionPoints()
}
@ -121,12 +117,6 @@ private extension ExtensionPointManager {
func extensionPoint(for extensionPointType: ExtensionPoint.Type, tokenSuccess: OAuthSwift.TokenSuccess?, completion: @escaping (Result<ExtensionPoint, Error>) -> Void) {
switch extensionPointType {
case is TwitterFeedProvider.Type:
if let tokenSuccess = tokenSuccess, let twitter = TwitterFeedProvider(tokenSuccess: tokenSuccess) {
completion(.success(twitter))
} else {
completion(.failure(ExtensionPointManagerError.unableToCreate))
}
case is RedditFeedProvider.Type:
if let tokenSuccess = tokenSuccess {
RedditFeedProvider.create(tokenSuccess: tokenSuccess) { result in
@ -147,8 +137,6 @@ private extension ExtensionPointManager {
func extensionPoint(for extensionPointID: ExtensionPointIdentifer) -> ExtensionPoint? {
switch extensionPointID {
case .twitter(let screenName):
return TwitterFeedProvider(screenName: screenName)
case .reddit(let username):
return RedditFeedProvider(username: username)
#if os(macOS)

View File

@ -2,7 +2,7 @@
%{
import os
secrets = ['FEED_WRANGLER_KEY', 'MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'TWITTER_CONSUMER_KEY', 'TWITTER_CONSUMER_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
secrets = ['FEED_WRANGLER_KEY', 'MERCURY_CLIENT_ID', 'MERCURY_CLIENT_SECRET', 'FEEDLY_CLIENT_ID', 'FEEDLY_CLIENT_SECRET', 'REDDIT_CONSUMER_KEY', 'INOREADER_APP_ID', 'INOREADER_APP_KEY']
def chunks(seq, size):
return (seq[i:(i + size)] for i in range(0, len(seq), size))

View File

@ -20,7 +20,7 @@ RSDatabase uses FMDB for SQLite persistence.
Required by OAuthSwift as a testing dependency. Not shipped in NNW.
## [OAuthSwift](https://github.com/OAuthSwift/OAuthSwift)
Our Reddit and Twitter use the OAuth framework to authenticate with the services
Our Reddit integration uses the OAuth framework to authenticate with the services
and then service requests to them.
## [PLCrashReporter](https://github.com/microsoft/plcrashreporter)