Integrate RSS feed proxy

pull/81/head
Niels Andriesse 5 years ago
parent c1c04b7ef2
commit e95f91558b

@ -28,41 +28,44 @@ public final class LokiRSSFeedPoller : NSObject {
private func poll() {
let feed = self.feed
let url = URL(string: feed.server)!
FeedParser(URL: url).parseAsync { wrapper in
guard case .rss(let x) = wrapper, let items = x.items else { return print("[Loki] Failed to parse RSS feed for: \(feed.server).") }
items.reversed().forEach { item in
guard let title = item.title, let description = item.description, let date = item.pubDate else { return }
let timestamp = UInt64(date.timeIntervalSince1970 * 1000)
let urlRegex = try! NSRegularExpression(pattern: "<a\\s+(?:[^>]*?\\s+)?href=\"([^\"]*)\".*?>(.*?)<.*?\\/a>")
var bodyAsHTML = "\(title)<br><br>\(description)".replacingOccurrences(of: "</p>", with: "</p><br>")
while true {
guard let match = urlRegex.firstMatch(in: bodyAsHTML, options: [], range: NSRange(location: 0, length: bodyAsHTML.utf16.count)) else { break }
let matchRange = match.range(at: 0)
let urlRange = match.range(at: 1)
let descriptionRange = match.range(at: 2)
let url = (bodyAsHTML as NSString).substring(with: urlRange)
let description = (bodyAsHTML as NSString).substring(with: descriptionRange)
bodyAsHTML = (bodyAsHTML as NSString).replacingCharacters(in: matchRange, with: "\(description) (\(url))") as String
}
guard let bodyAsData = bodyAsHTML.data(using: String.Encoding.unicode) else { return }
let options = [ NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html ]
guard let body = try? NSAttributedString(data: bodyAsData, options: options, documentAttributes: nil).string else { return }
let id = LKGroupUtilities.getEncodedRSSFeedIDAsData(feed.id)
let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver)
groupContext.setName(feed.displayName)
let dataMessage = SSKProtoDataMessage.builder()
dataMessage.setTimestamp(timestamp)
dataMessage.setGroup(try! groupContext.build())
dataMessage.setBody(body)
let content = SSKProtoContent.builder()
content.setDataMessage(try! dataMessage.build())
let envelope = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp)
envelope.setSource(NSLocalizedString("Loki", comment: ""))
envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
envelope.setContent(try! content.build().serializedData())
OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: 0)
let url = feed.server
let _ = LokiRSSFeedProxy.fetchContent(for: url).done { xml in
guard let data = xml.data(using: String.Encoding.utf8) else { return print("[Loki] Failed to parse RSS feed for: \(feed.server).") }
FeedParser(data: data).parseAsync { wrapper in
guard case .rss(let x) = wrapper, let items = x.items else { return print("[Loki] Failed to parse RSS feed for: \(feed.server).") }
items.reversed().forEach { item in
guard let title = item.title, let description = item.description, let date = item.pubDate else { return }
let timestamp = UInt64(date.timeIntervalSince1970 * 1000)
let urlRegex = try! NSRegularExpression(pattern: "<a\\s+(?:[^>]*?\\s+)?href=\"([^\"]*)\".*?>(.*?)<.*?\\/a>")
var bodyAsHTML = "\(title)<br><br>\(description)".replacingOccurrences(of: "</p>", with: "</p><br>")
while true {
guard let match = urlRegex.firstMatch(in: bodyAsHTML, options: [], range: NSRange(location: 0, length: bodyAsHTML.utf16.count)) else { break }
let matchRange = match.range(at: 0)
let urlRange = match.range(at: 1)
let descriptionRange = match.range(at: 2)
let url = (bodyAsHTML as NSString).substring(with: urlRange)
let description = (bodyAsHTML as NSString).substring(with: descriptionRange)
bodyAsHTML = (bodyAsHTML as NSString).replacingCharacters(in: matchRange, with: "\(description) (\(url))") as String
}
guard let bodyAsData = bodyAsHTML.data(using: String.Encoding.unicode) else { return }
let options = [ NSAttributedString.DocumentReadingOptionKey.documentType : NSAttributedString.DocumentType.html ]
guard let body = try? NSAttributedString(data: bodyAsData, options: options, documentAttributes: nil).string else { return }
let id = LKGroupUtilities.getEncodedRSSFeedIDAsData(feed.id)
let groupContext = SSKProtoGroupContext.builder(id: id, type: .deliver)
groupContext.setName(feed.displayName)
let dataMessage = SSKProtoDataMessage.builder()
dataMessage.setTimestamp(timestamp)
dataMessage.setGroup(try! groupContext.build())
dataMessage.setBody(body)
let content = SSKProtoContent.builder()
content.setDataMessage(try! dataMessage.build())
let envelope = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp)
envelope.setSource(NSLocalizedString("Loki", comment: ""))
envelope.setSourceDevice(OWSDevicePrimaryDeviceId)
envelope.setContent(try! content.build().serializedData())
OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! envelope.build(), plaintextData: try! content.build().serializedData(), wasReceivedByUD: false, transaction: transaction, serverID: 0)
}
}
}
}

@ -87,23 +87,42 @@ internal class LokiFileServerProxy : LokiHTTPClient {
task.resume()
return promise
}.map { rawResponse in
guard let data = rawResponse as? Data, !data.isEmpty, let cipherText = Data(base64Encoded: data) else {
print("[Loki] Received a non-string encoded response.")
guard let data = rawResponse as? Data, !data.isEmpty else {
print("[Loki] Received an empty response.")
return rawResponse
}
var uncheckedStatusCode: Int? = nil
let uncheckedCipherText: Data?
if let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON, let base64EncodedData = json["data"] as? String {
if let meta = json["meta"] as? JSON { uncheckedStatusCode = meta["code"] as? Int }
uncheckedCipherText = Data(base64Encoded: base64EncodedData)
} else {
uncheckedCipherText = data
}
guard let cipherText = uncheckedCipherText else {
print("[Loki] Received an invalid response.")
throw Error.proxyResponseParsingFailed
}
let response = try DiffieHellman.decrypt(cipherText, using: symmetricKey)
let uncheckedJSON = try? JSONSerialization.jsonObject(with: response, options: .allowFragments) as? JSON
guard let json = uncheckedJSON, let httpStatusCode = json["status"] as? Int else { throw HTTPError.networkError(code: -1, response: nil, underlyingError: Error.proxyResponseParsingFailed) }
let isSuccess = (200..<300).contains(httpStatusCode)
guard let json = uncheckedJSON else { throw HTTPError.networkError(code: -1, response: nil, underlyingError: Error.proxyResponseParsingFailed) }
if uncheckedStatusCode == nil {
uncheckedStatusCode = json["status"] as? Int
}
guard let statusCode = uncheckedStatusCode else {
print("[Loki] Received an invalid response.")
throw Error.proxyResponseParsingFailed
}
let isSuccess = (200..<300).contains(statusCode)
var body: Any? = nil
if let bodyAsString = json["body"] as? String {
body = bodyAsString
if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? [String: Any] {
if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? JSON {
body = bodyAsJSON
}
}
guard isSuccess else { throw HTTPError.networkError(code: httpStatusCode, response: body, underlyingError: Error.fileServerHTTPError(code: httpStatusCode, message: body)) }
return body
guard isSuccess else { throw HTTPError.networkError(code: statusCode, response: body, underlyingError: Error.fileServerHTTPError(code: statusCode, message: body)) }
return body ?? response
}.recover { error -> Promise<Any> in
print("[Loki] File server proxy request failed with error: \(error.localizedDescription).")
throw HTTPError.from(error: error) ?? error

@ -1,26 +1,26 @@
import PromiseKit
internal enum LokiRSSFeedProxy {
public enum LokiRSSFeedProxy {
internal enum Error : LocalizedError {
public enum Error : LocalizedError {
case proxyResponseParsingFailed
internal var errorDescription: String? {
public var errorDescription: String? {
switch self {
case .proxyResponseParsingFailed: return "Couldn't parse proxy response."
}
}
}
internal static func fetchContent(for url: String) -> Promise<String> {
public static func fetchContent(for url: String) -> Promise<String> {
let server = LokiStorageAPI.server
let endpoints = [ "messenger-updates/feed" : "loki/v1/rss/messenger", "loki.network/feed" : "loki/v1/rss/loki" ]
let endpoint = endpoints.first { url.lowercased().contains($0.key) }!.value
let url = URL(string: server + "/" + endpoint)!
let request = TSRequest(url: url)
return LokiFileServerProxy(for: server).perform(request).map { response -> String in
guard let json = response as? JSON, let data = json["data"] as? String else { throw Error.proxyResponseParsingFailed }
return data
guard let data = response as? Data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? JSON, let xml = json["data"] as? String else { throw Error.proxyResponseParsingFailed }
return xml
}
}
}

@ -78,7 +78,7 @@ internal class LokiSnodeProxy : LokiHTTPClient {
var body: Any? = nil
if let bodyAsString = json["body"] as? String {
body = bodyAsString
if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? [String: Any] {
if let bodyAsJSON = try? JSONSerialization.jsonObject(with: bodyAsString.data(using: .utf8)!, options: .allowFragments) as? JSON {
body = bodyAsJSON
}
}

Loading…
Cancel
Save