MessageSender.m 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2014-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 "MessageSender.h"
  21. #import "Conversation.h"
  22. #import "BoxTextMessage.h"
  23. #import "BoxLocationMessage.h"
  24. #import "DeliveryReceiptMessage.h"
  25. #import "TypingIndicatorMessage.h"
  26. #import "GroupCreateMessage.h"
  27. #import "GroupLeaveMessage.h"
  28. #import "GroupRenameMessage.h"
  29. #import "GroupTextMessage.h"
  30. #import "GroupLocationMessage.h"
  31. #import "GroupRequestSyncMessage.h"
  32. #import "Contact.h"
  33. #import "ContactStore.h"
  34. #import "TextMessage.h"
  35. #import "LocationMessage.h"
  36. #import "MessageQueue.h"
  37. #import "SystemMessage.h"
  38. #import "ThreemaError.h"
  39. #import "UserSettings.h"
  40. #import "MyIdentityStore.h"
  41. #import "DatabaseManager.h"
  42. #import "EntityManager.h"
  43. #import "NSString+Hex.h"
  44. #import "BallotMessageEncoder.h"
  45. #import "Ballot.h"
  46. #import "GroupBallotCreateMessage.h"
  47. #import "GroupBallotVoteMessage.h"
  48. #import "Utils.h"
  49. #import "BundleUtil.h"
  50. #import "ContactSetPhotoMessage.h"
  51. #import "ContactPhotoSender.h"
  52. #import "BackgroundTaskManagerProxy.h"
  53. #import "PinnedHTTPSURLLoader.h"
  54. #import "QuoteParser.h"
  55. #ifdef DEBUG
  56. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  57. #else
  58. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  59. #endif
  60. @implementation MessageSender
  61. + (void)sendDeliveryReceiptForMessage:(BaseMessage*)message fromIdentity:(NSString*)identity {
  62. DDLogVerbose(@"Sending delivery receipt for message ID: %@", message.id);
  63. DeliveryReceiptMessage *deliveryReceipt = [[DeliveryReceiptMessage alloc] init];
  64. deliveryReceipt.receiptType = DELIVERYRECEIPT_MSGRECEIVED;
  65. deliveryReceipt.receiptMessageIds = @[message.id];
  66. deliveryReceipt.toIdentity = identity;
  67. [[MessageQueue sharedMessageQueue] enqueue:deliveryReceipt];
  68. }
  69. + (void)sendDeliveryReceiptForAbstractMessage:(AbstractMessage*)message fromIdentity:(NSString*)identity {
  70. NSString *messageId = [NSString stringWithHexData:message.messageId];
  71. DDLogVerbose(@"Sending delivery receipt for notification extension message ID: %@", messageId);
  72. DeliveryReceiptMessage *deliveryReceipt = [[DeliveryReceiptMessage alloc] init];
  73. deliveryReceipt.receiptType = DELIVERYRECEIPT_MSGRECEIVED;
  74. deliveryReceipt.receiptMessageIds = @[message.messageId];
  75. deliveryReceipt.toIdentity = identity;
  76. [[MessageQueue sharedMessageQueue] enqueueWait:deliveryReceipt];
  77. }
  78. + (void)sendGroupCreateMessageForGroup:(GroupProxy*)group toMember:(Contact*)toMember {
  79. NSArray *groupMembers = nil;
  80. if ([group.activeMemberIds containsObject:toMember.identity]) {
  81. groupMembers = [group.activeMemberIds allObjects];
  82. } else {
  83. groupMembers = @[group.conversation.groupMyIdentity];
  84. }
  85. GroupCreateMessage *createMessage = [[GroupCreateMessage alloc] init];
  86. createMessage.toIdentity = toMember.identity;
  87. createMessage.groupId = group.groupId;
  88. createMessage.groupMembers = groupMembers;
  89. [[MessageQueue sharedMessageQueue] enqueue:createMessage];
  90. }
  91. + (void)sendGroupSharedMessagesForConversation:(Conversation*)groupConversation toMember:(Contact *)newMember {
  92. // send own open ballots to all members
  93. for (Ballot *ballot in groupConversation.ballots) {
  94. if (ballot.isOwn && [ballot isClosed] == NO) {
  95. [self sendCreateMessageForBallot:ballot toContact:newMember];
  96. if (ballot.isIntermediate) {
  97. [self sendBallotVoteMessage:ballot toContact:newMember];
  98. }
  99. }
  100. }
  101. }
  102. + (void)sendGroupRenameMessageForConversation:(Conversation*)conversation addSystemMessage:(BOOL)addSystemMessage {
  103. if (conversation.groupName.length == 0) {
  104. return;
  105. }
  106. /* send rename message to all members */
  107. for (Contact *member in conversation.members) {
  108. [self sendGroupRenameMessageForConversation:conversation toMember:member addSystemMessage:addSystemMessage];
  109. }
  110. if (addSystemMessage) {
  111. EntityManager *entityManager = [[EntityManager alloc] init];
  112. [entityManager performSyncBlockAndSafe:^{
  113. Conversation *conversationOwnContext = (Conversation *)[entityManager.entityFetcher getManagedObjectById:conversation.objectID];
  114. /* Insert system message to document this change */
  115. SystemMessage *systemMessage = [entityManager.entityCreator systemMessageForConversation:conversationOwnContext];
  116. systemMessage.type = [NSNumber numberWithInt:kSystemMessageRenameGroup];
  117. systemMessage.arg = [conversationOwnContext.groupName dataUsingEncoding:NSUTF8StringEncoding];
  118. }];
  119. }
  120. }
  121. + (void)sendGroupRenameMessageForConversation:(Conversation *)groupConversation toMember:(Contact*)member addSystemMessage:(BOOL)addSystemMessage {
  122. if (groupConversation.groupName.length == 0)
  123. return;
  124. GroupRenameMessage *renameMessage = [[GroupRenameMessage alloc] init];
  125. renameMessage.toIdentity = member.identity;
  126. renameMessage.groupId = groupConversation.groupId;
  127. renameMessage.name = groupConversation.groupName;
  128. [[MessageQueue sharedMessageQueue] enqueue:renameMessage];
  129. }
  130. + (void)sendGroupLeaveMessageForConversation:(Conversation*)groupConversation {
  131. NSString *creator;
  132. if (groupConversation.contact == nil)
  133. creator = [MyIdentityStore sharedMyIdentityStore].identity;
  134. else
  135. creator = groupConversation.contact.identity;
  136. /* send leave message to all members */
  137. for (Contact *member in groupConversation.members) {
  138. [self sendGroupLeaveMessageForCreator:creator groupId:groupConversation.groupId toIdentity:member.identity];
  139. }
  140. }
  141. + (void)sendGroupLeaveMessageForCreator:(NSString*)creator groupId:(NSData*)groupId toIdentity:(NSString*)toIdentity {
  142. GroupLeaveMessage *leaveMessage = [[GroupLeaveMessage alloc] init];
  143. leaveMessage.toIdentity = toIdentity;
  144. leaveMessage.groupCreator = creator;
  145. leaveMessage.groupId = groupId;
  146. [[MessageQueue sharedMessageQueue] enqueue:leaveMessage];
  147. }
  148. + (void)sendGroupRequestSyncMessageForCreatorContact:(Contact*)creatorContact groupId:(NSData*)groupId {
  149. GroupRequestSyncMessage *requestSyncMessage = [[GroupRequestSyncMessage alloc] init];
  150. requestSyncMessage.toIdentity = creatorContact.identity;
  151. requestSyncMessage.groupId = groupId;
  152. [[MessageQueue sharedMessageQueue] enqueue:requestSyncMessage];
  153. NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
  154. creatorContact, kKeyContact,
  155. nil];
  156. EntityManager *entityManager = [[EntityManager alloc] init];
  157. Group *group = [entityManager.entityFetcher groupForGroupId:groupId groupCreator:creatorContact.identity];
  158. if (group == nil) {
  159. if(![[UserSettings sharedUserSettings].blacklist containsObject:creatorContact.identity]) {
  160. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationErrorUnknownGroup object:nil userInfo:info];
  161. }
  162. }
  163. }
  164. + (void)sendMessage:(NSString*)message inConversation:(Conversation*)conversation async:(BOOL)async quickReply:(BOOL)quickReply requestId:(NSString *)requestId onCompletion:(void(^)(TextMessage *message, Conversation *conv))onCompletion {
  165. __block Conversation *conversationOwnContext;
  166. __block TextMessage *newMessage;
  167. __block NSString *remainingBody;
  168. NSData *quoteMessageId = [QuoteParser parseQuoteV2FromMessage:message remainingBody:&remainingBody];
  169. EntityManager *entityManager = [[EntityManager alloc] init];
  170. [entityManager performSyncBlockAndSafe:^{
  171. conversationOwnContext = (Conversation *)[entityManager.entityFetcher getManagedObjectById:conversation.objectID];
  172. newMessage = [entityManager.entityCreator textMessageForConversation:conversationOwnContext];
  173. newMessage.text = [[UserSettings sharedUserSettings] quoteV2Active] ? remainingBody : message;
  174. if (quoteMessageId != nil) {
  175. newMessage.quotedMessageId = quoteMessageId;
  176. }
  177. if (requestId != nil) {
  178. newMessage.webRequestId = requestId;
  179. }
  180. }];
  181. if (conversationOwnContext.groupId != nil) {
  182. /* send to each group member */
  183. for (Contact *member in conversationOwnContext.members) {
  184. DDLogVerbose(@"Sending group message to %@", member.identity);
  185. GroupTextMessage *msg = [[GroupTextMessage alloc] init];
  186. msg.messageId = newMessage.id;
  187. msg.date = newMessage.date;
  188. msg.text = message;
  189. msg.groupId = conversationOwnContext.groupId;
  190. if (conversationOwnContext.contact == nil)
  191. msg.groupCreator = [MyIdentityStore sharedMyIdentityStore].identity;
  192. else
  193. msg.groupCreator = conversationOwnContext.contact.identity;
  194. msg.toIdentity = member.identity;
  195. if (async) {
  196. [[MessageQueue sharedMessageQueue] enqueue:msg];
  197. } else {
  198. [[MessageQueue sharedMessageQueue] enqueueWait:msg];
  199. }
  200. if (!quickReply) {
  201. [ContactPhotoSender sendProfilePicture:msg];
  202. }
  203. }
  204. } else {
  205. BoxTextMessage *msg = [[BoxTextMessage alloc] init];
  206. msg.messageId = newMessage.id;
  207. msg.date = newMessage.date;
  208. msg.text = message;
  209. msg.toIdentity = conversationOwnContext.contact.identity;
  210. if (quickReply) {
  211. [[MessageQueue sharedMessageQueue] enqueueWaitForQuickReply:msg];
  212. } else {
  213. if (async) {
  214. [[MessageQueue sharedMessageQueue] enqueue:msg];
  215. } else {
  216. [[MessageQueue sharedMessageQueue] enqueueWait:msg];
  217. }
  218. if (!quickReply) {
  219. [ContactPhotoSender sendProfilePicture:msg];
  220. }
  221. }
  222. }
  223. onCompletion(newMessage, conversationOwnContext);
  224. }
  225. + (void)sendLocation:(CLLocationCoordinate2D)coordinates accuracy:(CLLocationAccuracy)accuracy poiName:(NSString*)poiName poiAddress:(NSString*)poiAddress inConversation:(Conversation*)conversation onCompletion:(void(^)(NSData *messageId))onCompletion {
  226. __block Conversation *conversationOwnContext;
  227. __block LocationMessage *newMessage;
  228. EntityManager *entityManager = [[EntityManager alloc] init];
  229. [entityManager performSyncBlockAndSafe:^{
  230. conversationOwnContext = (Conversation *)[entityManager.entityFetcher getManagedObjectById:conversation.objectID];
  231. newMessage = [entityManager.entityCreator locationMessageForConversation:conversationOwnContext];
  232. newMessage.latitude = [NSNumber numberWithDouble:coordinates.latitude];
  233. newMessage.longitude = [NSNumber numberWithDouble:coordinates.longitude];
  234. newMessage.accuracy = [NSNumber numberWithDouble:accuracy];
  235. newMessage.poiName = poiName;
  236. if (poiAddress != nil) {
  237. newMessage.poiName = [NSString stringWithFormat:@"%@\n%@", poiName, poiAddress];
  238. } else {
  239. newMessage.poiName = poiName;
  240. }
  241. }];
  242. if (conversationOwnContext.groupId != nil) {
  243. /* send to each group member */
  244. for (Contact *member in conversationOwnContext.members) {
  245. DDLogVerbose(@"Sending group location message to %@", member.identity);
  246. GroupLocationMessage *msg = [[GroupLocationMessage alloc] init];
  247. msg.messageId = newMessage.id;
  248. msg.date = newMessage.date;
  249. msg.latitude = coordinates.latitude;
  250. msg.longitude = coordinates.longitude;
  251. msg.accuracy = accuracy;
  252. msg.poiName = poiName;
  253. msg.poiAddress = poiAddress;
  254. msg.groupId = conversationOwnContext.groupId;
  255. if (conversationOwnContext.contact == nil)
  256. msg.groupCreator = [MyIdentityStore sharedMyIdentityStore].identity;
  257. else
  258. msg.groupCreator = conversationOwnContext.contact.identity;
  259. msg.toIdentity = member.identity;
  260. [[MessageQueue sharedMessageQueue] enqueue:msg];
  261. [ContactPhotoSender sendProfilePicture:msg];
  262. }
  263. } else {
  264. BoxLocationMessage *msg = [[BoxLocationMessage alloc] init];
  265. msg.messageId = newMessage.id;
  266. msg.date = newMessage.date;
  267. msg.latitude = coordinates.latitude;
  268. msg.longitude = coordinates.longitude;
  269. msg.accuracy = accuracy;
  270. msg.poiName = poiName;
  271. msg.poiAddress = poiAddress;
  272. msg.toIdentity = conversationOwnContext.contact.identity;
  273. [[MessageQueue sharedMessageQueue] enqueue:msg];
  274. [ContactPhotoSender sendProfilePicture:msg];
  275. }
  276. if (poiName == nil) {
  277. [Utils reverseGeocodeNearLatitude:coordinates.latitude longitude:coordinates.longitude accuracy:accuracy completion:^(NSString *label) {
  278. if ([newMessage wasDeleted]) {
  279. return;
  280. }
  281. [entityManager performSyncBlockAndSafe:^{
  282. newMessage.reverseGeocodingResult = label;
  283. }];
  284. } onError:^(NSError *error) {
  285. DDLogWarn(@"Reverse geocoding failed: %@", error);
  286. if ([newMessage wasDeleted]) {
  287. return;
  288. }
  289. [entityManager performSyncBlockAndSafe:^{
  290. newMessage.reverseGeocodingResult = NSLocalizedString(@"unknown_location", nil);
  291. }];
  292. }];
  293. }
  294. onCompletion(newMessage.id);
  295. }
  296. + (void)markMessageAsSent:(NSData*)messageId {
  297. /* Fetch message from DB */
  298. EntityManager *entityManager = [[EntityManager alloc] init];
  299. [entityManager performAsyncBlockAndSafe:^{
  300. BaseMessage *dbmsg = [entityManager.entityFetcher ownMessageWithId: messageId];
  301. if (dbmsg == nil) {
  302. /* This can happen normally, e.g. for outgoing delivery receipts that we don't store in the DB */
  303. return;
  304. }
  305. if (dbmsg.sent == nil || dbmsg.sent.boolValue == NO) {
  306. dbmsg.sent = [NSNumber numberWithBool:YES];
  307. }
  308. }];
  309. }
  310. + (void)sendReadReceiptForMessages:(NSArray*)messages toIdentity:(NSString*)identity async:(BOOL)async quickReply:(BOOL)quickReply {
  311. if (![UserSettings sharedUserSettings].sendReadReceipts)
  312. return;
  313. [self sendReceiptForMessages:messages toIdentity:identity receiptType:DELIVERYRECEIPT_MSGREAD async:async quickReply:quickReply];
  314. }
  315. + (void)sendUserAckForMessages:(NSArray*)messages toIdentity:(NSString*)identity async:(BOOL)async quickReply:(BOOL)quickReply {
  316. [self sendReceiptForMessages:messages toIdentity:identity receiptType:DELIVERYRECEIPT_MSGUSERACK async:async quickReply:quickReply];
  317. }
  318. + (void)sendUserDeclineForMessages:(NSArray*)messages toIdentity:(NSString*)identity async:(BOOL)async quickReply:(BOOL)quickReply {
  319. [self sendReceiptForMessages:messages toIdentity:identity receiptType:DELIVERYRECEIPT_MSGUSERDECLINE async:async quickReply:quickReply];
  320. }
  321. + (void)sendReceiptForMessages:(NSArray*)messages toIdentity:(NSString*)identity receiptType:(uint8_t)receiptType async:(BOOL)async quickReply:(BOOL)quickReply {
  322. NSMutableArray *receiptMessageIds = [NSMutableArray arrayWithCapacity:messages.count];
  323. for (BaseMessage *message in messages) {
  324. @try {
  325. if (![identity isEqualToString:message.conversation.contact.identity]) {
  326. DDLogError(@"Bad from identity encountered while sending read receipt");
  327. return;
  328. }
  329. [receiptMessageIds addObject:message.id];
  330. }
  331. @catch (NSException *exception) {
  332. DDLogError(@"Exception while marking message as read: %@", exception);
  333. }
  334. }
  335. DDLogVerbose(@"Sending read receipt for message IDs: %@", receiptMessageIds);
  336. DeliveryReceiptMessage *deliveryReceipt = [[DeliveryReceiptMessage alloc] init];
  337. deliveryReceipt.receiptType = receiptType;
  338. deliveryReceipt.receiptMessageIds = receiptMessageIds;
  339. deliveryReceipt.toIdentity = identity;
  340. if (quickReply) {
  341. [[MessageQueue sharedMessageQueue] enqueueWaitForQuickReply:deliveryReceipt];
  342. } else {
  343. if (async) {
  344. [[MessageQueue sharedMessageQueue] enqueue:deliveryReceipt];
  345. } else {
  346. [[MessageQueue sharedMessageQueue] enqueueWait:deliveryReceipt];
  347. }
  348. }
  349. }
  350. + (void)sendTypingIndicatorMessage:(BOOL)typing toIdentity:(NSString*)identity {
  351. if (![UserSettings sharedUserSettings].sendTypingIndicator) {
  352. return;
  353. }
  354. DDLogVerbose(@"Sending typing indicator %@ to %@", typing ? @"on" : @"off", identity);
  355. TypingIndicatorMessage *typingIndicatorMessage = [[TypingIndicatorMessage alloc] init];
  356. typingIndicatorMessage.typing = typing;
  357. typingIndicatorMessage.toIdentity = identity;
  358. [[MessageQueue sharedMessageQueue] enqueue:typingIndicatorMessage];
  359. }
  360. + (void)markBlobAsDone:(NSData *)blobId {
  361. NSString *blobIdHex = [NSString stringWithHexData:blobId];
  362. NSString *blobFirstByteHex = [blobIdHex substringWithRange:NSMakeRange(0, 2)];
  363. NSURL *blobUrl;
  364. if ([UserSettings sharedUserSettings].enableIPv6) {
  365. blobUrl = [NSURL URLWithString:[NSString stringWithFormat:[BundleUtil objectForInfoDictionaryKey:@"ThreemaBlobDoneURLv6"], blobFirstByteHex, blobIdHex]];
  366. } else {
  367. blobUrl = [NSURL URLWithString:[NSString stringWithFormat:[BundleUtil objectForInfoDictionaryKey:@"ThreemaBlobDoneURL"], blobFirstByteHex, blobIdHex]];
  368. }
  369. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:blobUrl cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:kBlobLoadTimeout];
  370. request.HTTPMethod = @"POST";
  371. PinnedHTTPSURLLoader *loader = [[PinnedHTTPSURLLoader alloc] init];
  372. [loader startWithURLRequest:request onCompletion:^(NSData *data) {
  373. DDLogInfo(@"Blob ID %@ marked as done", blobIdHex);
  374. } onError:^(NSError *error) {
  375. DDLogWarn(@"Error marking blob ID %@ as done: %@", blobIdHex, error);
  376. }];
  377. }
  378. + (void)sendCreateMessageForBallot:(Ballot *)ballot toContact:(Contact *)contact {
  379. EntityManager *entityManager = [[EntityManager alloc] init];
  380. Ballot *ballotOwnContext = (Ballot *)[entityManager.entityFetcher getManagedObjectById:ballot.objectID];
  381. Conversation *conversation = ballotOwnContext.conversation;
  382. BoxBallotCreateMessage *boxMessage = [BallotMessageEncoder encodeCreateMessageForBallot: ballot];
  383. boxMessage.messageId = [AbstractMessage randomMessageId];
  384. GroupBallotCreateMessage *msg = [BallotMessageEncoder groupBallotCreateMessageFrom:boxMessage forConversation:conversation];
  385. msg.toIdentity = contact.identity;
  386. [[MessageQueue sharedMessageQueue] enqueue:msg];
  387. }
  388. + (void)sendCreateMessageForBallot:(Ballot *)ballot {
  389. __block Ballot *ballotOwnContext;
  390. __block Conversation *conversation;
  391. __block BallotMessage *message;
  392. EntityManager *entityManager = [[EntityManager alloc] init];
  393. [entityManager performSyncBlockAndSafe:^{
  394. ballotOwnContext = (Ballot *)[entityManager.entityFetcher getManagedObjectById:ballot.objectID];
  395. conversation = ballotOwnContext.conversation;
  396. message = [entityManager.entityCreator ballotMessageForConversation:conversation];
  397. message.ballot = ballotOwnContext;
  398. message.conversation.lastMessage = message;
  399. }];
  400. BoxBallotCreateMessage *boxMessage = [BallotMessageEncoder encodeCreateMessageForBallot: ballot];
  401. boxMessage.messageId = message.id;
  402. if (conversation.groupId != nil) {
  403. /* send to each group member */
  404. for (Contact *member in conversation.members) {
  405. DDLogVerbose(@"Sending ballot create message to %@", member.identity);
  406. GroupBallotCreateMessage *msg = [BallotMessageEncoder groupBallotCreateMessageFrom:boxMessage forConversation:conversation];
  407. msg.toIdentity = member.identity;
  408. [[MessageQueue sharedMessageQueue] enqueue:msg];
  409. }
  410. } else {
  411. boxMessage.toIdentity = conversation.contact.identity;
  412. [[MessageQueue sharedMessageQueue] enqueue:boxMessage];
  413. }
  414. }
  415. + (void)sendBallotVoteMessage:(Ballot *)ballot toContact:(Contact *) contact {
  416. EntityManager *entityManager = [[EntityManager alloc] init];
  417. Ballot *ballotOwnContext = (Ballot *)[entityManager.entityFetcher getManagedObjectById:ballot.objectID];
  418. Conversation *conversation = ballotOwnContext.conversation;
  419. BoxBallotVoteMessage *boxMessage = [BallotMessageEncoder encodeVoteMessageForBallot: ballot];
  420. boxMessage.messageId = [AbstractMessage randomMessageId];
  421. GroupBallotVoteMessage *msg = [BallotMessageEncoder groupBallotVoteMessageFrom:boxMessage forConversation:conversation];
  422. msg.toIdentity = contact.identity;
  423. [[MessageQueue sharedMessageQueue] enqueue:msg];
  424. }
  425. + (void)sendBallotVoteMessage:(Ballot *)ballot {
  426. Conversation *conversation = ballot.conversation;
  427. BoxBallotVoteMessage *boxMessage = [BallotMessageEncoder encodeVoteMessageForBallot: ballot];
  428. if (conversation.groupId != nil) {
  429. /* send to each group member */
  430. for (Contact *member in conversation.members) {
  431. DDLogVerbose(@"Sending ballot vote message to %@", member.identity);
  432. GroupBallotVoteMessage *msg = [BallotMessageEncoder groupBallotVoteMessageFrom:boxMessage forConversation:conversation];
  433. msg.toIdentity = member.identity;
  434. [[MessageQueue sharedMessageQueue] enqueue:msg];
  435. }
  436. } else {
  437. boxMessage.toIdentity = conversation.contact.identity;
  438. [[MessageQueue sharedMessageQueue] enqueue:boxMessage];
  439. }
  440. }
  441. @end