NetNewsWire/Frameworks/Account/FeedProvider/Twitter/TwitterStatus.swift

164 lines
4.8 KiB
Swift
Raw Normal View History

//
// 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?
2020-04-18 19:03:16 +02:00
let entities: TwitterEntities?
2020-04-18 20:46:28 +02:00
let extendedEntities: TwitterExtendedEntities?
2020-04-18 19:03:16 +02:00
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"
2020-04-18 19:03:16 +02:00
case entities = "entities"
2020-04-18 20:46:28 +02:00
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
}
2020-04-18 23:01:18 +02:00
func renderAsHTML(topLevel: Bool = true) -> String {
2020-04-18 17:41:18 +02:00
if let retweetedStatus = retweetedStatus {
2020-04-18 23:01:18 +02:00
return renderAsRetweetHTML(retweetedStatus, topLevel: topLevel)
2020-04-18 17:41:18 +02:00
}
if let quotedStatus = quotedStatus {
2020-04-18 23:01:18 +02:00
return renderAsQuoteHTML(quotedStatus, topLevel: topLevel)
2020-04-18 17:41:18 +02:00
}
2020-04-18 23:01:18 +02:00
return renderAsOriginalHTML(topLevel: topLevel)
2020-04-18 17:41:18 +02:00
}
}
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 displayStartIndex = text.index(text.startIndex, offsetBy: displayRange[0], limitedBy: text.endIndex),
let displayEndIndex = text.index(text.startIndex, offsetBy: displayRange[1], limitedBy: text.endIndex),
let entities = entities?.combineAndSort() {
var html = String()
var prevIndex = displayStartIndex
for entity in entities {
if let entityStartIndex = text.index(text.startIndex, offsetBy: entity.startIndex, limitedBy: text.endIndex),
let entityEndIndex = text.index(text.startIndex, offsetBy: entity.endIndex, limitedBy: text.endIndex) {
if prevIndex < entityStartIndex {
html += String(text[prevIndex..<entityStartIndex])
}
// 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])
}
return html
} else {
return displayText
}
}
2020-04-18 23:27:19 +02:00
func renderAsTweetHTML(_ status: TwitterStatus, topLevel: Bool) -> String {
var html = "<div>\(status.displayHTML ?? "")</div>"
2020-04-18 23:27:19 +02:00
if !topLevel, let createdAt = status.createdAt, let url = status.url {
2020-04-18 23:27:19 +02:00
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .short
2020-04-20 14:37:06 +02:00
html += "<a href=\"\(url)\" class=\"twitterTimestamp\">\(dateFormatter.string(from: createdAt))</a>"
2020-04-18 23:27:19 +02:00
}
return html
2020-04-18 20:46:28 +02:00
}
2020-04-18 23:01:18 +02:00
func renderAsOriginalHTML(topLevel: Bool) -> String {
2020-04-18 23:27:19 +02:00
var html = renderAsTweetHTML(self, topLevel: topLevel)
2020-04-18 23:01:18 +02:00
if topLevel {
html += extendedEntities?.renderAsHTML() ?? ""
}
2020-04-18 20:46:28 +02:00
return html
2020-04-18 17:41:18 +02:00
}
2020-04-18 23:01:18 +02:00
func renderAsRetweetHTML(_ status: TwitterStatus, topLevel: Bool) -> String {
2020-04-18 20:46:28 +02:00
var html = "<blockquote>"
if let userHTML = status.user?.renderAsHTML() {
2020-04-18 17:41:18 +02:00
html += userHTML
}
2020-04-18 23:01:18 +02:00
html += status.renderAsHTML(topLevel: false)
2020-04-18 17:41:18 +02:00
html += "</blockquote>"
2020-04-18 23:01:18 +02:00
if topLevel {
html += status.extendedEntities?.renderAsHTML() ?? ""
}
2020-04-18 17:41:18 +02:00
return html
}
2020-04-18 23:01:18 +02:00
func renderAsQuoteHTML(_ quotedStatus: TwitterStatus, topLevel: Bool) -> String {
2020-04-18 17:41:18 +02:00
var html = String()
2020-04-18 23:27:19 +02:00
html += renderAsTweetHTML(self, topLevel: topLevel)
2020-04-18 17:41:18 +02:00
html += "<blockquote>"
2020-04-18 20:46:28 +02:00
if let userHTML = quotedStatus.user?.renderAsHTML() {
2020-04-18 17:41:18 +02:00
html += userHTML
}
2020-04-18 23:01:18 +02:00
html += quotedStatus.renderAsHTML(topLevel: false)
2020-04-18 17:41:18 +02:00
html += "</blockquote>"
2020-04-18 20:46:28 +02:00
html += self.extendedEntities?.renderAsHTML() ?? ""
2020-04-18 23:01:18 +02:00
if topLevel {
html += quotedStatus.extendedEntities?.renderAsHTML() ?? ""
}
2020-04-18 17:41:18 +02:00
return html
}
}