Merge pull request #41 from loki-project/rss

RSS Feed Support
pull/42/head
gmbnt 6 years ago committed by GitHub
commit b61b440063
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -73,6 +73,7 @@ target 'Signal' do
# Loki # Loki
pod 'GCDWebServer', '~> 3.0' pod 'GCDWebServer', '~> 3.0'
pod 'FeedKit', '~> 8.1'
target 'SignalTests' do target 'SignalTests' do
inherit! :search_paths inherit! :search_paths

@ -36,6 +36,7 @@ PODS:
- Curve25519Kit/Tests (2.1.0): - Curve25519Kit/Tests (2.1.0):
- CocoaLumberjack - CocoaLumberjack
- SignalCoreKit - SignalCoreKit
- FeedKit (8.1.1)
- GCDWebServer (3.5.2): - GCDWebServer (3.5.2):
- GCDWebServer/Core (= 3.5.2) - GCDWebServer/Core (= 3.5.2)
- GCDWebServer/Core (3.5.2) - GCDWebServer/Core (3.5.2)
@ -198,6 +199,7 @@ DEPENDENCIES:
- CryptoSwift - CryptoSwift
- Curve25519Kit (from `https://github.com/signalapp/Curve25519Kit`) - Curve25519Kit (from `https://github.com/signalapp/Curve25519Kit`)
- Curve25519Kit/Tests (from `https://github.com/signalapp/Curve25519Kit`) - Curve25519Kit/Tests (from `https://github.com/signalapp/Curve25519Kit`)
- FeedKit (~> 8.1)
- GCDWebServer (~> 3.0) - GCDWebServer (~> 3.0)
- GRKOpenSSLFramework (from `https://github.com/signalapp/GRKOpenSSLFramework`) - GRKOpenSSLFramework (from `https://github.com/signalapp/GRKOpenSSLFramework`)
- HKDFKit (from `https://github.com/signalapp/HKDFKit.git`) - HKDFKit (from `https://github.com/signalapp/HKDFKit.git`)
@ -224,6 +226,7 @@ SPEC REPOS:
- AFNetworking - AFNetworking
- CocoaLumberjack - CocoaLumberjack
- CryptoSwift - CryptoSwift
- FeedKit
- GCDWebServer - GCDWebServer
- IGIdenticon - IGIdenticon
- libPhoneNumber-iOS - libPhoneNumber-iOS
@ -297,6 +300,7 @@ SPEC CHECKSUMS:
CocoaLumberjack: 2f44e60eb91c176d471fdba43b9e3eae6a721947 CocoaLumberjack: 2f44e60eb91c176d471fdba43b9e3eae6a721947
CryptoSwift: d81eeaa59dc5a8d03720fe919a6fd07b51f7439f CryptoSwift: d81eeaa59dc5a8d03720fe919a6fd07b51f7439f
Curve25519Kit: b3e77b7152ebe95fee2b3fb6c955449492bc14f7 Curve25519Kit: b3e77b7152ebe95fee2b3fb6c955449492bc14f7
FeedKit: 3418eed25f0b493b205b4de1b8511ac21d413fa9
GCDWebServer: ead88cd14596dd4eae4f5830b8877c87c8728990 GCDWebServer: ead88cd14596dd4eae4f5830b8877c87c8728990
GRKOpenSSLFramework: 8a3735ad41e7dc1daff460467bccd32ca5d6ae3e GRKOpenSSLFramework: 8a3735ad41e7dc1daff460467bccd32ca5d6ae3e
HKDFKit: 3b6dbbb9d59c221cc6c52c3aa915700cbf24e376 HKDFKit: 3b6dbbb9d59c221cc6c52c3aa915700cbf24e376
@ -309,7 +313,7 @@ SPEC CHECKSUMS:
SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c
SignalCoreKit: c2d8132cdedb95d35eb2f8ae7eac0957695d0a8b SignalCoreKit: c2d8132cdedb95d35eb2f8ae7eac0957695d0a8b
SignalMetadataKit: 6fa5e9a53c7f104568662521a2f3874672ff7a02 SignalMetadataKit: 6fa5e9a53c7f104568662521a2f3874672ff7a02
SignalServiceKit: 5c5b63a39d5054201ab59ef6daf0fa0a1a0c7887 SignalServiceKit: 102576f58e17a5fe3093899adce7e7c192a7bee0
SQLCipher: efbdb52cdbe340bcd892b1b14297df4e07241b7f SQLCipher: efbdb52cdbe340bcd892b1b14297df4e07241b7f
SSZipArchive: 8e859da2520142e09166bc9161967db296e9d02f SSZipArchive: 8e859da2520142e09166bc9161967db296e9d02f
Starscream: ef3ece99d765eeccb67de105bfa143f929026cf5 Starscream: ef3ece99d765eeccb67de105bfa143f929026cf5
@ -317,6 +321,6 @@ SPEC CHECKSUMS:
YapDatabase: b418a4baa6906e8028748938f9159807fd039af4 YapDatabase: b418a4baa6906e8028748938f9159807fd039af4
YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54 YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54
PODFILE CHECKSUM: 10152a1fffafd51206b62fdd8cac86a5de8cf083 PODFILE CHECKSUM: 95f41137d4fe8c5b8a27de951b328f8c9531d166
COCOAPODS: 1.7.2 COCOAPODS: 1.5.3

@ -1 +1 @@
Subproject commit d9ab8b13002bf6ebc932ca4f45df56b577b6a188 Subproject commit 20b736ae28ecd42b5fc13f583a010ac9354be507

@ -3276,12 +3276,13 @@
files = ( files = (
); );
inputPaths = ( inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Signal/Pods-Signal-frameworks.sh", "${SRCROOT}/Pods/Target Support Files/Pods-Signal/Pods-Signal-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework", "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework",
"${BUILT_PRODUCTS_DIR}/AxolotlKit/AxolotlKit.framework", "${BUILT_PRODUCTS_DIR}/AxolotlKit/AxolotlKit.framework",
"${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework",
"${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework",
"${BUILT_PRODUCTS_DIR}/Curve25519Kit/Curve25519Kit.framework", "${BUILT_PRODUCTS_DIR}/Curve25519Kit/Curve25519Kit.framework",
"${BUILT_PRODUCTS_DIR}/FeedKit/FeedKit.framework",
"${BUILT_PRODUCTS_DIR}/GCDWebServer/GCDWebServer.framework", "${BUILT_PRODUCTS_DIR}/GCDWebServer/GCDWebServer.framework",
"${PODS_ROOT}/GRKOpenSSLFramework/OpenSSL-iOS/bin/openssl.framework", "${PODS_ROOT}/GRKOpenSSLFramework/OpenSSL-iOS/bin/openssl.framework",
"${BUILT_PRODUCTS_DIR}/HKDFKit/HKDFKit.framework", "${BUILT_PRODUCTS_DIR}/HKDFKit/HKDFKit.framework",
@ -3309,6 +3310,7 @@
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CocoaLumberjack.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/CryptoSwift.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Curve25519Kit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Curve25519Kit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/FeedKit.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GCDWebServer.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/GCDWebServer.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/openssl.framework",
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HKDFKit.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HKDFKit.framework",
@ -3331,7 +3333,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Signal/Pods-Signal-frameworks.sh\"\n"; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Signal/Pods-Signal-frameworks.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
6565655F4068F9E5CDC5687F /* [CP] Check Pods Manifest.lock */ = { 6565655F4068F9E5CDC5687F /* [CP] Check Pods Manifest.lock */ = {
@ -3358,7 +3360,7 @@
files = ( files = (
); );
inputPaths = ( inputPaths = (
"${PODS_ROOT}/Target Support Files/Pods-SignalTests/Pods-SignalTests-frameworks.sh", "${SRCROOT}/Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests-frameworks.sh",
"${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework", "${BUILT_PRODUCTS_DIR}/AFNetworking/AFNetworking.framework",
"${BUILT_PRODUCTS_DIR}/AxolotlKit/AxolotlKit.framework", "${BUILT_PRODUCTS_DIR}/AxolotlKit/AxolotlKit.framework",
"${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework",
@ -3409,7 +3411,7 @@
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh; shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-SignalTests/Pods-SignalTests-frameworks.sh\"\n"; shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-SignalTests/Pods-SignalTests-frameworks.sh\"\n";
showEnvVarsInLog = 0; showEnvVarsInLog = 0;
}; };
F4C416F20E3CB0B25DC10C56 /* [CP] Check Pods Manifest.lock */ = { F4C416F20E3CB0B25DC10C56 /* [CP] Check Pods Manifest.lock */ = {

@ -8,6 +8,7 @@ extern NSString *const AppDelegateStoryboardMain;
@interface AppDelegate : UIResponder <UIApplicationDelegate> @interface AppDelegate : UIResponder <UIApplicationDelegate>
- (void)startPublicChatPollingIfNeeded; - (void)createGroupChatsIfNeeded;
- (void)startGroupChatPollersIfNeeded;
@end @end

@ -65,6 +65,8 @@ static NSTimeInterval launchStartedAt;
@property (nonatomic) BOOL didAppLaunchFail; @property (nonatomic) BOOL didAppLaunchFail;
@property (nonatomic) LKP2PServer *lokiP2PServer; @property (nonatomic) LKP2PServer *lokiP2PServer;
@property (nonatomic) LKGroupChatPoller *lokiPublicChatPoller; @property (nonatomic) LKGroupChatPoller *lokiPublicChatPoller;
@property (nonatomic) LKGroupChatPoller *lokiNewsPoller;
@property (nonatomic) LKGroupChatPoller *lokiMessengerUpdatesPoller;
@end @end
@ -1485,15 +1487,30 @@ static NSTimeInterval launchStartedAt;
#pragma mark - Loki #pragma mark - Loki
- (void)setUpPublicChatIfNeeded - (LKGroupChat *)lokiPublicChat
{ {
if (self.lokiPublicChatPoller != nil) { return; } return [[LKGroupChat alloc] initWithKindAsString:@"publicChat" id:@(LKGroupChatAPI.publicChatID).stringValue server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true];
self.lokiPublicChatPoller = [[LKGroupChatPoller alloc] initForGroup:(NSUInteger)LKGroupChatAPI.publicChatID onServer:LKGroupChatAPI.publicChatServer]; }
BOOL isPublicChatSetUp = [NSUserDefaults.standardUserDefaults boolForKey:@"isPublicChatSetUp"];
if (isPublicChatSetUp) { return; } - (LKGroupChat *)lokiNews
NSString *title = NSLocalizedString(@"Loki Public Chat", @""); {
NSData *groupID = [[[LKGroupChatAPI.publicChatServer stringByAppendingString:@"."] stringByAppendingString:@(LKGroupChatAPI.publicChatID).stringValue] dataUsingEncoding:NSUTF8StringEncoding]; return [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.feed" server:@"https://loki.network/feed/" displayName:NSLocalizedString(@"Loki News", @"") isDeletable:true];
TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:title memberIds:@[ OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey, LKGroupChatAPI.publicChatServer ] image:nil groupId:groupID]; }
- (LKGroupChat *)lokiMessengerUpdates
{
return [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.messenger-update" server:@"https://loki.network/category/messenger-updates/feed/" displayName:NSLocalizedString(@"Loki Messenger Updates", @"") isDeletable:false];
}
- (void)createGroupChatsIfNeeded
{
NSArray *allGroupChats = @[ self.lokiPublicChat, self.lokiNews, self.lokiMessengerUpdates ];
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
for (LKGroupChat *chat in allGroupChats) {
NSString *userDefaultsKey = [@"isSetUp." stringByAppendingString:chat.id];
BOOL isChatSetUp = [NSUserDefaults.standardUserDefaults boolForKey:userDefaultsKey];
if (!isChatSetUp || !chat.isDeletable) {
TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:chat.displayName memberIds:@[ userHexEncodedPublicKey, chat.server ] image:nil groupId:[chat.id dataUsingEncoding:NSUTF8StringEncoding]];
__block TSGroupThread *thread; __block TSGroupThread *thread;
[OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { [OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
thread = [TSGroupThread getOrCreateThreadWithGroupModel:group transaction:transaction]; thread = [TSGroupThread getOrCreateThreadWithGroupModel:group transaction:transaction];
@ -1506,13 +1523,24 @@ static NSTimeInterval launchStartedAt;
[thread updateWithMutedUntilDate:date transaction:transaction]; [thread updateWithMutedUntilDate:date transaction:transaction];
}]; }];
[OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread]; [OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread];
[NSUserDefaults.standardUserDefaults setBool:YES forKey:@"isPublicChatSetUp"]; [NSUserDefaults.standardUserDefaults setBool:YES forKey:userDefaultsKey];
}
}
}
- (void)createGroupChatPollersIfNeeded
{
if (self.lokiPublicChatPoller == nil) { self.lokiPublicChatPoller = [[LKGroupChatPoller alloc] initForGroup:self.lokiPublicChat]; }
if (self.lokiNewsPoller == nil) { self.lokiNewsPoller = [[LKGroupChatPoller alloc] initForGroup:self.lokiNews]; }
if (self.lokiMessengerUpdatesPoller == nil) { self.lokiMessengerUpdatesPoller = [[LKGroupChatPoller alloc] initForGroup:self.lokiMessengerUpdates]; }
} }
- (void)startPublicChatPollingIfNeeded - (void)startGroupChatPollersIfNeeded
{ {
[self setUpPublicChatIfNeeded]; [self createGroupChatPollersIfNeeded];
[self.lokiPublicChatPoller startIfNeeded]; [self.lokiPublicChatPoller startIfNeeded];
[self.lokiNewsPoller startIfNeeded];
[self.lokiMessengerUpdatesPoller startIfNeeded];
} }
@end @end

@ -1,26 +1,41 @@
import FeedKit
// TODO: Move the RSS feed logic into its own file
@objc(LKGroupChatPoller) @objc(LKGroupChatPoller)
public final class LokiGroupChatPoller : NSObject { public final class LokiGroupChatPoller : NSObject {
private let group: UInt private let group: LokiGroupChat
private let server: String
private var pollForNewMessagesTimer: Timer? = nil private var pollForNewMessagesTimer: Timer? = nil
private var pollForDeletedMessagesTimer: Timer? = nil private var pollForDeletedMessagesTimer: Timer? = nil
private var hasStarted = false private var hasStarted = false
private let pollForNewMessagesInterval: TimeInterval = 4 private lazy var pollForNewMessagesInterval: TimeInterval = {
private let pollForDeletedMessagesInterval: TimeInterval = 120 switch group.kind {
case .publicChat(_): return 4
case .rss(_): return 8 * 60
}
}()
@objc(initForGroup:onServer:) private lazy var pollForDeletedMessagesInterval: TimeInterval = {
public init(for group: UInt, on server: String) { switch group.kind {
case .publicChat(_): return 32 * 60
case .rss(_): preconditionFailure()
}
}()
@objc(initForGroup:)
public init(for group: LokiGroupChat) {
self.group = group self.group = group
self.server = server
super.init() super.init()
} }
@objc public func startIfNeeded() { @objc public func startIfNeeded() {
if hasStarted { return } if hasStarted { return }
pollForNewMessagesTimer = Timer.scheduledTimer(withTimeInterval: pollForNewMessagesInterval, repeats: true) { [weak self] _ in self?.pollForNewMessages() } pollForNewMessagesTimer = Timer.scheduledTimer(withTimeInterval: pollForNewMessagesInterval, repeats: true) { [weak self] _ in self?.pollForNewMessages() }
pollForNewMessages() // Perform initial update
if group.isPublicChat {
pollForDeletedMessagesTimer = Timer.scheduledTimer(withTimeInterval: pollForDeletedMessagesInterval, repeats: true) { [weak self] _ in self?.pollForDeletedMessages() } pollForDeletedMessagesTimer = Timer.scheduledTimer(withTimeInterval: pollForDeletedMessagesInterval, repeats: true) { [weak self] _ in self?.pollForDeletedMessages() }
}
hasStarted = true hasStarted = true
} }
@ -32,23 +47,17 @@ public final class LokiGroupChatPoller : NSObject {
private func pollForNewMessages() { private func pollForNewMessages() {
let group = self.group let group = self.group
let server = self.server func parseGroupMessage(body: String, timestamp: UInt64, senderDisplayName: String) {
let _ = LokiGroupChatAPI.getMessages(for: group, on: server).map { messages in let id = group.id.data(using: String.Encoding.utf8)!
messages.reversed().map { message in
let id = "\(server).\(group)".data(using: String.Encoding.utf8)!
let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver) let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver)
x1.setName(NSLocalizedString("Loki Public Chat", comment: "")) x1.setName(group.displayName)
let x2 = SSKProtoDataMessage.builder() let x2 = SSKProtoDataMessage.builder()
x2.setTimestamp(message.timestamp) x2.setTimestamp(timestamp)
x2.setGroup(try! x1.build()) x2.setGroup(try! x1.build())
x2.setBody(message.body) x2.setBody(body)
let x3 = SSKProtoContent.builder() let x3 = SSKProtoContent.builder()
x3.setDataMessage(try! x2.build()) x3.setDataMessage(try! x2.build())
let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp) let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp)
let senderHexEncodedPublicKey = message.hexEncodedPublicKey
let endIndex = senderHexEncodedPublicKey.endIndex
let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8)
let senderDisplayName = "\(message.displayName) (...\(senderHexEncodedPublicKey[cutoffIndex..<endIndex]))"
x4.setSource(senderDisplayName) x4.setSource(senderDisplayName)
x4.setSourceDevice(OWSDevicePrimaryDeviceId) x4.setSourceDevice(OWSDevicePrimaryDeviceId)
x4.setContent(try! x3.build().serializedData()) x4.setContent(try! x3.build().serializedData())
@ -56,6 +65,41 @@ public final class LokiGroupChatPoller : NSObject {
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! x4.build(), plaintextData: try! x3.build().serializedData(), wasReceivedByUD: false, transaction: transaction) SSKEnvironment.shared.messageManager.throws_processEnvelope(try! x4.build(), plaintextData: try! x3.build().serializedData(), wasReceivedByUD: false, transaction: transaction)
} }
} }
switch group.kind {
case .publicChat(let id):
let _ = LokiGroupChatAPI.getMessages(for: id, on: group.server).done { messages in
messages.reversed().forEach { message in
let senderHexEncodedPublicKey = message.hexEncodedPublicKey
let endIndex = senderHexEncodedPublicKey.endIndex
let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8)
let senderDisplayName = "\(message.displayName) (...\(senderHexEncodedPublicKey[cutoffIndex..<endIndex]))"
parseGroupMessage(body: message.body, timestamp: message.timestamp, senderDisplayName: senderDisplayName)
}
}
case .rss(_):
let url = URL(string: group.server)!
FeedParser(URL: url).parseAsync { wrapper in
guard case .rss(let feed) = wrapper, let items = feed.items else { return print("[Loki] Failed to parse RSS feed for: \(group.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 regex = try! NSRegularExpression(pattern: "<a\\s+(?:[^>]*?\\s+)?href=\"([^\"]*)\".*?>(.*?)<.*?\\/a>")
var bodyAsHTML = "<b>\(title)</b>\(description)"
while true {
guard let match = regex.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) else { return }
parseGroupMessage(body: body.string, timestamp: timestamp, senderDisplayName: NSLocalizedString("Loki", comment: ""))
}
}
} }
} }

@ -692,7 +692,8 @@ typedef NS_ENUM(NSInteger, HomeViewControllerSection) {
} }
if (OWSIdentityManager.sharedManager.identityKeyPair != nil) { if (OWSIdentityManager.sharedManager.identityKeyPair != nil) {
AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate; AppDelegate *appDelegate = (AppDelegate *)UIApplication.sharedApplication.delegate;
[appDelegate startPublicChatPollingIfNeeded]; [appDelegate createGroupChatsIfNeeded];
[appDelegate startGroupChatPollersIfNeeded];
} }
} }

@ -2607,6 +2607,8 @@
"Update Required" = "Update Required"; "Update Required" = "Update Required";
"This version of Loki Messenger is no longer supported. Please press OK to reset your account and migrate to the latest version." = "This version of Loki Messenger is no longer supported. Please press OK to reset your account and migrate to the latest version."; "This version of Loki Messenger is no longer supported. Please press OK to reset your account and migrate to the latest version." = "This version of Loki Messenger is no longer supported. Please press OK to reset your account and migrate to the latest version.";
"Loki Public Chat" = "Loki Public Chat"; "Loki Public Chat" = "Loki Public Chat";
"Loki News" = "Loki News";
"Loki Messenger Updates" = "Loki Messenger Updates";
"Show QR Code" = "Show QR Code"; "Show QR Code" = "Show QR Code";
"This is your personal QR code. Other people can scan it to start a secure conversation with you." = "This is your personal QR code. Other people can scan it to start a secure conversation with you."; "This is your personal QR code. Other people can scan it to start a secure conversation with you." = "This is your personal QR code. Other people can scan it to start a secure conversation with you.";
"Scan a QR Code Instead" = "Scan a QR Code Instead"; "Scan a QR Code Instead" = "Scan a QR Code Instead";
@ -2614,3 +2616,4 @@
"You can enable camera access in your device settings." = "You can enable camera access in your device settings."; "You can enable camera access in your device settings." = "You can enable camera access in your device settings.";
"Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Loki Messenger's in-app settings and clicking \"Show QR Code\"." = "Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Loki Messenger's in-app settings and clicking \"Show QR Code\"."; "Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Loki Messenger's in-app settings and clicking \"Show QR Code\"." = "Scan the QR code of the person you'd like to securely message. They can find their QR code by going into Loki Messenger's in-app settings and clicking \"Show QR Code\".";
"Scan QR Code" = "Scan QR Code"; "Scan QR Code" = "Scan QR Code";
"Loki" = "Loki";

@ -0,0 +1,48 @@
@objc(LKGroupChat)
public final class LokiGroupChat : NSObject {
public let kind: Kind
@objc public let server: String
@objc public let displayName: String
@objc public let isDeletable: Bool
@objc public var id: String {
switch kind {
case .publicChat(let id): return "\(server).\(id)"
case .rss(let customID): return "rss://\(customID)"
}
}
// MARK: Convenience
@objc public var isPublicChat: Bool {
if case .publicChat(_) = kind { return true } else { return false }
}
@objc public var isRSS: Bool {
if case .rss(_) = kind { return true } else { return false }
}
// MARK: Kind
public enum Kind { case publicChat(id: UInt), rss(customID: String) }
// MARK: Initialization
public init(kind: Kind, server: String, displayName: String, isDeletable: Bool) {
self.kind = kind
self.server = server
self.displayName = displayName
self.isDeletable = isDeletable
}
@objc public convenience init(kindAsString: String, id: String, server: String, displayName: String, isDeletable: Bool) {
let kind: Kind
switch kindAsString {
case "publicChat": kind = .publicChat(id: UInt(id)!)
case "rss": kind = .rss(customID: id)
default: preconditionFailure()
}
self.init(kind: kind, server: server, displayName: displayName, isDeletable: isDeletable)
}
// MARK: Description
override public var description: String { return "\(id) (\(displayName))" }
}

@ -169,7 +169,7 @@ public final class LokiGroupChatAPI : NSObject {
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
guard let json = rawResponse as? JSON, let messageAsJSON = json["data"] as? JSON, let serverID = messageAsJSON["id"] as? UInt, let body = messageAsJSON["text"] as? String, guard let json = rawResponse as? JSON, let messageAsJSON = json["data"] as? JSON, let serverID = messageAsJSON["id"] as? UInt, let body = messageAsJSON["text"] as? String,
let dateAsString = messageAsJSON["created_at"] as? String, let date = dateFormatter.date(from: dateAsString) else { let dateAsString = messageAsJSON["created_at"] as? String, let date = dateFormatter.date(from: dateAsString) else {
print("[Loki] Couldn't parse messages for group chat with ID: \(group) on server: \(server) from: \(rawResponse).") print("[Loki] Couldn't parse message for group chat with ID: \(group) on server: \(server) from: \(rawResponse).")
throw Error.messageParsingFailed throw Error.messageParsingFailed
} }
let timestamp = UInt64(date.timeIntervalSince1970) * 1000 let timestamp = UInt64(date.timeIntervalSince1970) * 1000
@ -197,7 +197,7 @@ public final class LokiGroupChatAPI : NSObject {
} }
return rawMessages.flatMap { message in return rawMessages.flatMap { message in
guard let serverID = message["id"] as? UInt else { guard let serverID = message["id"] as? UInt else {
print("[Loki] Couldn't parse message for group chat with ID: \(group) on server: \(server) from: \(message).") print("[Loki] Couldn't parse deleted message for group chat with ID: \(group) on server: \(server) from: \(message).")
return nil return nil
} }
let isDeleted = (message["is_deleted"] as? Bool ?? false) let isDeleted = (message["is_deleted"] as? Bool ?? false)

Loading…
Cancel
Save