ScreenshotJsonParser.m 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2015-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import "ScreenshotJsonParser.h"
  21. #import "MyIdentityStore.h"
  22. #import "EntityManager.h"
  23. #import "Contact.h"
  24. #import "Conversation.h"
  25. #import "GroupProxy.h"
  26. #import "NSString+Hex.h"
  27. #import "MediaConverter.h"
  28. #import "ProtocolDefines.h"
  29. #import "ServerConnector.h"
  30. #import "UserSettings.h"
  31. #import "LicenseStore.h"
  32. #import <UIKit/UIImage.h>
  33. @interface ScreenshotJsonParser ()
  34. @property NSString *myIdentity;
  35. @property NSBundle *bundle;
  36. @property EntityManager *entityManager;
  37. @end
  38. @implementation ScreenshotJsonParser
  39. - (instancetype)init
  40. {
  41. self = [super init];
  42. if (self) {
  43. _entityManager = [[EntityManager alloc] initForBackgroundProcess:NO];
  44. }
  45. return self;
  46. }
  47. - (void)clearAll {
  48. [[ServerConnector sharedServerConnector] disconnect];
  49. MyIdentityStore *myIdentityStore = [MyIdentityStore sharedMyIdentityStore];
  50. [myIdentityStore destroy];
  51. [_entityManager performSyncBlockAndSafe:^{
  52. NSArray *conversations = [_entityManager.entityFetcher allConversations];
  53. for (Conversation* conversation in conversations) {
  54. [[_entityManager entityDestroyer] deleteObjectWithObject:conversation];
  55. }
  56. NSArray *contacts = [_entityManager.entityFetcher allContacts];
  57. for (Contact* contact in contacts) {
  58. [[_entityManager entityDestroyer] deleteObjectWithObject:contact];
  59. }
  60. }];
  61. }
  62. - (void)loadDataFromDirectory:(NSString*)directory {
  63. _bundle = [NSBundle bundleWithPath:directory];
  64. NSString *path = [_bundle pathForResource:@"data" ofType:@"json"];
  65. NSData *jsonData = [NSData dataWithContentsOfFile:path];
  66. NSError *error;
  67. NSDictionary *json = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
  68. if (json == nil) {
  69. NSLog(@"Error parsing ballot json data %@, %@", error, [error userInfo]);
  70. return;
  71. }
  72. NSString *idBackup = [json objectForKey:@"identity"];
  73. [self handleIdBackup:idBackup];
  74. _myIdentity = [MyIdentityStore sharedMyIdentityStore].identity;
  75. NSMutableDictionary *profile = [NSMutableDictionary new];
  76. [profile setValue:[self imageDataFromFileNamed:@"me.jpg"] forKey:@"ProfilePicture"];
  77. [profile removeObjectForKey:@"LastUpload"];
  78. [[MyIdentityStore sharedMyIdentityStore] setProfilePicture:profile];
  79. if ([LicenseStore requiresLicenseKey]) {
  80. [[MyIdentityStore sharedMyIdentityStore] setPushFromName:@"Julia S."];
  81. [[MyIdentityStore sharedMyIdentityStore] setLinkedEmail:@"***@***"];
  82. [[MyIdentityStore sharedMyIdentityStore] setLinkedMobileNo:@"1234567890"];
  83. [[MyIdentityStore sharedMyIdentityStore] setLinkEmailPending:false];
  84. [[MyIdentityStore sharedMyIdentityStore] setLinkMobileNoPending:false];
  85. } else {
  86. [[MyIdentityStore sharedMyIdentityStore] setPushFromName:@"Eva Anonymous"];
  87. }
  88. [_entityManager performSyncBlockAndSafe:^{
  89. NSDictionary *contactData = [json objectForKey:@"contacts"];
  90. [self handleContacts:contactData];
  91. }];
  92. [_entityManager performSyncBlockAndSafe:^{
  93. NSDictionary *groupData = [json objectForKey:@"groups"];
  94. [self handleGroups:groupData];
  95. }];
  96. }
  97. - (void)handleIdBackup:(NSString *)idBackup {
  98. MyIdentityStore *myIdentityStore = [MyIdentityStore sharedMyIdentityStore];
  99. dispatch_semaphore_t sema = dispatch_semaphore_create(0);
  100. dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
  101. [myIdentityStore restoreFromBackup:idBackup withPassword:@"12345678" onCompletion:^{
  102. myIdentityStore.serverGroup = @"ae";
  103. [myIdentityStore storeInKeychain];
  104. dispatch_semaphore_signal(sema);
  105. } onError:^(NSError *error) {
  106. [self fail:error.localizedDescription];
  107. dispatch_semaphore_signal(sema);
  108. }];
  109. });
  110. dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
  111. }
  112. - (void)fail:(NSString *)reason {
  113. NSLog(@"failed: %@", reason);
  114. NSAssert(FALSE, reason);
  115. }
  116. - (void)handleContacts:(NSDictionary *)contactData {
  117. NSEnumerator *enumerator = [contactData keyEnumerator];
  118. id identity;
  119. while ((identity = [enumerator nextObject])) {
  120. NSDictionary *data = [contactData objectForKey:identity];
  121. [self handleContact:identity data:data];
  122. }
  123. }
  124. - (void)handleContact:(NSString *)identity data:(NSDictionary *)data {
  125. Contact *contact = [_entityManager.entityCreator contact];
  126. contact.identity = identity;
  127. contact.imageData = [self localizedImageForKey:@"avatar" in:data];
  128. contact.verifiedEmail = [self localizedStringForKey:@"mail" in:data];
  129. NSNumber *verificationLevel = [data objectForKey:@"verification"];
  130. contact.verificationLevel = [NSNumber numberWithInt: verificationLevel.intValue - 1];
  131. BOOL isWork = [[data objectForKey:@"isWork"] boolValue];
  132. if (isWork) {
  133. contact.verificationLevel = [NSNumber numberWithInt:kVerificationLevelServerVerified];
  134. if ([LicenseStore requiresLicenseKey]) {
  135. contact.workContact = [NSNumber numberWithBool:YES];
  136. }
  137. NSMutableOrderedSet *workIdentities = [[NSMutableOrderedSet alloc] initWithOrderedSet:[UserSettings sharedUserSettings].workIdentities];
  138. [workIdentities addObject:contact.identity];
  139. [UserSettings sharedUserSettings].workIdentities = workIdentities;
  140. }
  141. NSString *publicKey = [data objectForKey:@"pk"];
  142. contact.publicKey = [publicKey decodeHex];
  143. NSArray *names = [self localizedArrayForKey:@"name" in:data];
  144. if (names.count == 2) {
  145. contact.firstName = names[0];
  146. contact.lastName = names[1];
  147. }
  148. NSArray *conversationData = [data objectForKey:@"conversation"];
  149. if (conversationData) {
  150. Conversation *conversation = [_entityManager.entityCreator conversation];
  151. conversation.contact = contact;
  152. [self handleConversation:conversation data:conversationData];
  153. }
  154. }
  155. - (void)handleConversation:(Conversation *)conversation data:(NSArray *)data {
  156. for (NSDictionary *messageData in data) {
  157. NSString *type = [messageData objectForKey:@"type"];
  158. BaseMessage *message;
  159. if ([type isEqualToString:@"TEXT"]) {
  160. message = [self handleTextMessage:messageData inConversation:conversation];
  161. } else if ([type isEqualToString:@"IMAGE"]) {
  162. message = [self handleImageMessage:messageData inConversation:conversation];
  163. } else if ([type isEqualToString:@"AUDIO"]) {
  164. message = [self handleAudioMessage:messageData inConversation:conversation];
  165. } else if ([type isEqualToString:@"BALLOT"]) {
  166. message = [self handleBallotMessage:messageData inConversation:conversation];
  167. } else if ([type isEqualToString:@"FILE"]) {
  168. message = [self handleFileMessage:messageData inConversation:conversation];
  169. } else if ([type isEqualToString:@"VOIP_STATUS"]) {
  170. message = [self handleCallMessage:messageData inConversation:conversation];
  171. } else if ([type isEqualToString:@"LOCATION"]) {
  172. message = [self handleLocationMessage:messageData inConversation:conversation];
  173. }
  174. NSNumber *dateOffsetMin = [messageData objectForKey:@"date"];
  175. NSInteger dateOffsetS = dateOffsetMin.integerValue * 60;
  176. message.date = [NSDate dateWithTimeInterval:dateOffsetS sinceDate:_referenceDate];
  177. BOOL outgoing = ((NSNumber *)[messageData objectForKey:@"out"]).boolValue;
  178. message.isOwn = [NSNumber numberWithBool:outgoing];
  179. if (outgoing == NO) {
  180. message.sender = conversation.contact;
  181. }
  182. message.sent = [NSNumber numberWithBool:YES];
  183. message.delivered = [NSNumber numberWithBool:YES];
  184. NSNumber *read = ((NSNumber *)[messageData objectForKey:@"read"]);
  185. if (!read) {
  186. message.read = [NSNumber numberWithBool:YES];
  187. } else {
  188. message.read = read;
  189. int unreadMessageCount = conversation.unreadMessageCount.intValue;
  190. unreadMessageCount++;
  191. conversation.unreadMessageCount = [NSNumber numberWithInt:unreadMessageCount];
  192. }
  193. NSString *state = [messageData objectForKey:@"state"];
  194. if ([state isEqualToString:@"USERACK"]) {
  195. message.userackDate = [NSDate date];
  196. message.userack = [NSNumber numberWithBool:YES];
  197. }
  198. // groups only
  199. NSString *identity = [messageData objectForKey:@"identity"];
  200. if ([identity isEqualToString:@"$"] == NO) {
  201. message.sender = [_entityManager.entityFetcher contactForId:identity];
  202. }
  203. }
  204. }
  205. - (Conversation *)createGroupConversationFromData:(NSDictionary *)groupData {
  206. Conversation *conversation = [_entityManager.entityCreator conversation];
  207. NSString *groupId = [groupData objectForKey:@"id"];
  208. conversation.groupId = [groupId decodeHex];
  209. NSString *creator = [groupData objectForKey:@"creator"];
  210. if ([creator isEqualToString:_myIdentity] == NO) {
  211. Contact *creatorContact = [_entityManager.entityFetcher contactForId:creator];
  212. conversation.contact = creatorContact;
  213. }
  214. NSData *avatar = [self localizedImageForKey:@"avatar" in:groupData];
  215. if (avatar) {
  216. ImageData *dbImage = [_entityManager.entityCreator imageData];
  217. dbImage.data = avatar;
  218. conversation.groupImage = dbImage;
  219. }
  220. conversation.groupName = [self localizedStringForKey:@"name" in:groupData];
  221. conversation.groupMyIdentity = [MyIdentityStore sharedMyIdentityStore].identity;
  222. NSArray *memberNames = [groupData objectForKey:@"members"];
  223. NSSet *members = [self groupMembersFrom:memberNames];
  224. [conversation addMembers:members];
  225. return conversation;
  226. }
  227. - (NSSet *)groupMembersFrom:(NSArray *)memberNames {
  228. NSMutableSet *members = [NSMutableSet setWithCapacity:memberNames.count];
  229. for (NSString *name in memberNames) {
  230. Contact *contact = [_entityManager.entityFetcher contactForId:name];
  231. [members addObject:contact];
  232. }
  233. return members;
  234. }
  235. - (void)handleGroups:(NSDictionary *)groupsData {
  236. for (NSDictionary *groupData in groupsData) {
  237. Conversation *conversation = [self createGroupConversationFromData:groupData];
  238. NSArray *conversationData = [groupData objectForKey:@"conversation"];
  239. if (conversationData) {
  240. [self handleConversation:conversation data:conversationData];
  241. }
  242. }
  243. }
  244. - (BaseMessage *)handleTextMessage:(NSDictionary *)messageData inConversation:(Conversation *)conversation {
  245. TextMessage *message = [_entityManager.entityCreator textMessageForConversation:conversation];
  246. message.text = [self localizedStringForKey:@"content" in:messageData];
  247. return message;
  248. }
  249. - (BaseMessage *)handleImageMessage:(NSDictionary *)messageData inConversation:(Conversation *)conversation {
  250. ImageMessage *message = [_entityManager.entityCreator imageMessageForConversation:conversation];
  251. ImageData *imageData = [_entityManager.entityCreator imageData];
  252. NSDictionary *captions = messageData[@"caption"];
  253. NSString *caption = nil;
  254. if (captions) {
  255. caption = [captions objectForKey:_languageCode];
  256. if (caption == nil) {
  257. caption = [captions objectForKey:@"default"];
  258. }
  259. }
  260. [imageData setCaption:caption];
  261. NSData *data = [self localizedImageForKey:@"content" in:messageData];
  262. if (data) {
  263. imageData.data = data;
  264. [imageData setCaption:caption];
  265. UIImage *image = [UIImage imageWithData:data];
  266. UIImage *thumbnail = [MediaConverter getThumbnailForImage:image];
  267. NSData *thumbnailData = UIImageJPEGRepresentation(thumbnail, kJPEGCompressionQuality);
  268. ImageData *dbThumbnail = [_entityManager.entityCreator imageData];
  269. dbThumbnail.data = thumbnailData;
  270. dbThumbnail.width = [NSNumber numberWithInt:thumbnail.size.width];
  271. dbThumbnail.height = [NSNumber numberWithInt:thumbnail.size.height];
  272. [imageData setCaption:caption];
  273. message.thumbnail = dbThumbnail;
  274. } else {
  275. imageData.data = [NSData data];
  276. }
  277. message.image = imageData;
  278. return message;
  279. }
  280. - (BaseMessage *)handleAudioMessage:(NSDictionary *)messageData inConversation:(Conversation *)conversation {
  281. AudioMessage *message = [_entityManager.entityCreator audioMessageForConversation:conversation];
  282. message.duration = [NSNumber numberWithInt:42];
  283. return message;
  284. }
  285. - (BaseMessage *)handleLocationMessage:(NSDictionary *)messageData inConversation:(Conversation *)conversation {
  286. LocationMessage *message = [_entityManager.entityCreator locationMessageForConversation:conversation];
  287. NSArray *location = [self localizedArrayForKey:@"content" in:messageData];
  288. if (location.count == 4) {
  289. message.latitude = location[0];
  290. message.longitude = location[1];
  291. message.accuracy = location[2];
  292. message.poiName = location[3];
  293. }
  294. return message;
  295. }
  296. - (BaseMessage *)handleFileMessage:(NSDictionary *)messageData inConversation:(Conversation *)conversation {
  297. FileMessage *message = [_entityManager.entityCreator fileMessageForConversation:conversation];
  298. message.fileName = [self localizedStringForKey:@"content" in:messageData];
  299. message.fileSize = [NSNumber numberWithInt:2308565];
  300. return message;
  301. }
  302. - (BaseMessage *)handleCallMessage:(NSDictionary *)messageData inConversation:(Conversation *)conversation {
  303. SystemMessage *message = [_entityManager.entityCreator systemMessageForConversation:conversation];
  304. BOOL outgoing = ((NSNumber *)[messageData objectForKey:@"out"]).boolValue;
  305. NSDictionary *callData = [messageData objectForKey:@"content"];
  306. NSString *type = [callData objectForKey:@"type"];
  307. if ([type isEqualToString:@"answered"]) {
  308. int duration = ((NSNumber *)[callData objectForKey:@"duration"]).intValue;
  309. NSString *durationString = [self timeFormatted:duration];
  310. NSDictionary *argDict = @{@"DateString": [DateFormatter shortStyleTimeNoDate:[NSDate date]], @"CallTime": durationString, @"CallInitiator": [NSNumber numberWithBool:outgoing]};
  311. NSError *error;
  312. NSData *data = [NSJSONSerialization dataWithJSONObject:argDict options:NSJSONWritingPrettyPrinted error:&error];
  313. message.arg = data;
  314. message.type = [NSNumber numberWithInteger:kSystemMessageCallEnded];
  315. conversation.lastMessage = message;
  316. }
  317. return message;
  318. }
  319. - (BaseMessage *)handleBallotMessage:(NSDictionary *)messageData inConversation:(Conversation *)conversation {
  320. BallotMessage *message = [_entityManager.entityCreator ballotMessageForConversation:conversation];
  321. NSDictionary *ballotData = [messageData objectForKey:@"content"];
  322. Ballot *ballot = [_entityManager.entityCreator ballot];
  323. NSData *idData = [NSData data];
  324. ballot.id = idData;
  325. ballot.title = [self localizedStringForKey:@"question" in:ballotData];
  326. ballot.conversation = conversation;
  327. if ([[ballotData objectForKey:@"state"] isEqualToString:@"closed"]) {
  328. [ballot setClosed];
  329. }
  330. NSArray *choices = [ballotData objectForKey:@"choices"];
  331. [self handleBallot:ballot choices:choices];
  332. message.ballot = ballot;
  333. return message;
  334. }
  335. - (void)handleBallot:(Ballot *)ballot choices:(NSArray *)choices {
  336. NSInteger count = 0;
  337. for (NSDictionary *choiceData in choices) {
  338. BallotChoice *ballotChoice = [_entityManager.entityCreator ballotChoice];
  339. ballotChoice.name = [self localizedStringForKey:@"choice" in:choiceData];
  340. ballotChoice.id = [NSNumber numberWithInteger:count];
  341. ballotChoice.orderPosition = [NSNumber numberWithInteger:count];
  342. NSDictionary *votes = [choiceData objectForKey:@"votes"];
  343. [self handleBallotChoice:ballotChoice voteData:votes];
  344. [ballot addChoicesObject:ballotChoice];
  345. ++count;
  346. }
  347. }
  348. - (void)handleBallotChoice:(BallotChoice *)choice voteData:(NSDictionary *)voteData {
  349. NSEnumerator *enumerator = [voteData keyEnumerator];
  350. NSString *identity;
  351. while ((identity = [enumerator nextObject])) {
  352. BallotResult *ballotResult = [_entityManager.entityCreator ballotResult];
  353. ballotResult.value = [voteData objectForKey:identity];
  354. if ([identity isEqualToString:@"$"]) {
  355. ballotResult.participantId = _myIdentity;
  356. } else {
  357. ballotResult.participantId = identity;
  358. }
  359. [choice addResultObject:ballotResult];
  360. }
  361. }
  362. - (id)localizedObjectForKey:(NSString *)key in:(NSDictionary *)dictionary {
  363. NSDictionary *localizations = [dictionary objectForKey:key];
  364. id result = [localizations objectForKey:_languageCode];
  365. if (result == nil) {
  366. result = [localizations objectForKey:@"default"];
  367. }
  368. return result;
  369. }
  370. - (NSString *)localizedStringForKey:(NSString *)key in:(NSDictionary *)dictionary {
  371. return (NSString *)[self localizedObjectForKey:key in:dictionary];
  372. }
  373. - (NSData *)localizedImageForKey:(NSString *)key in:(NSDictionary *)dictionary {
  374. NSString *imageName = [self localizedObjectForKey:key in:dictionary];
  375. return [self imageDataFromFileNamed:imageName];
  376. }
  377. - (NSArray *)localizedArrayForKey:(NSString *)key in:(NSDictionary *)dictionary {
  378. return (NSArray *)[self localizedObjectForKey:key in:dictionary];
  379. }
  380. - (NSData *)imageDataFromFileNamed:(NSString *)imageName {
  381. NSString *path = [_bundle pathForResource:imageName ofType:nil];
  382. NSData *data = [NSData dataWithContentsOfFile:path];
  383. return data;
  384. }
  385. - (NSString *)timeFormatted:(int)totalSeconds {
  386. int seconds = totalSeconds % 60;
  387. int minutes = (totalSeconds / 60);
  388. return [NSString stringWithFormat:@"%02d:%02d", minutes, seconds];
  389. }
  390. @end