// // MBIMBridge.m // MessagesBridge // // Created by James Magahern on 11/12/18. // Copyright © 2018 James Magahern. All rights reserved. // #import "MBIMBridge.h" #import #import #import #import #import static NSString *const MBIMBridgeToken = @"net.buzzert.MBIMBridge"; static NSString *const kAPIEndpointConversationList = @"conversations"; static NSString *const kAPIEndpointConversationContents = @"messages"; static NSString *const kAPIEndpointSendMessage = @"sendMessage"; @interface MBIMBridge (/* INTERNAL */) @property (nonatomic, strong) GCDWebServer *webServer; - (instancetype)_init; @end @implementation MBIMBridge + (instancetype)sharedInstance { static dispatch_once_t onceToken; static __strong MBIMBridge *sharedBridge = nil; dispatch_once(&onceToken, ^{ sharedBridge = [[MBIMBridge alloc] _init]; }); return sharedBridge; } - (instancetype)_init { self = [super init]; if (self) { [self registerForNotifications]; [self startWebServer]; [sDaemonController setDelegate:self]; [sDaemonListener addHandler:self]; } return self; } #pragma mark - #pragma mark Connection - (void)connect { if (![sDaemonController hasListenerForID: MBIMBridgeToken]) { if (![sDaemonController addListenerID:MBIMBridgeToken capabilities:(kFZListenerCapFileTransfers | kFZListenerCapManageStatus | kFZListenerCapChats | kFZListenerCapMessageHistory | kFZListenerCapIDQueries | kFZListenerCapSendMessages)]) { NSLog(@"Failed to connect to imagent"); } } } - (void)disconnect { [sDaemonController removeListenerID:MBIMBridgeToken]; } #pragma mark - #pragma mark Notifications - (void)registerForNotifications { (void)[IMChatRegistry sharedInstance]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_messageReceived:) name:IMChatMessageReceivedNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_chatRegistryDidLoad:) name:IMChatRegistryDidLoadNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_chatItemsDidChange:) name:IMChatItemsDidChangeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserverForName: IMChatRegistryUnreadCountChangedNotification object: nil queue: [NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) { NSLog(@"Unread count changed: %d", (int)[[IMChatRegistry sharedInstance] unreadCount]); }]; } - (void)_messageReceived:(NSNotification *)notification { NSLog(@"Received!"); // Sending a message /* IMAccount *iMessageAccount = [[IMAccountController sharedInstance] bestAccountForService:[IMServiceImpl iMessageService]]; IMHandle *handle = [[iMessageAccount arrayOfAllIMHandles] firstObject]; NSAttributedString *replyAttrString = [[NSAttributedString alloc] initWithString:@"This is a test automated reply. Please ignore."]; IMMessage *reply = [IMMessage fromMeIMHandle:handle withText:replyAttrString fileTransferGUIDs:@[] flags:kIMMessageFinished]; IMChat *chat = [notification object]; [chat sendMessage:reply]; */ } - (void)_chatRegistryDidLoad:(NSNotification *)notification { NSLog(@"Loaded chat registry. %lu existing chats", (unsigned long)[sChatRegistry numberOfExistingChats]); } - (void)_chatItemsDidChange:(NSNotification *)notification { NSLog(@"chat items changed: %@", notification); } - (BOOL)_sendMessage:(NSString *)messageBody toChatWithGUID:(NSString *)chatGUID { IMAccount *iMessageAccount = [[IMAccountController sharedInstance] bestAccountForService:[IMServiceImpl iMessageService]]; IMHandle *handle = [[iMessageAccount arrayOfAllIMHandles] firstObject]; NSAttributedString *replyAttrString = [[NSAttributedString alloc] initWithString:messageBody]; IMMessage *reply = [IMMessage fromMeIMHandle:handle withText:replyAttrString fileTransferGUIDs:@[] flags:kIMMessageFinished]; IMChat *chat = [sChatRegistry existingChatWithGUID:chatGUID]; if (!chat) { NSLog(@"Chat does not exist: %@", chatGUID); return NO; } [chat sendMessage:reply]; return YES; } #pragma mark - #pragma mark Web Server initialization - (void)startWebServer { [GCDWebServer setLogLevel:3]; __auto_type __weak weakSelf = self; self.webServer = [[GCDWebServer alloc] init]; [self.webServer addDefaultHandlerForMethod:@"GET" requestClass:[GCDWebServerRequest class] processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) { return [weakSelf _handleWebServerRequest:request]; }]; [self.webServer addDefaultHandlerForMethod:@"POST" requestClass:[GCDWebServerURLEncodedFormRequest class] processBlock:^GCDWebServerResponse * _Nullable(__kindof GCDWebServerRequest * _Nonnull request) { return [weakSelf _handleWebServerPOST:(GCDWebServerURLEncodedFormRequest *)request]; }]; [self.webServer startWithPort:8080 bonjourName:nil]; } - (GCDWebServerResponse *)_handleWebServerPOST:(__kindof GCDWebServerURLEncodedFormRequest * _Nonnull)request { NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:[request URL] resolvingAgainstBaseURL:NO]; NSString *endpoint = [[urlComponents path] lastPathComponent]; if ([endpoint isEqualToString:kAPIEndpointSendMessage]) { NSDictionary *args = [request arguments]; NSString *guid = [args objectForKey:@"guid"]; NSString *messageBody = [args objectForKey:@"body"]; BOOL result = [self _sendMessage:messageBody toChatWithGUID:guid]; if (result) { return [GCDWebServerDataResponse responseWithStatusCode:200]; } else { return [GCDWebServerDataResponse responseWithStatusCode:500]; } } return [GCDWebServerDataResponse responseWithStatusCode:404]; } - (GCDWebServerResponse *)_handleWebServerRequest:(__kindof GCDWebServerRequest * _Nonnull)request { NSURLComponents *urlComponents = [NSURLComponents componentsWithURL:[request URL] resolvingAgainstBaseURL:NO]; NSString *endpoint = [[urlComponents path] lastPathComponent]; if ([endpoint isEqualToString:kAPIEndpointConversationList]) { NSArray *chats = [sChatRegistry allExistingChats]; NSMutableArray *conversations = [NSMutableArray array]; for (IMChat *chat in chats) { NSMutableDictionary *chatDict = [NSMutableDictionary dictionary]; chatDict[@"guid"] = [chat guid]; chatDict[@"displayName"] = [chat displayName]; chatDict[@"date"] = GCDWebServerFormatRFC822([chat lastFinishedMessageDate]); [conversations addObject:chatDict]; } return [GCDWebServerDataResponse responseWithJSONObject:conversations]; } if ([endpoint isEqualToString:kAPIEndpointConversationContents]) { NSString *guid = nil; for (NSURLQueryItem *queryItem in [urlComponents queryItems]) { if ([[queryItem name] isEqualToString:@"guid"]) { guid = [queryItem value]; break; } } if (!guid) { NSLog(@"No query item provided"); return [GCDWebServerDataResponse responseWithStatusCode:500]; } IMChat *chat = [sChatRegistry existingChatWithGUID:guid]; if (!chat) { NSLog(@"Chat with guid: %@ not found", guid); return [GCDWebServerDataResponse responseWithStatusCode:500]; } // Load messages [chat loadMessagesBeforeDate:[NSDate date] limit:50 loadImmediately:YES]; NSMutableArray *messages = [NSMutableArray array]; for (IMMessageItem *imMessage in [[chat chatItems] messages]) { NSMutableDictionary *messageDict = [NSMutableDictionary dictionary]; messageDict[@"text"] = [[imMessage body] string]; messageDict[@"date"] = GCDWebServerFormatRFC822([imMessage time]); messageDict[@"sender"] = [imMessage sender]; [messages addObject:messageDict]; } return [GCDWebServerDataResponse responseWithJSONObject:messages]; } return [GCDWebServerDataResponse responseWithStatusCode:404]; } #pragma mark - #pragma mark Daemon lifecycle - (void)daemonControllerWillConnect { NSLog(@"About to connect to daemon"); } - (void)daemonControllerDidConnect { NSLog(@"Did connect to daemon"); IMAccount *iMessageAccount = [[IMAccountController sharedInstance] bestAccountForService:[IMServiceImpl iMessageService]]; if (iMessageAccount) { NSLog(@"iMessage account connected: %@", iMessageAccount); } else { NSLog(@"Was not able to connect to iMessage account"); } } - (void)daemonControllerDidDisconnect { NSLog(@"Did disconnect from daemon"); } - (void)daemonConnectionLost { NSLog(@"Connection lost to daemon"); } @end