From 3930891a3df55e381be7c7b1b39f27ba9e4742a2 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 27 Aug 2019 10:44:00 +1000 Subject: [PATCH 01/16] Added dictionary for adding more servers. Updated group chat poller to take an array of groups. --- Signal/src/AppDelegate.m | 112 ++++++++++++++++++---- Signal/src/Loki/LokiGroupChatPoller.swift | 76 +++++++++------ 2 files changed, 138 insertions(+), 50 deletions(-) diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index fe9d21603..3267d03fd 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -56,6 +56,13 @@ static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewContr static NSString *const kURLSchemeSGNLKey = @"sgnl"; static NSString *const kURLHostVerifyPrefix = @"verify"; +static NSString *const kChatID = @"PublicChatID"; +static NSString *const kChatType = @"PublicChatType"; +static NSString *const kChatServerURL = @"PublicChatServerURL"; +static NSString *const kChatName = @"PublicChatName"; +static NSString *const kChatClosable = @"PublicChatClosable"; +static NSString *const kChatChannelID = @"PublicChatChannelID"; + static NSTimeInterval launchStartedAt; @interface AppDelegate () @@ -1485,28 +1492,93 @@ static NSTimeInterval launchStartedAt; #pragma mark - Loki +- (NSArray *)publicChats +{ + return @[ + @{ + kChatID: @"chat.lokinet.org.1", + kChatType: @"publicChat", + kChatServerURL: LKGroupChatAPI.publicChatServer, + kChatName: NSLocalizedString(@"Loki Public Chat", @""), + kChatClosable: @true, + kChatChannelID: @(LKGroupChatAPI.publicChatID), + }, + @{ + kChatID: @"rss://loki.network/feed/", + kChatType: @"rss", + kChatServerURL: @"https://loki.network/feed/", + kChatName: NSLocalizedString(@"Loki.network News", @""), + kChatClosable: @true, + kChatChannelID: @1, + }, + @{ + kChatID: @"rss://loki.network/category/messenger-updates/feed/", + kChatType: @"rss", + kChatServerURL: @"https://loki.network/category/messenger-updates/feed/", + kChatName: NSLocalizedString(@"Messenger updates", @""), + kChatClosable: @false, + kChatChannelID: @1, + } + ]; +} + +- (void)setupPublicChatGroupsIfNeeded +{ + NSArray *chats = [self publicChats]; + NSString *ourPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; + for (NSDictionary *chat in chats) { + NSString *chatID = [chat objectForKey:kChatID]; + BOOL closable = [[chat objectForKey:kChatClosable] boolValue]; + + NSString *setupKey = [@"setup-" stringByAppendingString:chatID]; + if (closable) { + BOOL isChatSetup = [NSUserDefaults.standardUserDefaults boolForKey:setupKey]; + if (isChatSetup) { continue; } + } + + NSString *title = [chat objectForKey:kChatName]; + NSString *serverURL = [chat objectForKey:kChatServerURL]; + + // Create the group threads + TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:title memberIds:@[ ourPublicKey, serverURL ] image:nil groupId:[chatID dataUsingEncoding:NSUTF8StringEncoding]]; + __block TSGroupThread *thread; + [OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + thread = [TSGroupThread getOrCreateThreadWithGroupModel:group transaction:transaction]; + NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; + NSCalendar *calendar = NSCalendar.currentCalendar; + [calendar setTimeZone:timeZone]; + NSDateComponents *dateComponents = [NSDateComponents new]; + [dateComponents setYear:999]; + NSDate *date = [calendar dateByAddingComponents:dateComponents toDate:[NSDate new] options:0]; + [thread updateWithMutedUntilDate:date transaction:transaction]; + }]; + + [OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread]; + if (closable) { + [NSUserDefaults.standardUserDefaults setBool:YES forKey:setupKey]; + } + } +} + - (void)setUpPublicChatIfNeeded { - if (self.lokiPublicChatPoller != nil) { return; } - self.lokiPublicChatPoller = [[LKGroupChatPoller alloc] initForGroup:(NSUInteger)LKGroupChatAPI.publicChatID onServer:LKGroupChatAPI.publicChatServer]; - BOOL isPublicChatSetUp = [NSUserDefaults.standardUserDefaults boolForKey:@"isPublicChatSetUp"]; - if (isPublicChatSetUp) { return; } - NSString *title = NSLocalizedString(@"Loki Public Chat", @""); - NSData *groupID = [[[LKGroupChatAPI.publicChatServer stringByAppendingString:@"."] stringByAppendingString:@(LKGroupChatAPI.publicChatID).stringValue] dataUsingEncoding:NSUTF8StringEncoding]; - TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:title memberIds:@[ OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey, LKGroupChatAPI.publicChatServer ] image:nil groupId:groupID]; - __block TSGroupThread *thread; - [OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - thread = [TSGroupThread getOrCreateThreadWithGroupModel:group transaction:transaction]; - NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; - NSCalendar *calendar = NSCalendar.currentCalendar; - [calendar setTimeZone:timeZone]; - NSDateComponents *dateComponents = [NSDateComponents new]; - [dateComponents setYear:999]; - NSDate *date = [calendar dateByAddingComponents:dateComponents toDate:[NSDate new] options:0]; - [thread updateWithMutedUntilDate:date transaction:transaction]; - }]; - [OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread]; - [NSUserDefaults.standardUserDefaults setBool:YES forKey:@"isPublicChatSetUp"]; + static dispatch_once_t groupChatSetup; + dispatch_once(&groupChatSetup, ^{ + [self setupPublicChatGroupsIfNeeded]; + }); + [self setupPublicChatPollersIfNeeded]; +} + +- (void)setupPublicChatPollersIfNeeded +{ + if (self.lokiPublicChatPoller == nil) { + NSArray *publicChats = [[self publicChats] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary* bindings) { + NSDictionary *group = (NSDictionary *)object; + NSString *chatType = [group objectForKey:kChatType]; + return [chatType isEqualToString:@"publicChat"]; + }]]; + self.lokiPublicChatPoller = [[LKGroupChatPoller alloc] initWithGroups:publicChats]; + } } - (void)startPublicChatPollingIfNeeded diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index 4acaf58e8..b8918da0b 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -1,8 +1,12 @@ +private let kChatID = "PublicChatID" +private let kChatChannelID = "PublicChatChannelID" +private let kChatName = "PublicChatName" +private let kServerURL = "PublicChatServerURL" + @objc(LKGroupChatPoller) public final class LokiGroupChatPoller : NSObject { - private let group: UInt - private let server: String + private let groups: [[String: Any]] private var pollForNewMessagesTimer: Timer? = nil private var pollForDeletedMessagesTimer: Timer? = nil private var hasStarted = false @@ -10,10 +14,8 @@ public final class LokiGroupChatPoller : NSObject { private let pollForNewMessagesInterval: TimeInterval = 4 private let pollForDeletedMessagesInterval: TimeInterval = 120 - @objc(initForGroup:onServer:) - public init(for group: UInt, on server: String) { - self.group = group - self.server = server + @objc public init(groups: [[String: Any]]) { + self.groups = groups super.init() } @@ -31,30 +33,44 @@ public final class LokiGroupChatPoller : NSObject { } private func pollForNewMessages() { - let group = self.group - let server = self.server - let _ = LokiGroupChatAPI.getMessages(for: group, on: server).map { messages in - messages.reversed().map { message in - let id = "\(server).\(group)".data(using: String.Encoding.utf8)! - let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver) - x1.setName(NSLocalizedString("Loki Public Chat", comment: "")) - let x2 = SSKProtoDataMessage.builder() - x2.setTimestamp(message.timestamp) - x2.setGroup(try! x1.build()) - x2.setBody(message.body) - let x3 = SSKProtoContent.builder() - x3.setDataMessage(try! x2.build()) - let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp) - let senderHexEncodedPublicKey = message.hexEncodedPublicKey - let endIndex = senderHexEncodedPublicKey.endIndex - let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8) - let senderDisplayName = "\(message.displayName) (...\(senderHexEncodedPublicKey[cutoffIndex.. Void { + guard let groupID = group[kChatID] as? String, let groupName = group[kChatName] as? String else { + Logger.info("[Loki] Failed to handle messages for group: \(group))") + return + } + + messages.reversed().forEach { message in + let id = groupID.data(using: String.Encoding.utf8)! + let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver) + x1.setName(groupName) + let x2 = SSKProtoDataMessage.builder() + x2.setTimestamp(message.timestamp) + x2.setGroup(try! x1.build()) + x2.setBody(message.body) + let x3 = SSKProtoContent.builder() + x3.setDataMessage(try! x2.build()) + let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp) + let senderHexEncodedPublicKey = message.hexEncodedPublicKey + let endIndex = senderHexEncodedPublicKey.endIndex + let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8) + let senderDisplayName = "\(message.displayName) (...\(senderHexEncodedPublicKey[cutoffIndex.. Date: Tue, 27 Aug 2019 11:58:12 +1000 Subject: [PATCH 02/16] Add LokiGroupChat --- .../src/Loki/API/LokiGroupChat.swift | 38 +++++++++++++++++++ .../src/Loki/API/LokiGroupChatAPI.swift | 4 +- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 SignalServiceKit/src/Loki/API/LokiGroupChat.swift diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChat.swift b/SignalServiceKit/src/Loki/API/LokiGroupChat.swift new file mode 100644 index 000000000..43912ae00 --- /dev/null +++ b/SignalServiceKit/src/Loki/API/LokiGroupChat.swift @@ -0,0 +1,38 @@ + +@objc(LKGroupChat) +public final class LokiGroupChat : NSObject { + public let kind: Kind + public let server: String + public let displayName: String + public let isDeletable: Bool + + public var id: String { + switch kind { + case .publicChat(let id): return "\(server).\(id)" + case .rss(let customID): return customID + } + } + + 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))" } +} diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift index 9e430926d..205d58c5a 100644 --- a/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift +++ b/SignalServiceKit/src/Loki/API/LokiGroupChatAPI.swift @@ -169,7 +169,7 @@ public final class LokiGroupChatAPI : NSObject { 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, 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 } let timestamp = UInt64(date.timeIntervalSince1970) * 1000 @@ -197,7 +197,7 @@ public final class LokiGroupChatAPI : NSObject { } return rawMessages.flatMap { message in 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 } let isDeleted = (message["is_deleted"] as? Bool ?? false) From ccb5ce21fed2cc405b50d147b1a39df678241ba2 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 11:58:48 +1000 Subject: [PATCH 03/16] Update Pods --- Pods | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pods b/Pods index d9ab8b130..276b10db4 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit d9ab8b13002bf6ebc932ca4f45df56b577b6a188 +Subproject commit 276b10db459e84a7352e1de87e1a5a9677f633c6 From 99656b5961b1b17bbebfb05ff214ed329c420e3d Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 12:22:32 +1000 Subject: [PATCH 04/16] Lower deletion polling interval --- Signal/src/Loki/LokiGroupChatPoller.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index b8918da0b..9e15676eb 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -12,7 +12,7 @@ public final class LokiGroupChatPoller : NSObject { private var hasStarted = false private let pollForNewMessagesInterval: TimeInterval = 4 - private let pollForDeletedMessagesInterval: TimeInterval = 120 + private let pollForDeletedMessagesInterval: TimeInterval = 32 * 60 @objc public init(groups: [[String: Any]]) { self.groups = groups From 4c21cecc66029f5d5e78710a73e26a07d02e4074 Mon Sep 17 00:00:00 2001 From: Mikunj Date: Tue, 27 Aug 2019 12:26:02 +1000 Subject: [PATCH 05/16] Migrate from Dictionary to LokiGroupChat. --- Pods | 2 +- Signal/src/AppDelegate.m | 50 ++++--------------- Signal/src/Loki/LokiGroupChatPoller.swift | 26 +++------- .../src/Loki/API/LokiGroupChat.swift | 24 +++++++-- 4 files changed, 39 insertions(+), 63 deletions(-) diff --git a/Pods b/Pods index 276b10db4..98077061d 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 276b10db459e84a7352e1de87e1a5a9677f633c6 +Subproject commit 98077061d67643bda2dc906dd37d571eccbcd164 diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 3267d03fd..646c8edea 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -1495,52 +1495,25 @@ static NSTimeInterval launchStartedAt; - (NSArray *)publicChats { return @[ - @{ - kChatID: @"chat.lokinet.org.1", - kChatType: @"publicChat", - kChatServerURL: LKGroupChatAPI.publicChatServer, - kChatName: NSLocalizedString(@"Loki Public Chat", @""), - kChatClosable: @true, - kChatChannelID: @(LKGroupChatAPI.publicChatID), - }, - @{ - kChatID: @"rss://loki.network/feed/", - kChatType: @"rss", - kChatServerURL: @"https://loki.network/feed/", - kChatName: NSLocalizedString(@"Loki.network News", @""), - kChatClosable: @true, - kChatChannelID: @1, - }, - @{ - kChatID: @"rss://loki.network/category/messenger-updates/feed/", - kChatType: @"rss", - kChatServerURL: @"https://loki.network/category/messenger-updates/feed/", - kChatName: NSLocalizedString(@"Messenger updates", @""), - kChatClosable: @false, - kChatChannelID: @1, - } - ]; + [[LKGroupChat alloc] initWithKindAsString:@"publicChat" id:@(LKGroupChatAPI.publicChatID).stringValue server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true], + [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.feed" server:@"https://loki.network/feed/" displayName:NSLocalizedString(@"Loki.network News", @"") isDeletable:true], + [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.messenger-update" server:@"https://loki.network/category/messenger-updates/feed/" displayName:NSLocalizedString(@"Messenger updates", @"") isDeletable:false], + ]; } - (void)setupPublicChatGroupsIfNeeded { NSArray *chats = [self publicChats]; NSString *ourPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - for (NSDictionary *chat in chats) { - NSString *chatID = [chat objectForKey:kChatID]; - BOOL closable = [[chat objectForKey:kChatClosable] boolValue]; - - NSString *setupKey = [@"setup-" stringByAppendingString:chatID]; - if (closable) { + for (LKGroupChat *chat in chats) { + NSString *setupKey = [@"setup-" stringByAppendingString:chat.id]; + if (chat.isDeletable) { BOOL isChatSetup = [NSUserDefaults.standardUserDefaults boolForKey:setupKey]; if (isChatSetup) { continue; } } - NSString *title = [chat objectForKey:kChatName]; - NSString *serverURL = [chat objectForKey:kChatServerURL]; - // Create the group threads - TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:title memberIds:@[ ourPublicKey, serverURL ] image:nil groupId:[chatID dataUsingEncoding:NSUTF8StringEncoding]]; + TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:chat.displayName memberIds:@[ ourPublicKey, chat.server ] image:nil groupId:[chat.id dataUsingEncoding:NSUTF8StringEncoding]]; __block TSGroupThread *thread; [OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { thread = [TSGroupThread getOrCreateThreadWithGroupModel:group transaction:transaction]; @@ -1554,7 +1527,7 @@ static NSTimeInterval launchStartedAt; }]; [OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread]; - if (closable) { + if (chat.isDeletable) { [NSUserDefaults.standardUserDefaults setBool:YES forKey:setupKey]; } } @@ -1573,9 +1546,8 @@ static NSTimeInterval launchStartedAt; { if (self.lokiPublicChatPoller == nil) { NSArray *publicChats = [[self publicChats] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary* bindings) { - NSDictionary *group = (NSDictionary *)object; - NSString *chatType = [group objectForKey:kChatType]; - return [chatType isEqualToString:@"publicChat"]; + LKGroupChat *group = (LKGroupChat *)object; + return group.isPublicChat; }]]; self.lokiPublicChatPoller = [[LKGroupChatPoller alloc] initWithGroups:publicChats]; } diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index 9e15676eb..a423d8254 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -1,12 +1,7 @@ -private let kChatID = "PublicChatID" -private let kChatChannelID = "PublicChatChannelID" -private let kChatName = "PublicChatName" -private let kServerURL = "PublicChatServerURL" - @objc(LKGroupChatPoller) public final class LokiGroupChatPoller : NSObject { - private let groups: [[String: Any]] + private let groups: [LokiGroupChat] private var pollForNewMessagesTimer: Timer? = nil private var pollForDeletedMessagesTimer: Timer? = nil private var hasStarted = false @@ -14,7 +9,7 @@ public final class LokiGroupChatPoller : NSObject { private let pollForNewMessagesInterval: TimeInterval = 4 private let pollForDeletedMessagesInterval: TimeInterval = 32 * 60 - @objc public init(groups: [[String: Any]]) { + @objc public init(groups: [LokiGroupChat]) { self.groups = groups super.init() } @@ -34,27 +29,22 @@ public final class LokiGroupChatPoller : NSObject { private func pollForNewMessages() { for group in groups { - guard let channelID = group[kChatChannelID] as? UInt, let server = group[kServerURL] as? String else { - Logger.info("[Loki] Failed to get channel id or server url from group: \(group)") + guard case let LokiGroupChat.Kind.publicChat(channelID) = group.kind else { + Logger.info("[Loki] Trying to poll RSS group chat: \(group)") return } - LokiGroupChatAPI.getMessages(for: channelID, on: server).map { [weak self] messages in + LokiGroupChatAPI.getMessages(for: channelID, on: group.server).map { [weak self] messages in self?.handleMessages(messages: messages, group: group) } } } - private func handleMessages(messages: [LokiGroupMessage], group: [String: Any]) -> Void { - guard let groupID = group[kChatID] as? String, let groupName = group[kChatName] as? String else { - Logger.info("[Loki] Failed to handle messages for group: \(group))") - return - } - + private func handleMessages(messages: [LokiGroupMessage], group: LokiGroupChat) -> Void { messages.reversed().forEach { message in - let id = groupID.data(using: String.Encoding.utf8)! + let id = group.id.data(using: String.Encoding.utf8)! let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver) - x1.setName(groupName) + x1.setName(group.displayName) let x2 = SSKProtoDataMessage.builder() x2.setTimestamp(message.timestamp) x2.setGroup(try! x1.build()) diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChat.swift b/SignalServiceKit/src/Loki/API/LokiGroupChat.swift index 43912ae00..d9090a585 100644 --- a/SignalServiceKit/src/Loki/API/LokiGroupChat.swift +++ b/SignalServiceKit/src/Loki/API/LokiGroupChat.swift @@ -2,14 +2,28 @@ @objc(LKGroupChat) public final class LokiGroupChat : NSObject { public let kind: Kind - public let server: String - public let displayName: String - public let isDeletable: Bool + @objc public let server: String + @objc public let displayName: String + @objc public let isDeletable: Bool - public var id: String { + @objc public var id: String { switch kind { case .publicChat(let id): return "\(server).\(id)" - case .rss(let customID): return customID + case .rss(let customID): return "rss://\(customID)" + } + } + + @objc public var isRSS: Bool { + switch kind { + case .publicChat(let id): return false + case .rss(let customID): return true + } + } + + @objc public var isPublicChat: Bool { + switch kind { + case .publicChat(let id): return true + case .rss(let customID): return false } } From 6056575cdfa684c4de13f6f33971f38a2b061674 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 12:35:31 +1000 Subject: [PATCH 06/16] Clean --- Signal/src/AppDelegate.m | 8 ++++---- Signal/translations/en.lproj/Localizable.strings | 2 ++ SignalServiceKit/src/Loki/API/LokiGroupChat.swift | 15 +++++---------- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 646c8edea..2abce65df 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -1495,10 +1495,10 @@ static NSTimeInterval launchStartedAt; - (NSArray *)publicChats { return @[ - [[LKGroupChat alloc] initWithKindAsString:@"publicChat" id:@(LKGroupChatAPI.publicChatID).stringValue server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true], - [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.feed" server:@"https://loki.network/feed/" displayName:NSLocalizedString(@"Loki.network News", @"") isDeletable:true], - [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.messenger-update" server:@"https://loki.network/category/messenger-updates/feed/" displayName:NSLocalizedString(@"Messenger updates", @"") isDeletable:false], - ]; + [[LKGroupChat alloc] initWithKindAsString:@"publicChat" id:@(LKGroupChatAPI.publicChatID).stringValue server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true], + [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.feed" server:@"https://loki.network/feed/" displayName:NSLocalizedString(@"Loki News", @"") isDeletable:true], + [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.messenger-update" server:@"https://loki.network/category/messenger-updates/feed/" displayName:NSLocalizedString(@"Messenger Updates", @"") isDeletable:false], + ]; } - (void)setupPublicChatGroupsIfNeeded diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 0e17a7b9e..d5a90fc64 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2607,6 +2607,8 @@ "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."; "Loki Public Chat" = "Loki Public Chat"; +"Loki News" = "Loki News"; +"Messenger Updates" = "Messenger Updates"; "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."; "Scan a QR Code Instead" = "Scan a QR Code Instead"; diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChat.swift b/SignalServiceKit/src/Loki/API/LokiGroupChat.swift index d9090a585..be6e36213 100644 --- a/SignalServiceKit/src/Loki/API/LokiGroupChat.swift +++ b/SignalServiceKit/src/Loki/API/LokiGroupChat.swift @@ -13,20 +13,15 @@ public final class LokiGroupChat : NSObject { } } - @objc public var isRSS: Bool { - switch kind { - case .publicChat(let id): return false - case .rss(let customID): return true - } + @objc public var isPublicChat: Bool { + if case .publicChat(_) = kind { return true } else { return false } } - @objc public var isPublicChat: Bool { - switch kind { - case .publicChat(let id): return true - case .rss(let customID): 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 From 06dba7ddd92715573143a40c36c3653fe8e8ba15 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 12:45:01 +1000 Subject: [PATCH 07/16] WIP --- Signal/src/AppDelegate.m | 43 +++++++++++----------------------------- 1 file changed, 12 insertions(+), 31 deletions(-) diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 2abce65df..b4241f27e 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -56,13 +56,6 @@ static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewContr static NSString *const kURLSchemeSGNLKey = @"sgnl"; static NSString *const kURLHostVerifyPrefix = @"verify"; -static NSString *const kChatID = @"PublicChatID"; -static NSString *const kChatType = @"PublicChatType"; -static NSString *const kChatServerURL = @"PublicChatServerURL"; -static NSString *const kChatName = @"PublicChatName"; -static NSString *const kChatClosable = @"PublicChatClosable"; -static NSString *const kChatChannelID = @"PublicChatChannelID"; - static NSTimeInterval launchStartedAt; @interface AppDelegate () @@ -1492,7 +1485,7 @@ static NSTimeInterval launchStartedAt; #pragma mark - Loki -- (NSArray *)publicChats +- (NSArray *)groupChats { return @[ [[LKGroupChat alloc] initWithKindAsString:@"publicChat" id:@(LKGroupChatAPI.publicChatID).stringValue server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true], @@ -1501,18 +1494,16 @@ static NSTimeInterval launchStartedAt; ]; } -- (void)setupPublicChatGroupsIfNeeded +- (void)setUpGroupChatsIfNeeded { - NSArray *chats = [self publicChats]; - NSString *ourPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - for (LKGroupChat *chat in chats) { - NSString *setupKey = [@"setup-" stringByAppendingString:chat.id]; + NSArray *groupChats = self.groupChats; + NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; + for (LKGroupChat *chat in groupChats) { + NSString *userDefaultsKey = [@"isGroupChatSetUp." stringByAppendingString:chat.id]; if (chat.isDeletable) { - BOOL isChatSetup = [NSUserDefaults.standardUserDefaults boolForKey:setupKey]; - if (isChatSetup) { continue; } + BOOL isChatSetUp = [NSUserDefaults.standardUserDefaults boolForKey:setupKey]; + if (isChatSetUp) { continue; } } - - // Create the group threads TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:chat.displayName memberIds:@[ ourPublicKey, chat.server ] image:nil groupId:[chat.id dataUsingEncoding:NSUTF8StringEncoding]]; __block TSGroupThread *thread; [OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { @@ -1525,27 +1516,17 @@ static NSTimeInterval launchStartedAt; NSDate *date = [calendar dateByAddingComponents:dateComponents toDate:[NSDate new] options:0]; [thread updateWithMutedUntilDate:date transaction:transaction]; }]; - [OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread]; if (chat.isDeletable) { - [NSUserDefaults.standardUserDefaults setBool:YES forKey:setupKey]; + [NSUserDefaults.standardUserDefaults setBool:YES forKey:userDefaultsKey]; } } } -- (void)setUpPublicChatIfNeeded -{ - static dispatch_once_t groupChatSetup; - dispatch_once(&groupChatSetup, ^{ - [self setupPublicChatGroupsIfNeeded]; - }); - [self setupPublicChatPollersIfNeeded]; -} - -- (void)setupPublicChatPollersIfNeeded +- (void)setUpGroupChatPollerIfNeeded { - if (self.lokiPublicChatPoller == nil) { - NSArray *publicChats = [[self publicChats] filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary* bindings) { + if (self.lokiGroupChatPoller == nil) { + NSArray *groupChats = [self.groupChats filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary* bindings) { LKGroupChat *group = (LKGroupChat *)object; return group.isPublicChat; }]]; From ba0da149ad0d61195ed6ac0c7d082a08d1a1e9cc Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 13:42:57 +1000 Subject: [PATCH 08/16] Refactor --- Signal/src/AppDelegate.h | 3 +- Signal/src/AppDelegate.m | 56 +++++++------- Signal/src/Loki/LokiGroupChatPoller.swift | 75 ++++++++++--------- .../HomeView/HomeViewController.m | 3 +- .../translations/en.lproj/Localizable.strings | 2 +- 5 files changed, 71 insertions(+), 68 deletions(-) diff --git a/Signal/src/AppDelegate.h b/Signal/src/AppDelegate.h index c14a3e70b..a0c86290a 100644 --- a/Signal/src/AppDelegate.h +++ b/Signal/src/AppDelegate.h @@ -8,6 +8,7 @@ extern NSString *const AppDelegateStoryboardMain; @interface AppDelegate : UIResponder -- (void)startPublicChatPollingIfNeeded; +- (void)createGroupChatsIfNeeded; +- (void)startGroupChatPollersIfNeeded; @end diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index b4241f27e..f15c7dd71 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -65,6 +65,8 @@ static NSTimeInterval launchStartedAt; @property (nonatomic) BOOL didAppLaunchFail; @property (nonatomic) LKP2PServer *lokiP2PServer; @property (nonatomic) LKGroupChatPoller *lokiPublicChatPoller; +@property (nonatomic) LKGroupChatPoller *lokiNewsPoller; +@property (nonatomic) LKGroupChatPoller *lokiMessengerUpdatesPoller; @end @@ -1485,26 +1487,30 @@ static NSTimeInterval launchStartedAt; #pragma mark - Loki -- (NSArray *)groupChats +- (LKGroupChat *)lokiPublicChat { - return @[ - [[LKGroupChat alloc] initWithKindAsString:@"publicChat" id:@(LKGroupChatAPI.publicChatID).stringValue server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true], - [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.feed" server:@"https://loki.network/feed/" displayName:NSLocalizedString(@"Loki News", @"") isDeletable:true], - [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.messenger-update" server:@"https://loki.network/category/messenger-updates/feed/" displayName:NSLocalizedString(@"Messenger Updates", @"") isDeletable:false], - ]; + return [[LKGroupChat alloc] initWithKindAsString:@"publicChat" id:@(LKGroupChatAPI.publicChatID).stringValue server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true]; +} + +- (LKGroupChat *)lokiNews +{ + return [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.feed" server:@"https://loki.network/feed/" displayName:NSLocalizedString(@"Loki News", @"") isDeletable:true]; +} + +- (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)setUpGroupChatsIfNeeded +- (void)createGroupChatsIfNeeded { - NSArray *groupChats = self.groupChats; + NSArray *allGroupChats = @[ self.lokiPublicChat, self.lokiNews, self.lokiMessengerUpdates ]; NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; - for (LKGroupChat *chat in groupChats) { - NSString *userDefaultsKey = [@"isGroupChatSetUp." stringByAppendingString:chat.id]; - if (chat.isDeletable) { - BOOL isChatSetUp = [NSUserDefaults.standardUserDefaults boolForKey:setupKey]; - if (isChatSetUp) { continue; } - } - TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:chat.displayName memberIds:@[ ourPublicKey, chat.server ] image:nil groupId:[chat.id dataUsingEncoding:NSUTF8StringEncoding]]; + for (LKGroupChat *chat in allGroupChats) { + NSString *userDefaultsKey = [@"isSetUp." stringByAppendingString:chat.id]; + BOOL isChatSetUp = [NSUserDefaults.standardUserDefaults boolForKey:userDefaultsKey]; + if (chat.isDeletable && isChatSetUp) { continue; } + TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:chat.displayName memberIds:@[ userHexEncodedPublicKey, chat.server ] image:nil groupId:[chat.id dataUsingEncoding:NSUTF8StringEncoding]]; __block TSGroupThread *thread; [OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { thread = [TSGroupThread getOrCreateThreadWithGroupModel:group transaction:transaction]; @@ -1517,26 +1523,20 @@ static NSTimeInterval launchStartedAt; [thread updateWithMutedUntilDate:date transaction:transaction]; }]; [OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread]; - if (chat.isDeletable) { - [NSUserDefaults.standardUserDefaults setBool:YES forKey:userDefaultsKey]; - } + [NSUserDefaults.standardUserDefaults setBool:YES forKey:userDefaultsKey]; } } -- (void)setUpGroupChatPollerIfNeeded +- (void)createGroupChatPollersIfNeeded { - if (self.lokiGroupChatPoller == nil) { - NSArray *groupChats = [self.groupChats filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id object, NSDictionary* bindings) { - LKGroupChat *group = (LKGroupChat *)object; - return group.isPublicChat; - }]]; - self.lokiPublicChatPoller = [[LKGroupChatPoller alloc] initWithGroups:publicChats]; - } + 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]; } diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index a423d8254..45401519f 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -1,16 +1,23 @@ @objc(LKGroupChatPoller) public final class LokiGroupChatPoller : NSObject { - private let groups: [LokiGroupChat] + private let group: LokiGroupChat private var pollForNewMessagesTimer: Timer? = nil private var pollForDeletedMessagesTimer: Timer? = nil private var hasStarted = false - private let pollForNewMessagesInterval: TimeInterval = 4 + private lazy var pollForNewMessagesInterval: TimeInterval = { + switch group.kind { + case .publicChat(_): return 4 + case .rss(_): return 8 * 60 + } + }() + private let pollForDeletedMessagesInterval: TimeInterval = 32 * 60 - @objc public init(groups: [LokiGroupChat]) { - self.groups = groups + @objc(initForGroup:) + public init(for group: LokiGroupChat) { + self.group = group super.init() } @@ -28,40 +35,34 @@ public final class LokiGroupChatPoller : NSObject { } private func pollForNewMessages() { - for group in groups { - guard case let LokiGroupChat.Kind.publicChat(channelID) = group.kind else { - Logger.info("[Loki] Trying to poll RSS group chat: \(group)") - return - } - - LokiGroupChatAPI.getMessages(for: channelID, on: group.server).map { [weak self] messages in - self?.handleMessages(messages: messages, group: group) - } - } - } - - private func handleMessages(messages: [LokiGroupMessage], group: LokiGroupChat) -> Void { - messages.reversed().forEach { message in - let id = group.id.data(using: String.Encoding.utf8)! - let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver) - x1.setName(group.displayName) - let x2 = SSKProtoDataMessage.builder() - x2.setTimestamp(message.timestamp) - x2.setGroup(try! x1.build()) - x2.setBody(message.body) - let x3 = SSKProtoContent.builder() - x3.setDataMessage(try! x2.build()) - let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp) - let senderHexEncodedPublicKey = message.hexEncodedPublicKey - let endIndex = senderHexEncodedPublicKey.endIndex - let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8) - let senderDisplayName = "\(message.displayName) (...\(senderHexEncodedPublicKey[cutoffIndex.. Date: Tue, 27 Aug 2019 13:50:31 +1000 Subject: [PATCH 09/16] Disable deletion polling for RSS feeds --- Signal/src/Loki/LokiGroupChatPoller.swift | 11 +++++++++-- SignalServiceKit/src/Loki/API/LokiGroupChat.swift | 1 + 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index 45401519f..9609f00c3 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -13,7 +13,12 @@ public final class LokiGroupChatPoller : NSObject { } }() - private let pollForDeletedMessagesInterval: TimeInterval = 32 * 60 + private lazy var pollForDeletedMessagesInterval: TimeInterval = { + switch group.kind { + case .publicChat(_): return 32 * 60 + case .rss(_): preconditionFailure() + } + }() @objc(initForGroup:) public init(for group: LokiGroupChat) { @@ -24,7 +29,9 @@ public final class LokiGroupChatPoller : NSObject { @objc public func startIfNeeded() { if hasStarted { return } pollForNewMessagesTimer = Timer.scheduledTimer(withTimeInterval: pollForNewMessagesInterval, repeats: true) { [weak self] _ in self?.pollForNewMessages() } - pollForDeletedMessagesTimer = Timer.scheduledTimer(withTimeInterval: pollForDeletedMessagesInterval, repeats: true) { [weak self] _ in self?.pollForDeletedMessages() } + if group.isPublicChat { + pollForDeletedMessagesTimer = Timer.scheduledTimer(withTimeInterval: pollForDeletedMessagesInterval, repeats: true) { [weak self] _ in self?.pollForDeletedMessages() } + } hasStarted = true } diff --git a/SignalServiceKit/src/Loki/API/LokiGroupChat.swift b/SignalServiceKit/src/Loki/API/LokiGroupChat.swift index be6e36213..07bb21d27 100644 --- a/SignalServiceKit/src/Loki/API/LokiGroupChat.swift +++ b/SignalServiceKit/src/Loki/API/LokiGroupChat.swift @@ -13,6 +13,7 @@ public final class LokiGroupChat : NSObject { } } + // MARK: Convenience @objc public var isPublicChat: Bool { if case .publicChat(_) = kind { return true } else { return false } } From 738d4f05f5a1074742ca137edfcaf4c32838f310 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 14:27:10 +1000 Subject: [PATCH 10/16] Create LokiRSSFeedParser --- .../src/Loki/API/LokiRSSFeedParser.swift | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 SignalServiceKit/src/Loki/API/LokiRSSFeedParser.swift diff --git a/SignalServiceKit/src/Loki/API/LokiRSSFeedParser.swift b/SignalServiceKit/src/Loki/API/LokiRSSFeedParser.swift new file mode 100644 index 000000000..2f063325b --- /dev/null +++ b/SignalServiceKit/src/Loki/API/LokiRSSFeedParser.swift @@ -0,0 +1,54 @@ + +public final class LokiRSSFeedParser : NSObject, XMLParserDelegate { + private let url: URL + private var completion: (([Item]) -> Void)? + private var tag: Tag? + private var currentItem: Item? + private var items: [Item] = [] + + private enum Tag : String { + case item, title, description, date = "pubDate" + } + + public struct Item { + public var title: String? = nil + public var description: String? = nil + public var dateAsString: String? = nil + } + + public init(url: URL) { + self.url = url + super.init() + } + + public func parse(completion: @escaping (([Item]) -> Void)) { + guard let parser = XMLParser(contentsOf: url) else { return } + self.completion = completion + parser.delegate = self + parser.parse() + } + + public func parser(_ parser: XMLParser, didStartElement elementAsString: String, namespaceURI: String?, qualifiedName: String?, attributes: [String:String] = [:]) { + if let element = Tag(rawValue: elementAsString) { self.tag = element } + if tag == .item { currentItem = Item() } + } + + public func parser(_ parser: XMLParser, foundCharacters string: String) { + guard let element = tag else { return } + switch element { + case .title: currentItem?.title = string + case .description: currentItem?.description = string + case .date: currentItem?.dateAsString = string + default: break + } + } + + public func parser(_ parser: XMLParser, didEndElement elementAsString: String, namespaceURI: String?, qualifiedName: String?) { + guard let element = Tag(rawValue: elementAsString) else { return } + if element == .item, let currentItem = self.currentItem { items.append(currentItem) } + } + + public func parserDidEndDocument(_ parser: XMLParser) { + completion?(items) + } +} From a943be6c973a1b641b52dd1e21600adfc08b1f0d Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 14:56:33 +1000 Subject: [PATCH 11/16] WIP --- Podfile | 1 + Pods | 2 +- Signal/src/AppDelegate.m | 33 ++++++++++++----------- Signal/src/Loki/LokiGroupChatPoller.swift | 12 +++++++-- 4 files changed, 30 insertions(+), 18 deletions(-) diff --git a/Podfile b/Podfile index 7b1bd834f..bf1a97832 100644 --- a/Podfile +++ b/Podfile @@ -73,6 +73,7 @@ target 'Signal' do # Loki pod 'GCDWebServer', '~> 3.0' + pod 'FeedKit', '~> 8.1' target 'SignalTests' do inherit! :search_paths diff --git a/Pods b/Pods index 98077061d..95f519e4b 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 98077061d67643bda2dc906dd37d571eccbcd164 +Subproject commit 95f519e4b407713a98f98d0aec572b7487c941b3 diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index f15c7dd71..b1ca8310c 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -1509,21 +1509,22 @@ static NSTimeInterval launchStartedAt; for (LKGroupChat *chat in allGroupChats) { NSString *userDefaultsKey = [@"isSetUp." stringByAppendingString:chat.id]; BOOL isChatSetUp = [NSUserDefaults.standardUserDefaults boolForKey:userDefaultsKey]; - if (chat.isDeletable && isChatSetUp) { continue; } - TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:chat.displayName memberIds:@[ userHexEncodedPublicKey, chat.server ] image:nil groupId:[chat.id dataUsingEncoding:NSUTF8StringEncoding]]; - __block TSGroupThread *thread; - [OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { - thread = [TSGroupThread getOrCreateThreadWithGroupModel:group transaction:transaction]; - NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; - NSCalendar *calendar = NSCalendar.currentCalendar; - [calendar setTimeZone:timeZone]; - NSDateComponents *dateComponents = [NSDateComponents new]; - [dateComponents setYear:999]; - NSDate *date = [calendar dateByAddingComponents:dateComponents toDate:[NSDate new] options:0]; - [thread updateWithMutedUntilDate:date transaction:transaction]; - }]; - [OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread]; - [NSUserDefaults.standardUserDefaults setBool:YES forKey: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; + [OWSPrimaryStorage.dbReadWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) { + thread = [TSGroupThread getOrCreateThreadWithGroupModel:group transaction:transaction]; + NSTimeZone *timeZone = [NSTimeZone timeZoneWithName:@"UTC"]; + NSCalendar *calendar = NSCalendar.currentCalendar; + [calendar setTimeZone:timeZone]; + NSDateComponents *dateComponents = [NSDateComponents new]; + [dateComponents setYear:999]; + NSDate *date = [calendar dateByAddingComponents:dateComponents toDate:[NSDate new] options:0]; + [thread updateWithMutedUntilDate:date transaction:transaction]; + }]; + [OWSProfileManager.sharedManager addThreadToProfileWhitelist:thread]; + [NSUserDefaults.standardUserDefaults setBool:YES forKey:userDefaultsKey]; + } } } @@ -1538,6 +1539,8 @@ static NSTimeInterval launchStartedAt; { [self createGroupChatPollersIfNeeded]; [self.lokiPublicChatPoller startIfNeeded]; + [self.lokiNewsPoller startIfNeeded]; + [self.lokiMessengerUpdatesPoller startIfNeeded]; } @end diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index 9609f00c3..130b9bf58 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -9,7 +9,7 @@ public final class LokiGroupChatPoller : NSObject { private lazy var pollForNewMessagesInterval: TimeInterval = { switch group.kind { case .publicChat(_): return 4 - case .rss(_): return 8 * 60 + case .rss(_): return 4//8 * 60 } }() @@ -29,6 +29,7 @@ public final class LokiGroupChatPoller : NSObject { @objc public func startIfNeeded() { if hasStarted { return } 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() } } @@ -69,7 +70,14 @@ public final class LokiGroupChatPoller : NSObject { } } } - case .rss(let customID): break // TODO: Implement + case .rss(_): + let url = URL(string: group.server)! + let parser = LokiRSSFeedParser(url: url) + parser.parse { items in + items.reversed().forEach { item in + print("Loki", "\(item.title ?? "nil"), \(item.description ?? "nil"), \(item.dateAsString ?? "nil")") + } + } } } From 7ecfedbf9af7f38dd41d6feded0d1b30f377899c Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 15:14:24 +1000 Subject: [PATCH 12/16] Implement RSS feed parsing --- Podfile.lock | 10 +++++-- Signal.xcodeproj/project.pbxproj | 10 ++++--- Signal/src/Loki/LokiGroupChatPoller.swift | 28 ++++++++++++++++--- .../translations/en.lproj/Localizable.strings | 1 + 4 files changed, 38 insertions(+), 11 deletions(-) diff --git a/Podfile.lock b/Podfile.lock index 79a4f123f..5b624389a 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -36,6 +36,7 @@ PODS: - Curve25519Kit/Tests (2.1.0): - CocoaLumberjack - SignalCoreKit + - FeedKit (8.1.1) - GCDWebServer (3.5.2): - GCDWebServer/Core (= 3.5.2) - GCDWebServer/Core (3.5.2) @@ -198,6 +199,7 @@ DEPENDENCIES: - CryptoSwift - Curve25519Kit (from `https://github.com/signalapp/Curve25519Kit`) - Curve25519Kit/Tests (from `https://github.com/signalapp/Curve25519Kit`) + - FeedKit (~> 8.1) - GCDWebServer (~> 3.0) - GRKOpenSSLFramework (from `https://github.com/signalapp/GRKOpenSSLFramework`) - HKDFKit (from `https://github.com/signalapp/HKDFKit.git`) @@ -224,6 +226,7 @@ SPEC REPOS: - AFNetworking - CocoaLumberjack - CryptoSwift + - FeedKit - GCDWebServer - IGIdenticon - libPhoneNumber-iOS @@ -297,6 +300,7 @@ SPEC CHECKSUMS: CocoaLumberjack: 2f44e60eb91c176d471fdba43b9e3eae6a721947 CryptoSwift: d81eeaa59dc5a8d03720fe919a6fd07b51f7439f Curve25519Kit: b3e77b7152ebe95fee2b3fb6c955449492bc14f7 + FeedKit: 3418eed25f0b493b205b4de1b8511ac21d413fa9 GCDWebServer: ead88cd14596dd4eae4f5830b8877c87c8728990 GRKOpenSSLFramework: 8a3735ad41e7dc1daff460467bccd32ca5d6ae3e HKDFKit: 3b6dbbb9d59c221cc6c52c3aa915700cbf24e376 @@ -309,7 +313,7 @@ SPEC CHECKSUMS: SAMKeychain: 483e1c9f32984d50ca961e26818a534283b4cd5c SignalCoreKit: c2d8132cdedb95d35eb2f8ae7eac0957695d0a8b SignalMetadataKit: 6fa5e9a53c7f104568662521a2f3874672ff7a02 - SignalServiceKit: 5c5b63a39d5054201ab59ef6daf0fa0a1a0c7887 + SignalServiceKit: 102576f58e17a5fe3093899adce7e7c192a7bee0 SQLCipher: efbdb52cdbe340bcd892b1b14297df4e07241b7f SSZipArchive: 8e859da2520142e09166bc9161967db296e9d02f Starscream: ef3ece99d765eeccb67de105bfa143f929026cf5 @@ -317,6 +321,6 @@ SPEC CHECKSUMS: YapDatabase: b418a4baa6906e8028748938f9159807fd039af4 YYImage: 1e1b62a9997399593e4b9c4ecfbbabbf1d3f3b54 -PODFILE CHECKSUM: 10152a1fffafd51206b62fdd8cac86a5de8cf083 +PODFILE CHECKSUM: 95f41137d4fe8c5b8a27de951b328f8c9531d166 -COCOAPODS: 1.7.2 +COCOAPODS: 1.5.3 diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 29912f5ee..50cca813b 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -3276,12 +3276,13 @@ files = ( ); 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}/AxolotlKit/AxolotlKit.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", "${BUILT_PRODUCTS_DIR}/CryptoSwift/CryptoSwift.framework", "${BUILT_PRODUCTS_DIR}/Curve25519Kit/Curve25519Kit.framework", + "${BUILT_PRODUCTS_DIR}/FeedKit/FeedKit.framework", "${BUILT_PRODUCTS_DIR}/GCDWebServer/GCDWebServer.framework", "${PODS_ROOT}/GRKOpenSSLFramework/OpenSSL-iOS/bin/openssl.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}/CryptoSwift.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}/openssl.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/HKDFKit.framework", @@ -3331,7 +3333,7 @@ ); runOnlyForDeploymentPostprocessing = 0; 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; }; 6565655F4068F9E5CDC5687F /* [CP] Check Pods Manifest.lock */ = { @@ -3358,7 +3360,7 @@ files = ( ); 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}/AxolotlKit/AxolotlKit.framework", "${BUILT_PRODUCTS_DIR}/CocoaLumberjack/CocoaLumberjack.framework", @@ -3409,7 +3411,7 @@ ); runOnlyForDeploymentPostprocessing = 0; 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; }; F4C416F20E3CB0B25DC10C56 /* [CP] Check Pods Manifest.lock */ = { diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index 130b9bf58..bfad24987 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -1,3 +1,4 @@ +import FeedKit @objc(LKGroupChatPoller) public final class LokiGroupChatPoller : NSObject { @@ -9,7 +10,7 @@ public final class LokiGroupChatPoller : NSObject { private lazy var pollForNewMessagesInterval: TimeInterval = { switch group.kind { case .publicChat(_): return 4 - case .rss(_): return 4//8 * 60 + case .rss(_): return 8 * 60 } }() @@ -72,12 +73,31 @@ public final class LokiGroupChatPoller : NSObject { } case .rss(_): let url = URL(string: group.server)! - let parser = LokiRSSFeedParser(url: url) - parser.parse { items in + 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 - print("Loki", "\(item.title ?? "nil"), \(item.description ?? "nil"), \(item.dateAsString ?? "nil")") + let id = group.id.data(using: String.Encoding.utf8)! + let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver) + x1.setName(group.displayName) + let x2 = SSKProtoDataMessage.builder() + guard let date = item.pubDate else { return } + let timestamp = UInt64(date.timeIntervalSince1970 * 1000) + x2.setTimestamp(timestamp) + x2.setGroup(try! x1.build()) + guard let title = item.title, let description = item.description else { return } + x2.setBody("\(title) ... \(description)") + let x3 = SSKProtoContent.builder() + x3.setDataMessage(try! x2.build()) + let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp) + x4.setSource(NSLocalizedString("Loki", comment: "")) + x4.setSourceDevice(OWSDevicePrimaryDeviceId) + x4.setContent(try! x3.build().serializedData()) + OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in + SSKEnvironment.shared.messageManager.throws_processEnvelope(try! x4.build(), plaintextData: try! x3.build().serializedData(), wasReceivedByUD: false, transaction: transaction) + } } } + } } diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 7ee7c96d5..94e785776 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2616,3 +2616,4 @@ "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 QR Code" = "Scan QR Code"; +"Loki" = "Loki"; From d5e56d9c9f8be0e489b95029149d19115f7927c3 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 15:25:37 +1000 Subject: [PATCH 13/16] Clean --- Signal/src/Loki/LokiGroupChatPoller.swift | 56 ++++++++----------- .../src/Loki/API/LokiRSSFeedParser.swift | 54 ------------------ 2 files changed, 22 insertions(+), 88 deletions(-) delete mode 100644 SignalServiceKit/src/Loki/API/LokiRSSFeedParser.swift diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index bfad24987..7e840770e 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -45,30 +45,33 @@ public final class LokiGroupChatPoller : NSObject { private func pollForNewMessages() { let group = self.group + func parseGroupMessage(body: String, timestamp: UInt64, senderDisplayName: String) { + let id = group.id.data(using: String.Encoding.utf8)! + let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver) + x1.setName(group.displayName) + let x2 = SSKProtoDataMessage.builder() + x2.setTimestamp(timestamp) + x2.setGroup(try! x1.build()) + x2.setBody(body) + let x3 = SSKProtoContent.builder() + x3.setDataMessage(try! x2.build()) + let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp) + x4.setSource(senderDisplayName) + x4.setSourceDevice(OWSDevicePrimaryDeviceId) + x4.setContent(try! x3.build().serializedData()) + OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in + 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 id = group.id.data(using: String.Encoding.utf8)! - let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver) - x1.setName(group.displayName) - let x2 = SSKProtoDataMessage.builder() - x2.setTimestamp(message.timestamp) - x2.setGroup(try! x1.build()) - x2.setBody(message.body) - let x3 = SSKProtoContent.builder() - x3.setDataMessage(try! x2.build()) - let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp) let senderHexEncodedPublicKey = message.hexEncodedPublicKey let endIndex = senderHexEncodedPublicKey.endIndex let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8) let senderDisplayName = "\(message.displayName) (...\(senderHexEncodedPublicKey[cutoffIndex.. Void)? - private var tag: Tag? - private var currentItem: Item? - private var items: [Item] = [] - - private enum Tag : String { - case item, title, description, date = "pubDate" - } - - public struct Item { - public var title: String? = nil - public var description: String? = nil - public var dateAsString: String? = nil - } - - public init(url: URL) { - self.url = url - super.init() - } - - public func parse(completion: @escaping (([Item]) -> Void)) { - guard let parser = XMLParser(contentsOf: url) else { return } - self.completion = completion - parser.delegate = self - parser.parse() - } - - public func parser(_ parser: XMLParser, didStartElement elementAsString: String, namespaceURI: String?, qualifiedName: String?, attributes: [String:String] = [:]) { - if let element = Tag(rawValue: elementAsString) { self.tag = element } - if tag == .item { currentItem = Item() } - } - - public func parser(_ parser: XMLParser, foundCharacters string: String) { - guard let element = tag else { return } - switch element { - case .title: currentItem?.title = string - case .description: currentItem?.description = string - case .date: currentItem?.dateAsString = string - default: break - } - } - - public func parser(_ parser: XMLParser, didEndElement elementAsString: String, namespaceURI: String?, qualifiedName: String?) { - guard let element = Tag(rawValue: elementAsString) else { return } - if element == .item, let currentItem = self.currentItem { items.append(currentItem) } - } - - public func parserDidEndDocument(_ parser: XMLParser) { - completion?(items) - } -} From 1ad54f8073b48b6e31aa099eff39cbfc167a6b1f Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 15:26:04 +1000 Subject: [PATCH 14/16] Update Pods --- Pods | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Pods b/Pods index 95f519e4b..20b736ae2 160000 --- a/Pods +++ b/Pods @@ -1 +1 @@ -Subproject commit 95f519e4b407713a98f98d0aec572b7487c941b3 +Subproject commit 20b736ae28ecd42b5fc13f583a010ac9354be507 From 00978d3a9667a40ef474dde201fceaa848567f1c Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 16:02:46 +1000 Subject: [PATCH 15/16] Fix Loki RSS feed item formatting --- Signal/src/Loki/LokiGroupChatPoller.swift | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index 7e840770e..78fdb8424 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -81,11 +81,23 @@ public final class LokiGroupChatPoller : NSObject { 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 body = "\(title) ... \(description)" - parseGroupMessage(body: body, timestamp: timestamp, senderDisplayName: NSLocalizedString("Loki", comment: "")) + let regex = try! NSRegularExpression(pattern: "]*?\\s+)?href=\"([^\"]*)\".*?>(.*?)<.*?\\/a>") + var bodyAsHTML = "\(title)\(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: "")) } } - } } From 465b8d7b7e0fd1b37f15cb07d2018b0563a372ba Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 27 Aug 2019 16:04:01 +1000 Subject: [PATCH 16/16] Add TODO --- Signal/src/Loki/LokiGroupChatPoller.swift | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Signal/src/Loki/LokiGroupChatPoller.swift b/Signal/src/Loki/LokiGroupChatPoller.swift index 78fdb8424..fe1012f70 100644 --- a/Signal/src/Loki/LokiGroupChatPoller.swift +++ b/Signal/src/Loki/LokiGroupChatPoller.swift @@ -1,5 +1,7 @@ import FeedKit +// TODO: Move the RSS feed logic into its own file + @objc(LKGroupChatPoller) public final class LokiGroupChatPoller : NSObject { private let group: LokiGroupChat