This commit is contained in:
Niels Andriesse 2019-08-27 16:48:53 +10:00
parent b61b440063
commit e21cced9bb
10 changed files with 177 additions and 124 deletions

View File

@ -562,6 +562,7 @@
B821F2FA2272CEEE002C88C0 /* SeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821F2F92272CEEE002C88C0 /* SeedViewController.swift */; }; B821F2FA2272CEEE002C88C0 /* SeedViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B821F2F92272CEEE002C88C0 /* SeedViewController.swift */; };
B825848B230F94FE001B41CB /* QRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B825848A230F94FE001B41CB /* QRCodeViewController.swift */; }; B825848B230F94FE001B41CB /* QRCodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B825848A230F94FE001B41CB /* QRCodeViewController.swift */; };
B8258493230FA5E9001B41CB /* ScanQRCodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B8258492230FA5E9001B41CB /* ScanQRCodeViewController.m */; }; B8258493230FA5E9001B41CB /* ScanQRCodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B8258492230FA5E9001B41CB /* ScanQRCodeViewController.m */; };
B82584A02315024B001B41CB /* LokiRSSFeedPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */; };
B845B4D4230CD09100D759F0 /* LokiGroupChatPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */; }; B845B4D4230CD09100D759F0 /* LokiGroupChatPoller.swift in Sources */ = {isa = PBXBuildFile; fileRef = B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */; };
B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */; }; B846365B22B7418B00AF1514 /* Identicon+ObjC.swift in Sources */ = {isa = PBXBuildFile; fileRef = B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */; };
B89841E322B7579F00B1BDC6 /* NewConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */; }; B89841E322B7579F00B1BDC6 /* NewConversationViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */; };
@ -1355,6 +1356,7 @@
B825848A230F94FE001B41CB /* QRCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeViewController.swift; sourceTree = "<group>"; }; B825848A230F94FE001B41CB /* QRCodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeViewController.swift; sourceTree = "<group>"; };
B8258491230FA5DA001B41CB /* ScanQRCodeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScanQRCodeViewController.h; sourceTree = "<group>"; }; B8258491230FA5DA001B41CB /* ScanQRCodeViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ScanQRCodeViewController.h; sourceTree = "<group>"; };
B8258492230FA5E9001B41CB /* ScanQRCodeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScanQRCodeViewController.m; sourceTree = "<group>"; }; B8258492230FA5E9001B41CB /* ScanQRCodeViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScanQRCodeViewController.m; sourceTree = "<group>"; };
B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiRSSFeedPoller.swift; sourceTree = "<group>"; };
B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiGroupChatPoller.swift; sourceTree = "<group>"; }; B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LokiGroupChatPoller.swift; sourceTree = "<group>"; };
B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = "<group>"; }; B846365A22B7418B00AF1514 /* Identicon+ObjC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Identicon+ObjC.swift"; sourceTree = "<group>"; };
B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationViewController.swift; sourceTree = "<group>"; }; B89841E222B7579F00B1BDC6 /* NewConversationViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NewConversationViewController.swift; sourceTree = "<group>"; };
@ -2620,6 +2622,7 @@
B821F2F92272CEEE002C88C0 /* SeedViewController.swift */, B821F2F92272CEEE002C88C0 /* SeedViewController.swift */,
24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */, 24A830A12293CD0100F4CAC0 /* LokiP2PServer.swift */,
B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */, B845B4D3230CD09000D759F0 /* LokiGroupChatPoller.swift */,
B825849F2315024B001B41CB /* LokiRSSFeedPoller.swift */,
B825848A230F94FE001B41CB /* QRCodeViewController.swift */, B825848A230F94FE001B41CB /* QRCodeViewController.swift */,
B8258491230FA5DA001B41CB /* ScanQRCodeViewController.h */, B8258491230FA5DA001B41CB /* ScanQRCodeViewController.h */,
B8258492230FA5E9001B41CB /* ScanQRCodeViewController.m */, B8258492230FA5E9001B41CB /* ScanQRCodeViewController.m */,
@ -3674,6 +3677,7 @@
4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */, 4C4AEC4520EC343B0020E72B /* DismissableTextField.swift in Sources */,
4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */, 4CB5F26720F6E1E2004D1B42 /* MenuActionsViewController.swift in Sources */,
3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */, 3496955E219B605E00DCFE74 /* PhotoLibrary.swift in Sources */,
B82584A02315024B001B41CB /* LokiRSSFeedPoller.swift in Sources */,
24A830A22293CD0100F4CAC0 /* LokiP2PServer.swift in Sources */, 24A830A22293CD0100F4CAC0 /* LokiP2PServer.swift in Sources */,
349ED990221B0194008045B0 /* Onboarding2FAViewController.swift in Sources */, 349ED990221B0194008045B0 /* Onboarding2FAViewController.swift in Sources */,
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */, 45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */,

View File

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

View File

@ -65,8 +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) LKRSSFeedPoller *lokiNewsFeedPoller;
@property (nonatomic) LKGroupChatPoller *lokiMessengerUpdatesPoller; @property (nonatomic) LKRSSFeedPoller *lokiMessengerUpdatesFeedPoller;
@end @end
@ -1489,28 +1489,52 @@ static NSTimeInterval launchStartedAt;
- (LKGroupChat *)lokiPublicChat - (LKGroupChat *)lokiPublicChat
{ {
return [[LKGroupChat alloc] initWithKindAsString:@"publicChat" id:@(LKGroupChatAPI.publicChatID).stringValue server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true]; return [[LKGroupChat alloc] initWithServerID:@(LKGroupChatAPI.publicChatServerID).unsignedIntegerValue server:LKGroupChatAPI.publicChatServer displayName:NSLocalizedString(@"Loki Public Chat", @"") isDeletable:true];
} }
- (LKGroupChat *)lokiNews - (LKRSSFeed *)lokiNewsFeed
{ {
return [[LKGroupChat alloc] initWithKindAsString:@"rss" id:@"loki.network.feed" server:@"https://loki.network/feed/" displayName:NSLocalizedString(@"Loki News", @"") isDeletable:true]; return [[LKRSSFeed alloc] initWithId:@"loki.network.feed" server:@"https://loki.network/feed/" displayName:NSLocalizedString(@"Loki News", @"") isDeletable:true];
} }
- (LKGroupChat *)lokiMessengerUpdates - (LKRSSFeed *)lokiMessengerUpdatesFeed
{ {
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]; return [[LKRSSFeed alloc] initWithId:@"loki.network.messenger-updates" server:@"https://loki.network/category/messenger-updates/feed/" displayName:NSLocalizedString(@"Loki Messenger Updates", @"") isDeletable:false];
} }
- (void)createGroupChatsIfNeeded - (void)createGroupChatsIfNeeded
{ {
NSArray *allGroupChats = @[ self.lokiPublicChat, self.lokiNews, self.lokiMessengerUpdates ]; LKGroupChat *publicChat = self.lokiPublicChat;
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey; NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
for (LKGroupChat *chat in allGroupChats) { NSString *userDefaultsKey = [@"isGroupChatSetUp." stringByAppendingString:publicChat.id];
NSString *userDefaultsKey = [@"isSetUp." stringByAppendingString:chat.id]; BOOL isChatSetUp = [NSUserDefaults.standardUserDefaults boolForKey:userDefaultsKey];
BOOL isChatSetUp = [NSUserDefaults.standardUserDefaults boolForKey:userDefaultsKey]; if (!isChatSetUp || !publicChat.isDeletable) {
if (!isChatSetUp || !chat.isDeletable) { TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:publicChat.displayName memberIds:@[ userHexEncodedPublicKey, publicChat.server ] image:nil groupId:[publicChat.id dataUsingEncoding:NSUTF8StringEncoding]];
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];
}
}
- (void)createRSSFeedsIfNeeded
{
NSArray *feeds = @[ self.lokiNewsFeed, self.lokiMessengerUpdatesFeed ];
NSString *userHexEncodedPublicKey = OWSIdentityManager.sharedManager.identityKeyPair.hexEncodedPublicKey;
for (LKRSSFeed *feed in feeds) {
NSString *userDefaultsKey = [@"isRSSFeedSetUp." stringByAppendingString:feed.id];
BOOL isFeedSetUp = [NSUserDefaults.standardUserDefaults boolForKey:userDefaultsKey];
if (!isFeedSetUp || !feed.isDeletable) {
TSGroupModel *group = [[TSGroupModel alloc] initWithTitle:feed.displayName memberIds:@[ userHexEncodedPublicKey, feed.server ] image:nil groupId:[feed.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];
@ -1531,16 +1555,25 @@ static NSTimeInterval launchStartedAt;
- (void)createGroupChatPollersIfNeeded - (void)createGroupChatPollersIfNeeded
{ {
if (self.lokiPublicChatPoller == nil) { self.lokiPublicChatPoller = [[LKGroupChatPoller alloc] initForGroup:self.lokiPublicChat]; } 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)createRSSFeedPollersIfNeeded
{
if (self.lokiNewsFeedPoller == nil) { self.lokiNewsFeedPoller = [[LKRSSFeedPoller alloc] initForFeed:self.lokiNewsFeed]; }
if (self.lokiMessengerUpdatesFeedPoller == nil) { self.lokiMessengerUpdatesFeedPoller = [[LKRSSFeedPoller alloc] initForFeed:self.lokiMessengerUpdatesFeed]; }
} }
- (void)startGroupChatPollersIfNeeded - (void)startGroupChatPollersIfNeeded
{ {
[self createGroupChatPollersIfNeeded]; [self createGroupChatPollersIfNeeded];
[self.lokiPublicChatPoller startIfNeeded]; [self.lokiPublicChatPoller startIfNeeded];
[self.lokiNewsPoller startIfNeeded]; }
[self.lokiMessengerUpdatesPoller startIfNeeded];
- (void)startRSSFeedPollersIfNeeded
{
[self createRSSFeedPollersIfNeeded];
[self.lokiNewsFeedPoller startIfNeeded];
[self.lokiMessengerUpdatesFeedPoller startIfNeeded];
} }
@end @end

View File

@ -1,6 +1,3 @@
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 {
@ -9,19 +6,8 @@ public final class LokiGroupChatPoller : NSObject {
private var pollForDeletedMessagesTimer: Timer? = nil private var pollForDeletedMessagesTimer: Timer? = nil
private var hasStarted = false private var hasStarted = false
private lazy var pollForNewMessagesInterval: TimeInterval = { private let pollForNewMessagesInterval: TimeInterval = 4
switch group.kind { private let pollForDeletedMessagesInterval: TimeInterval = 32 * 60
case .publicChat(_): return 4
case .rss(_): return 8 * 60
}
}()
private lazy var pollForDeletedMessagesInterval: TimeInterval = {
switch group.kind {
case .publicChat(_): return 32 * 60
case .rss(_): preconditionFailure()
}
}()
@objc(initForGroup:) @objc(initForGroup:)
public init(for group: LokiGroupChat) { public init(for group: LokiGroupChat) {
@ -33,9 +19,7 @@ public final class LokiGroupChatPoller : NSObject {
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 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
} }
@ -47,57 +31,27 @@ public final class LokiGroupChatPoller : NSObject {
private func pollForNewMessages() { private func pollForNewMessages() {
let group = self.group let group = self.group
func parseGroupMessage(body: String, timestamp: UInt64, senderDisplayName: String) { let _ = LokiGroupChatAPI.getMessages(for: group.serverID, on: group.server).done { messages in
let id = group.id.data(using: String.Encoding.utf8)! messages.reversed().forEach { message in
let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver) let senderHexEncodedPublicKey = message.hexEncodedPublicKey
x1.setName(group.displayName) let endIndex = senderHexEncodedPublicKey.endIndex
let x2 = SSKProtoDataMessage.builder() let cutoffIndex = senderHexEncodedPublicKey.index(endIndex, offsetBy: -8)
x2.setTimestamp(timestamp) let senderDisplayName = "\(message.displayName) (...\(senderHexEncodedPublicKey[cutoffIndex..<endIndex]))"
x2.setGroup(try! x1.build()) let id = group.id.data(using: String.Encoding.utf8)!
x2.setBody(body) let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver)
let x3 = SSKProtoContent.builder() x1.setName(group.displayName)
x3.setDataMessage(try! x2.build()) let x2 = SSKProtoDataMessage.builder()
let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: timestamp) x2.setTimestamp(message.timestamp)
x4.setSource(senderDisplayName) x2.setGroup(try! x1.build())
x4.setSourceDevice(OWSDevicePrimaryDeviceId) x2.setBody(message.body)
x4.setContent(try! x3.build().serializedData()) let x3 = SSKProtoContent.builder()
OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in x3.setDataMessage(try! x2.build())
SSKEnvironment.shared.messageManager.throws_processEnvelope(try! x4.build(), plaintextData: try! x3.build().serializedData(), wasReceivedByUD: false, transaction: transaction) let x4 = SSKProtoEnvelope.builder(type: .ciphertext, timestamp: message.timestamp)
} x4.setSource(senderDisplayName)
} x4.setSourceDevice(OWSDevicePrimaryDeviceId)
switch group.kind { x4.setContent(try! x3.build().serializedData())
case .publicChat(let id): OWSPrimaryStorage.shared().dbReadWriteConnection.readWrite { transaction in
let _ = LokiGroupChatAPI.getMessages(for: id, on: group.server).done { messages in SSKEnvironment.shared.messageManager.throws_processEnvelope(try! x4.build(), plaintextData: try! x3.build().serializedData(), wasReceivedByUD: false, transaction: transaction)
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: ""))
} }
} }
} }

View File

@ -0,0 +1,70 @@
import FeedKit
@objc(LKRSSFeedPoller)
public final class LokiRSSFeedPoller : NSObject {
private let feed: LokiRSSFeed
private var timer: Timer? = nil
private var hasStarted = false
private let interval: TimeInterval = 8 * 60
@objc(initForFeed:)
public init(for feed: LokiRSSFeed) {
self.feed = feed
super.init()
}
@objc public func startIfNeeded() {
if hasStarted { return }
timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: true) { [weak self] _ in self?.poll() }
poll() // Perform initial update
hasStarted = true
}
@objc public func stop() {
timer?.invalidate()
hasStarted = false
}
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>\(description)"
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 = feed.id.data(using: String.Encoding.utf8)!
let x1 = SSKProtoGroupContext.builder(id: id, type: .deliver)
x1.setName(feed.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(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)
}
}
}
}
}

View File

@ -693,7 +693,9 @@ 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 createGroupChatsIfNeeded]; [appDelegate createGroupChatsIfNeeded];
[appDelegate createRSSFeedsIfNeeded];
[appDelegate startGroupChatPollersIfNeeded]; [appDelegate startGroupChatPollersIfNeeded];
[appDelegate startRSSFeedPollersIfNeeded];
} }
} }

View File

@ -1,48 +1,19 @@
@objc(LKGroupChat) @objc(LKGroupChat)
public final class LokiGroupChat : NSObject { public final class LokiGroupChat : NSObject {
public let kind: Kind @objc public let id: String
@objc public let serverID: UInt
@objc public let server: String @objc public let server: String
@objc public let displayName: String @objc public let displayName: String
@objc public let isDeletable: Bool @objc public let isDeletable: Bool
@objc public var id: String { @objc public init(serverID: UInt, server: String, displayName: String, isDeletable: Bool) {
switch kind { self.id = "\(server).\(serverID)"
case .publicChat(let id): return "\(server).\(id)" self.serverID = serverID
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.server = server
self.displayName = displayName self.displayName = displayName
self.isDeletable = isDeletable self.isDeletable = isDeletable
} }
@objc public convenience init(kindAsString: String, id: String, server: String, displayName: String, isDeletable: Bool) { override public var description: String { return displayName }
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))" }
} }

View File

@ -11,7 +11,7 @@ public final class LokiGroupChatAPI : NSObject {
// MARK: Public Chat // MARK: Public Chat
@objc public static let publicChatServer = "https://chat.lokinet.org" @objc public static let publicChatServer = "https://chat.lokinet.org"
@objc public static let publicChatMessageType = "network.loki.messenger.publicChat" @objc public static let publicChatMessageType = "network.loki.messenger.publicChat"
@objc public static let publicChatID = 1 @objc public static let publicChatServerID = 1
// MARK: Convenience // MARK: Convenience
private static var userDisplayName: String { private static var userDisplayName: String {

View File

@ -0,0 +1,17 @@
@objc(LKRSSFeed)
public final class LokiRSSFeed : NSObject {
@objc public let id: String
@objc public let server: String
@objc public let displayName: String
@objc public let isDeletable: Bool
@objc public init(id: String, server: String, displayName: String, isDeletable: Bool) {
self.id = id
self.server = server
self.displayName = displayName
self.isDeletable = isDeletable
}
override public var description: String { return displayName }
}

View File

@ -1114,7 +1114,7 @@ NSString *const OWSMessageSenderRateLimitedException = @"RateLimitedException";
NSString *displayName = SSKEnvironment.shared.profileManager.localProfileName; NSString *displayName = SSKEnvironment.shared.profileManager.localProfileName;
if (displayName == nil) { displayName = @"Anonymous"; } if (displayName == nil) { displayName = @"Anonymous"; }
LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName body:message.body type:LKGroupChatAPI.publicChatMessageType timestamp:message.timestamp]; LKGroupMessage *groupMessage = [[LKGroupMessage alloc] initWithHexEncodedPublicKey:userHexEncodedPublicKey displayName:displayName body:message.body type:LKGroupChatAPI.publicChatMessageType timestamp:message.timestamp];
[[LKGroupChatAPI sendMessage:groupMessage toGroup:LKGroupChatAPI.publicChatID onServer:LKGroupChatAPI.publicChatServer] [[LKGroupChatAPI sendMessage:groupMessage toGroup:LKGroupChatAPI.publicChatServerID onServer:LKGroupChatAPI.publicChatServer]
.thenOn(OWSDispatch.sendingQueue, ^(id result) { .thenOn(OWSDispatch.sendingQueue, ^(id result) {
[self messageSendDidSucceed:messageSend deviceMessages:deviceMessages wasSentByUD:false wasSentByWebsocket:false]; [self messageSendDidSucceed:messageSend deviceMessages:deviceMessages wasSentByUD:false wasSentByWebsocket:false];
}) })