#import "TGModernSendCommonMessageActor.h" #import "ActionStage.h" #import "SGraphObjectNode.h" #import "TGPeerIdAdapter.h" #import "TGPreparedTextMessage.h" #import "TGPreparedMapMessage.h" #import "TGPreparedLocalImageMessage.h" #import "TGPreparedRemoteImageMessage.h" #import "TGPreparedLocalVideoMessage.h" #import "TGPreparedRemoteVideoMessage.h" #import "TGPreparedForwardedMessage.h" #import "TGPreparedContactMessage.h" #import "TGPreparedLocalDocumentMessage.h" #import "TGPreparedRemoteDocumentMessage.h" #import "TGPreparedDownloadImageMessage.h" #import "TGPreparedDownloadDocumentMessage.h" #import "TGPreparedCloudDocumentMessage.h" #import "TGPreparedDownloadExternalGifMessage.h" #import "TGPreparedDownloadExternalImageMessage.h" #import "TGPreparedAssetImageMessage.h" #import "TGPreparedAssetVideoMessage.h" #import "TGLinkPreviewsContentProperty.h" #import "TGTelegraph.h" #import "TGTelegramNetworking.h" #import "TGDatabase.h" #import "TGRemoteImageView.h" #import "TGImageDownloadActor.h" #import "TGVideoDownloadActor.h" #import "TGMediaAssetsLibrary.h" #import "TGMediaAssetImageSignals.h" #import "TGVideoConverter.h" #import "TGVideoEditAdjustments.h" #import "TGImageUtils.h" #import "TGStringUtils.h" #import "TGFileUtils.h" #import "TGMessage+Telegraph.h" #import "TGMediaStoreContext.h" #import "PSLMDBKeyValueStore.h" #import "TLMessage$modernMessage.h" #import "TLMessage$modernMessageService.h" #import "TLUpdates$updateShortSentMessage.h" #import "TLUpdates+TG.h" #import "UIImage+TG.h" #import #import "TGAppDelegate.h" #import "TGChannelManagementSignals.h" #import "TGAlertView.h" #import "TGWebpageSignals.h" #import "TGBotContextResultAttachment.h" #import "TGRecentGifsSignal.h" #import #import "TGDocumentMediaAttachment+Telegraph.h" #import "TLDocumentAttribute$documentAttributeAudio.h" @interface TGModernSendCommonMessageActor () { int64_t _conversationId; int64_t _accessHash; bool _postAsChannel; bool _notifyMembers; bool _shouldPostAlmostDeliveredMessage; } @end @implementation TGModernSendCommonMessageActor + (PSLMDBKeyValueStore *)uploadedMediaStore { static PSLMDBKeyValueStore *store = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { NSString *documentsPath = [TGAppDelegate documentsPath]; store = [PSLMDBKeyValueStore storeWithPath:[documentsPath stringByAppendingPathComponent:@"misc/remotefiles"] size:4 * 1024 * 1024]; }); return store; } + (TGDocumentMediaAttachment *)remoteDocumentByGiphyId:(NSString *)giphyId { if (giphyId.length == 0) return nil; __block NSData *documentData = nil; [[self uploadedMediaStore] readInTransaction:^(id reader) { NSMutableData *keyData = [[NSMutableData alloc] init]; int8_t keyspace = 0; [keyData appendBytes:&keyspace length:1]; [keyData appendData:[giphyId dataUsingEncoding:NSUTF8StringEncoding]]; PSData key = {.data = (uint8_t *)keyData.bytes, .length = keyData.length}; PSData value; if ([reader readValueForRawKey:&key value:&value]) documentData = [[NSData alloc] initWithBytes:value.data length:value.length]; }]; if (documentData != nil) { id attachment = [TGMessage parseMediaAttachments:documentData].firstObject; if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) return attachment; } return nil; } + (void)setRemoteDocumentForGiphyId:(NSString *)giphyId document:(TGDocumentMediaAttachment *)document { if (giphyId.length == 0 || document == nil) return; NSData *documentData = [TGMessage serializeMediaAttachments:true attachments:@[document]]; if (documentData != nil) { [[self uploadedMediaStore] readWriteInTransaction:^(id writer) { NSMutableData *keyData = [[NSMutableData alloc] init]; int8_t keyspace = 0; [keyData appendBytes:&keyspace length:1]; [keyData appendData:[giphyId dataUsingEncoding:NSUTF8StringEncoding]]; PSData key = {.data = (uint8_t *)keyData.bytes, .length = keyData.length}; PSData value = {.data = (uint8_t *)documentData.bytes, .length = documentData.length}; [writer writeValueForRawKey:key.data keyLength:key.length value:value.data valueLength:value.length]; }]; } } + (TGImageMediaAttachment *)remoteImageByRemoteUrl:(NSString *)url { if (url.length == 0) return nil; __block NSData *imageData = nil; [[self uploadedMediaStore] readInTransaction:^(id reader) { NSMutableData *keyData = [[NSMutableData alloc] init]; int8_t keyspace = 1; [keyData appendBytes:&keyspace length:1]; [keyData appendData:[url dataUsingEncoding:NSUTF8StringEncoding]]; PSData key = {.data = (uint8_t *)keyData.bytes, .length = keyData.length}; PSData value; if ([reader readValueForRawKey:&key value:&value]) imageData = [[NSData alloc] initWithBytes:value.data length:value.length]; }]; if (imageData != nil) { id attachment = [TGMessage parseMediaAttachments:imageData].firstObject; if ([attachment isKindOfClass:[TGImageMediaAttachment class]]) return attachment; } return nil; } + (void)setRemoteImageForRemoteUrl:(NSString *)url image:(TGImageMediaAttachment *)image { if (url.length == 0 || image == nil) return; NSData *imageData = [TGMessage serializeMediaAttachments:true attachments:@[image]]; if (imageData != nil) { [[self uploadedMediaStore] readWriteInTransaction:^(id writer) { NSMutableData *keyData = [[NSMutableData alloc] init]; int8_t keyspace = 1; [keyData appendBytes:&keyspace length:1]; [keyData appendData:[url dataUsingEncoding:NSUTF8StringEncoding]]; PSData key = {.data = (uint8_t *)keyData.bytes, .length = keyData.length}; PSData value = {.data = (uint8_t *)imageData.bytes, .length = imageData.length}; [writer writeValueForRawKey:key.data keyLength:key.length value:value.data valueLength:value.length]; }]; } } + (void)clearRemoteMediaMapping { [[self uploadedMediaStore] readWriteInTransaction:^(id writer) { [writer deleteAllValues]; }]; } + (NSString *)genericPath { return @"/tg/sendCommonMessage/@/@"; } - (void)prepare:(NSDictionary *)options { [super prepare:options]; _conversationId = [options[@"conversationId"] longLongValue]; _accessHash = [options[@"accessHash"] longLongValue]; _postAsChannel = [options[@"asChannel"] boolValue]; _notifyMembers = [options[@"notifyMembers"] boolValue]; if (options[@"sendActivity"] != nil) { self.sendActivity = [options[@"sendActivity"] boolValue]; } else { self.sendActivity = true; } } - (int64_t)peerId { return _conversationId; } - (int64_t)conversationIdForActivity { return _conversationId; } - (int64_t)accessHashForActivity { return _accessHash; } - (NSArray *)convertEntities:(NSArray *)entities { NSMutableArray *result = [[NSMutableArray alloc] init]; for (TGMessageEntity *entity in entities) { if ([entity isKindOfClass:[TGMessageEntityBold class]]) { TLMessageEntity$messageEntityBold *boldEntity = [[TLMessageEntity$messageEntityBold alloc] init]; boldEntity.offset = (int32_t)entity.range.location; boldEntity.length = (int32_t)entity.range.length; [result addObject:boldEntity]; } else if ([entity isKindOfClass:[TGMessageEntityBotCommand class]]) { TLMessageEntity$messageEntityBotCommand *botCommandEntity = [[TLMessageEntity$messageEntityBotCommand alloc] init]; botCommandEntity.offset = (int32_t)entity.range.location; botCommandEntity.length = (int32_t)entity.range.length; [result addObject:botCommandEntity]; } else if ([entity isKindOfClass:[TGMessageEntityCode class]]) { TLMessageEntity$messageEntityCode *codeEntity = [[TLMessageEntity$messageEntityCode alloc] init]; codeEntity.offset = (int32_t)entity.range.location; codeEntity.length = (int32_t)entity.range.length; [result addObject:codeEntity]; } else if ([entity isKindOfClass:[TGMessageEntityEmail class]]) { TLMessageEntity$messageEntityEmail *emailEntity = [[TLMessageEntity$messageEntityEmail alloc] init]; emailEntity.offset = (int32_t)entity.range.location; emailEntity.length = (int32_t)entity.range.length; [result addObject:emailEntity]; } else if ([entity isKindOfClass:[TGMessageEntityHashtag class]]) { TLMessageEntity$messageEntityHashtag *hashtagEntity = [[TLMessageEntity$messageEntityHashtag alloc] init]; hashtagEntity.offset = (int32_t)entity.range.location; hashtagEntity.length = (int32_t)entity.range.length; [result addObject:hashtagEntity]; } else if ([entity isKindOfClass:[TGMessageEntityItalic class]]) { TLMessageEntity$messageEntityItalic *italicEntity = [[TLMessageEntity$messageEntityItalic alloc] init]; italicEntity.offset = (int32_t)entity.range.location; italicEntity.length = (int32_t)entity.range.length; [result addObject:italicEntity]; } else if ([entity isKindOfClass:[TGMessageEntityMention class]]) { TLMessageEntity$messageEntityMention *mentionEntity = [[TLMessageEntity$messageEntityMention alloc] init]; mentionEntity.offset = (int32_t)entity.range.location; mentionEntity.length = (int32_t)entity.range.length; [result addObject:mentionEntity]; } else if ([entity isKindOfClass:[TGMessageEntityPre class]]) { TLMessageEntity$messageEntityPre *preEntity = [[TLMessageEntity$messageEntityPre alloc] init]; preEntity.offset = (int32_t)entity.range.location; preEntity.length = (int32_t)entity.range.length; [result addObject:preEntity]; } else if ([entity isKindOfClass:[TGMessageEntityTextUrl class]]) { TLMessageEntity$messageEntityTextUrl *textUrlEntity = [[TLMessageEntity$messageEntityTextUrl alloc] init]; textUrlEntity.url = ((TGMessageEntityTextUrl *)entity).url; textUrlEntity.offset = (int32_t)entity.range.location; textUrlEntity.length = (int32_t)entity.range.length; [result addObject:textUrlEntity]; } else if ([entity isKindOfClass:[TGMessageEntityUrl class]]) { TLMessageEntity$messageEntityUrl *urlEntity = [[TLMessageEntity$messageEntityUrl alloc] init]; urlEntity.offset = (int32_t)entity.range.location; urlEntity.length = (int32_t)entity.range.length; [result addObject:urlEntity]; } } return result; } - (void)_commitSend { if (_conversationId == 0) [self _fail]; else { if (self.preparedMessage.botContextResult != nil) { self.cancelToken = [TGTelegraphInstance doConversationBotContextResult:_conversationId accessHash:_accessHash botContextResult:self.preparedMessage.botContextResult tmpId:self.preparedMessage.randomId replyMessageId:self.preparedMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedTextMessage class]]) { TGPreparedTextMessage *textMessage = (TGPreparedTextMessage *)self.preparedMessage; if (self.preparedMessage.randomId != 0 && self.preparedMessage.mid != 0) { [TGDatabaseInstance() setTempIdForMessageId:textMessage.mid peerId:_conversationId tempId:textMessage.randomId]; } [self setupFailTimeout:[TGModernSendMessageActor defaultTimeoutInterval]]; _shouldPostAlmostDeliveredMessage = true; self.cancelToken = [TGTelegraphInstance doConversationSendMessage:_conversationId accessHash:_accessHash messageText:textMessage.text messageGuid:nil tmpId:textMessage.randomId replyMessageId:textMessage.replyMessage.mid disableLinkPreviews:textMessage.disableLinkPreviews postAsChannel:_postAsChannel notifyMembers:_notifyMembers entities:[self convertEntities:textMessage.entities] actor:self]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedMapMessage class]]) { TGPreparedMapMessage *mapMessage = (TGPreparedMapMessage *)self.preparedMessage; [self setupFailTimeout:[TGModernSendMessageActor defaultTimeoutInterval]]; self.cancelToken = [TGTelegraphInstance doConversationSendLocation:_conversationId accessHash:_accessHash latitude:mapMessage.latitude longitude:mapMessage.longitude venue:mapMessage.venue messageGuid:nil tmpId:mapMessage.randomId replyMessageId:mapMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedLocalImageMessage class]]) { TGPreparedLocalImageMessage *localImageMessage = (TGPreparedLocalImageMessage *)self.preparedMessage; [self setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; [self uploadFilesWithExtensions:@[@[localImageMessage.localImageDataPath, @"jpg", @(true)]]]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedRemoteImageMessage class]]) { TGPreparedRemoteImageMessage *remoteImageMessage = (TGPreparedRemoteImageMessage *)self.preparedMessage; TLInputMedia$inputMediaPhoto *remotePhoto = [[TLInputMedia$inputMediaPhoto alloc] init]; TLInputPhoto$inputPhoto *inputId = [[TLInputPhoto$inputPhoto alloc] init]; inputId.n_id = remoteImageMessage.imageId; inputId.access_hash = remoteImageMessage.accessHash; remotePhoto.n_id = inputId; remotePhoto.caption = remoteImageMessage.caption; [self setupFailTimeout:[TGModernSendMessageActor defaultTimeoutInterval]]; self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:remotePhoto messageGuid:nil tmpId:remoteImageMessage.randomId replyMessageId:remoteImageMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedLocalVideoMessage class]]) { TGPreparedLocalVideoMessage *localVideoMessage = (TGPreparedLocalVideoMessage *)self.preparedMessage; UIImage *thumbnailImage = [[UIImage alloc] initWithContentsOfFile:[self pathForLocalImagePath:localVideoMessage.localThumbnailDataPath]]; CGSize thumbnailSize = TGFitSize(thumbnailImage.size, CGSizeMake(90, 90)); NSData *thumbnailData = UIImageJPEGRepresentation(TGScaleImageToPixelSize(thumbnailImage, thumbnailSize), 0.6f); [self setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; NSMutableArray *desc = [[NSMutableArray alloc] initWithArray:@[[localVideoMessage localVideoPath], @"mp4", @(true)]]; if (localVideoMessage.liveData != nil) [desc addObject:localVideoMessage.liveData]; [self uploadFilesWithExtensions:@[desc, @[thumbnailData, @"jpg", @(false)]]]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedRemoteVideoMessage class]]) { TGPreparedRemoteVideoMessage *remoteVideoMessage = (TGPreparedRemoteVideoMessage *)self.preparedMessage; TLInputMedia$inputMediaDocument *remoteDocument = [[TLInputMedia$inputMediaDocument alloc] init]; TLInputDocument$inputDocument *inputDocument = [[TLInputDocument$inputDocument alloc] init]; inputDocument.n_id = remoteVideoMessage.videoId; inputDocument.access_hash = remoteVideoMessage.accessHash; remoteDocument.n_id = inputDocument; remoteDocument.caption = remoteVideoMessage.caption; [self setupFailTimeout:[TGModernSendMessageActor defaultTimeoutInterval]]; self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:remoteDocument messageGuid:nil tmpId:remoteVideoMessage.randomId replyMessageId:remoteVideoMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedLocalDocumentMessage class]]) { TGPreparedLocalDocumentMessage *localDocumentMessage = (TGPreparedLocalDocumentMessage *)self.preparedMessage; bool locateByHash = true; for (id attribute in localDocumentMessage.attributes) { if ([attribute isKindOfClass:[TGDocumentAttributeAudio class]]) { if (((TGDocumentAttributeAudio *)attribute).isVoice) { locateByHash = false; } } } SSignal *locateSignal = [SSignal single:nil]; if (locateByHash) { NSData *documentData = [NSData dataWithContentsOfFile:[[localDocumentMessage localDocumentDirectory] stringByAppendingPathComponent:[localDocumentMessage localDocumentFileName]] options:NSDataReadingMappedIfSafe error:nil]; TLRPCmessages_getDocumentByHash$messages_getDocumentByHash *getDocumentByHash = [[TLRPCmessages_getDocumentByHash$messages_getDocumentByHash alloc] init]; CC_SHA256_CTX ctx; CC_SHA256_Init(&ctx); int32_t length = (int32_t)documentData.length; for (int32_t offset = 0; offset < length; offset += 128 * 1024) { CC_SHA256_Update(&ctx, ((uint8_t *)documentData.bytes) + offset, MIN(length - offset, 128 * 1024)); } uint8_t digest[CC_SHA256_DIGEST_LENGTH]; CC_SHA256_Final(digest, &ctx); getDocumentByHash.sha256 = [[NSData alloc] initWithBytes:digest length:CC_SHA256_DIGEST_LENGTH]; getDocumentByHash.mime_type = localDocumentMessage.mimeType; getDocumentByHash.size = (int32_t)documentData.length; TGLog(@"getDocumentByHash hash: %@", getDocumentByHash.sha256); locateSignal = [[TGTelegramNetworking instance] requestSignal:getDocumentByHash]; } __weak TGModernSendCommonMessageActor *weakSelf = self; [self.disposables add:[[locateSignal deliverOn:[SQueue wrapConcurrentNativeQueue:[ActionStageInstance() globalStageDispatchQueue]]] startWithNext:^(TLDocument *result) { __strong TGModernSendCommonMessageActor *strongSelf = weakSelf; if (strongSelf != nil) { if ([result isKindOfClass:[TLDocument$document class]]) { TGDocumentMediaAttachment *documentAttachment = [[TGDocumentMediaAttachment alloc] initWithTelegraphDocumentDesc:result]; TLInputMedia$inputMediaDocument *remoteDocument = [[TLInputMedia$inputMediaDocument alloc] init]; TLInputDocument$inputDocument *inputDocument = [[TLInputDocument$inputDocument alloc] init]; inputDocument.n_id = documentAttachment.documentId; inputDocument.access_hash = documentAttachment.accessHash; remoteDocument.caption = documentAttachment.caption; remoteDocument.n_id = inputDocument; [strongSelf setupFailTimeout:[TGModernSendMessageActor defaultTimeoutInterval]]; strongSelf.cancelToken = [TGTelegraphInstance doConversationSendMedia:strongSelf->_conversationId accessHash:strongSelf->_accessHash media:remoteDocument messageGuid:nil tmpId:strongSelf.preparedMessage.randomId replyMessageId:strongSelf.preparedMessage.replyMessage.mid postAsChannel:strongSelf->_postAsChannel notifyMembers:strongSelf->_notifyMembers actor:strongSelf]; } else { [strongSelf setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; NSMutableArray *uploadFiles = [[NSMutableArray alloc] init]; NSMutableArray *desc = [[NSMutableArray alloc] init]; [desc addObjectsFromArray:@[ [[localDocumentMessage localDocumentDirectory] stringByAppendingPathComponent:[localDocumentMessage localDocumentFileName]], @"bin", @(true) ]]; if (localDocumentMessage.liveUploadData != nil) [desc addObject:localDocumentMessage.liveUploadData]; [uploadFiles addObject:desc]; if (localDocumentMessage.localThumbnailDataPath != nil) { UIImage *image = [[UIImage alloc] initWithContentsOfFile:[strongSelf pathForLocalImagePath:localDocumentMessage.localThumbnailDataPath]]; if (image != nil) { NSData *thumbnailData = UIImageJPEGRepresentation(TGScaleImageToPixelSize(image, TGFitSize(image.size, CGSizeMake(90, 90))), 0.6f); if (thumbnailData != nil) [uploadFiles addObject:@[thumbnailData, @"jpg", @(false)]]; } } [strongSelf uploadFilesWithExtensions:uploadFiles]; } } } error:^(__unused id error) { __strong TGModernSendCommonMessageActor *strongSelf = weakSelf; if (strongSelf != nil) { [strongSelf _fail]; } } completed:nil]]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedRemoteDocumentMessage class]]) { TGPreparedRemoteDocumentMessage *remoteDocumentMessage = (TGPreparedRemoteDocumentMessage *)self.preparedMessage; TLInputMedia$inputMediaDocument *remoteDocument = [[TLInputMedia$inputMediaDocument alloc] init]; TLInputDocument$inputDocument *inputDocument = [[TLInputDocument$inputDocument alloc] init]; inputDocument.n_id = remoteDocumentMessage.documentId; inputDocument.access_hash = remoteDocumentMessage.accessHash; remoteDocument.caption = remoteDocumentMessage.caption; remoteDocument.n_id = inputDocument; [self setupFailTimeout:[TGModernSendMessageActor defaultTimeoutInterval]]; self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:remoteDocument messageGuid:nil tmpId:remoteDocumentMessage.randomId replyMessageId:remoteDocumentMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedForwardedMessage class]]) { TGPreparedForwardedMessage *forwardedMessage = (TGPreparedForwardedMessage *)self.preparedMessage; int64_t fromPeerAccessHash = 0; if (TGPeerIdIsChannel(forwardedMessage.forwardSourcePeerId)) { fromPeerAccessHash = ((TGConversation *)[TGDatabaseInstance() loadChannels:@[@(forwardedMessage.forwardSourcePeerId)]][@(forwardedMessage.forwardSourcePeerId)]).accessHash; } [self setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; self.cancelToken = [TGTelegraphInstance doConversationForwardMessage:_conversationId accessHash:_accessHash messageId:forwardedMessage.forwardMid fromPeer:forwardedMessage.forwardSourcePeerId fromPeerAccessHash:fromPeerAccessHash postAsChannel:_postAsChannel notifyMembers:_notifyMembers tmpId:forwardedMessage.randomId actor:self]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedContactMessage class]]) { TGPreparedContactMessage *contactMessage = (TGPreparedContactMessage *)self.preparedMessage; [self setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; TLInputMedia$inputMediaContact *inputContact = [[TLInputMedia$inputMediaContact alloc] init]; inputContact.first_name = contactMessage.firstName; inputContact.last_name = contactMessage.lastName; inputContact.phone_number = contactMessage.phoneNumber; self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:inputContact messageGuid:nil tmpId:contactMessage.randomId replyMessageId:contactMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadImageMessage class]]) { TGPreparedDownloadImageMessage *downloadImageMessage = (TGPreparedDownloadImageMessage *)self.preparedMessage; [self setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; bool dispatchThumbnail = false; NSString *url = [downloadImageMessage.imageInfo imageUrlForLargestSize:NULL]; NSString *imagePath = [self filePathForLocalImageUrl:url]; [[NSFileManager defaultManager] createDirectoryAtPath:[imagePath stringByDeletingLastPathComponent] withIntermediateDirectories:true attributes:nil error:nil]; NSData *imageData = [[NSData alloc] initWithContentsOfFile:imagePath]; if (imageData == nil) { imageData = [[[TGMediaStoreContext instance] temporaryFilesCache] getValueForKey:[url dataUsingEncoding:NSUTF8StringEncoding]]; if (imageData != nil) { [imageData writeToFile:imagePath atomically:false]; dispatchThumbnail = true; } } if (imageData != nil) { [self _uploadDownloadedData:imageData dispatchThumbnail:dispatchThumbnail]; } else { self.uploadProgressContainsPreDownloads = true; NSString *path = [[NSString alloc] initWithFormat:@"/temporaryDownload/(%@)", url]; [ActionStageInstance() requestActor:path options:@{@"url": url, @"file": imagePath, @"queue": @"messagePreDownloads"} flags:0 watcher:self]; [self beginUploadProgress]; } } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadDocumentMessage class]]) { TGPreparedDownloadDocumentMessage *downloadDocumentMessage = (TGPreparedDownloadDocumentMessage *)self.preparedMessage; bool dispatchThumbnail = false; NSString *documentPath = [self filePathForLocalDocumentId:downloadDocumentMessage.localDocumentId attributes:downloadDocumentMessage.attributes]; NSData *documentData = [[NSData alloc] initWithContentsOfFile:documentPath]; if (documentData == nil) { NSString *documentUrl = downloadDocumentMessage.documentUrl; if ([documentUrl isKindOfClass:[NSURL class]]) documentUrl = [(NSURL *)documentUrl path]; documentData = [[[TGMediaStoreContext instance] temporaryFilesCache] getValueForKey:[documentUrl dataUsingEncoding:NSUTF8StringEncoding]]; if (documentData != nil) { [[NSFileManager defaultManager] createDirectoryAtPath:[documentPath stringByDeletingLastPathComponent] withIntermediateDirectories:true attributes:nil error:nil]; [documentData writeToFile:documentPath atomically:false]; dispatchThumbnail = true; } } if (documentData != nil) { [self _uploadDownloadedData:documentData dispatchThumbnail:dispatchThumbnail]; } else { [self setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; self.uploadProgressContainsPreDownloads = true; NSString *path = [[NSString alloc] initWithFormat:@"/temporaryDownload/(%@)", [TGStringUtils stringByEscapingForActorURL:downloadDocumentMessage.documentUrl]]; [ActionStageInstance() requestActor:path options:@{@"url": downloadDocumentMessage.documentUrl, @"size": @(downloadDocumentMessage.size), @"path": documentPath, @"queue": @"messagePreDownloads"} flags:0 watcher:self]; [self beginUploadProgress]; } } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadExternalGifMessage class]]) { TGPreparedDownloadExternalGifMessage *externalGifMessage = (TGPreparedDownloadExternalGifMessage *)self.preparedMessage; __weak TGModernSendCommonMessageActor *weakSelf = self; [self.disposables add:[[[TGWebpageSignals webpagePreview:externalGifMessage.searchResult.url] deliverOn:[SQueue wrapConcurrentNativeQueue:[ActionStageInstance() globalStageDispatchQueue]]] startWithNext:^(TGWebPageMediaAttachment *webPage) { __strong TGModernSendCommonMessageActor *strongSelf = weakSelf; if (strongSelf != nil) { if (webPage.document != nil) { TLInputMedia$inputMediaDocument *remoteDocument = [[TLInputMedia$inputMediaDocument alloc] init]; TLInputDocument$inputDocument *inputDocument = [[TLInputDocument$inputDocument alloc] init]; inputDocument.n_id = webPage.document.documentId; inputDocument.access_hash = webPage.document.accessHash; remoteDocument.caption = externalGifMessage.caption; remoteDocument.n_id = inputDocument; [strongSelf setupFailTimeout:[TGModernSendMessageActor defaultTimeoutInterval]]; strongSelf.cancelToken = [TGTelegraphInstance doConversationSendMedia:strongSelf->_conversationId accessHash:strongSelf->_accessHash media:remoteDocument messageGuid:nil tmpId:externalGifMessage.randomId replyMessageId:externalGifMessage.replyMessage.mid postAsChannel:strongSelf->_postAsChannel notifyMembers:strongSelf->_notifyMembers actor:strongSelf]; } else { TGLog(@"Webpage doesn't contain document"); [strongSelf _fail]; } } } error:^(__unused id error) { __strong TGModernSendCommonMessageActor *strongSelf = weakSelf; if (strongSelf != nil) { TGLog(@"Webpage fetch error"); [strongSelf _fail]; } } completed:nil]]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadExternalImageMessage class]]) { TGPreparedDownloadExternalImageMessage *externalImageMessage = (TGPreparedDownloadExternalImageMessage *)self.preparedMessage; __weak TGModernSendCommonMessageActor *weakSelf = self; [self.disposables add:[[[TGWebpageSignals webpagePreview:externalImageMessage.searchResult.url] deliverOn:[SQueue wrapConcurrentNativeQueue:[ActionStageInstance() globalStageDispatchQueue]]] startWithNext:^(TGWebPageMediaAttachment *webPage) { __strong TGModernSendCommonMessageActor *strongSelf = weakSelf; if (strongSelf != nil) { if (webPage.photo != nil) { TLInputMedia$inputMediaPhoto *inputMedia = [[TLInputMedia$inputMediaPhoto alloc] init]; TLInputPhoto$inputPhoto *inputPhoto = [[TLInputPhoto$inputPhoto alloc] init]; inputPhoto.n_id = webPage.photo.imageId; inputPhoto.access_hash = webPage.photo.accessHash; inputMedia.n_id = inputPhoto; [strongSelf setupFailTimeout:[TGModernSendMessageActor defaultTimeoutInterval]]; strongSelf.cancelToken = [TGTelegraphInstance doConversationSendMedia:strongSelf->_conversationId accessHash:strongSelf->_accessHash media:inputMedia messageGuid:nil tmpId:externalImageMessage.randomId replyMessageId:externalImageMessage.replyMessage.mid postAsChannel:strongSelf->_postAsChannel notifyMembers:strongSelf->_notifyMembers actor:strongSelf]; } else { TGLog(@"Webpage doesn't contain photo"); [strongSelf _fail]; } } } error:^(__unused id error) { __strong TGModernSendCommonMessageActor *strongSelf = weakSelf; if (strongSelf != nil) { TGLog(@"Webpage fetch error"); [strongSelf _fail]; } } completed:nil]]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedAssetImageMessage class]]) { TGPreparedAssetImageMessage *assetImageMessage = (TGPreparedAssetImageMessage *)self.preparedMessage; [self beginUploadProgress]; __weak TGModernSendCommonMessageActor *weakSelf = self; [self.disposables add:[[[[[[TGMediaAssetsLibrary sharedLibrary] assetWithIdentifier:assetImageMessage.assetIdentifier] mapToSignal:^SSignal *(TGMediaAsset *asset) { if (!assetImageMessage.document) { return [[TGMediaAssetImageSignals imageForAsset:asset imageType:TGMediaAssetImageTypeScreen size:CGSizeMake(1280, 1280) allowNetworkAccess:false] catch:^SSignal *(id error) { if (![error isKindOfClass:[NSNumber class]] && !assetImageMessage.isCloud) return [SSignal fail:error]; self.uploadProgressContainsPreDownloads = true; return [TGMediaAssetImageSignals imageForAsset:asset imageType:TGMediaAssetImageTypeScreen size:CGSizeMake(1280, 1280) allowNetworkAccess:true]; }]; } else { return [[TGMediaAssetImageSignals imageDataForAsset:asset allowNetworkAccess:false] catch:^SSignal *(id error) { if (![error isKindOfClass:[NSNumber class]] && !assetImageMessage.isCloud) return [SSignal fail:error]; self.uploadProgressContainsPreDownloads = true; return [TGMediaAssetImageSignals imageDataForAsset:asset allowNetworkAccess:true]; }]; } }] filter:^bool(id value) { if ([value isKindOfClass:[UIImage class]]) return !((UIImage *)value).degraded; return true; }] deliverOn:[SQueue wrapConcurrentNativeQueue:[ActionStageInstance() globalStageDispatchQueue]]] startWithNext:^(id next) { __strong TGModernSendCommonMessageActor *strongSelf = weakSelf; if ([next isKindOfClass:[NSNumber class]]) { float value = [next floatValue]; [strongSelf updatePreDownloadsProgress:value]; } else if ([next isKindOfClass:[UIImage class]]) { [strongSelf updatePreDownloadsProgress:1.0f]; [strongSelf setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; NSData *imageData = UIImageJPEGRepresentation((UIImage *)next, 0.54f); NSString *imagePath = [self filePathForLocalImageUrl:[assetImageMessage.imageInfo imageUrlForLargestSize:NULL]]; [[NSFileManager defaultManager] createDirectoryAtPath:[imagePath stringByDeletingLastPathComponent] withIntermediateDirectories:true attributes:nil error:nil]; [imageData writeToFile:imagePath atomically:false]; NSString *localImageDirectory = [imagePath stringByDeletingLastPathComponent]; NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:localImageDirectory error:nil]; for (NSString *file in files) { if ([file hasPrefix:@"thumbnail-"]) [[NSFileManager defaultManager] removeItemAtPath:[localImageDirectory stringByAppendingPathComponent:file] error:nil]; } NSString *thumbnailUrl = [assetImageMessage.imageInfo closestImageUrlWithSize:CGSizeZero resultingSize:NULL]; if (thumbnailUrl != nil) [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/as/media/imageThumbnailUpdated"] resource:thumbnailUrl]; NSString *hash = nil; if (assetImageMessage.useMediaCache) { hash = TGImageHash(imageData); assetImageMessage.imageHash = hash; } TGImageMediaAttachment *attachment = [TGImageDownloadActor serverMediaDataForAssetUrl:hash][@"imageAttachment"]; if (hash != nil && attachment != nil) { TLInputMedia$inputMediaPhoto *remotePhoto = [[TLInputMedia$inputMediaPhoto alloc] init]; TLInputPhoto$inputPhoto *inputId = [[TLInputPhoto$inputPhoto alloc] init]; inputId.n_id = attachment.imageId; inputId.access_hash = attachment.accessHash; remotePhoto.n_id = inputId; remotePhoto.caption = assetImageMessage.caption; self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:remotePhoto messageGuid:nil tmpId:assetImageMessage.randomId replyMessageId:assetImageMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else { [strongSelf uploadFilesWithExtensions:@[@[imageData, @"jpg", @(true)]]]; } } else if ([next isKindOfClass:[TGMediaAssetImageData class]]) { [strongSelf updatePreDownloadsProgress:1.0f]; [strongSelf setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; TGMediaAssetImageData *assetData = (TGMediaAssetImageData *)next; NSData *documentData = assetData.imageData; assetImageMessage.fileSize = (uint32_t)assetData.imageData.length; TGMessage *updatedMessage = self.preparedMessage.message; [TGDatabaseInstance() updateMessage:self.preparedMessage.mid peerId:_conversationId flags:std::vector() media:updatedMessage.mediaAttachments dispatch:false]; updatedMessage = [TGDatabaseInstance() loadMessageWithMid:self.preparedMessage.mid peerId:_conversationId]; id resource = [[SGraphObjectNode alloc] initWithObject:[[NSArray alloc] initWithObjects:[[NSNumber alloc] initWithInt:self.preparedMessage.mid], updatedMessage, nil]]; [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/tg/conversation/(%lld)/messagesChanged", _conversationId] resource:resource]; NSString *fileExtension = [assetData.fileName pathExtension]; if (fileExtension == nil) fileExtension = @""; NSArray *attributes = assetImageMessage.attributes; NSString *documentPath = [self filePathForLocalDocumentId:assetImageMessage.localDocumentId attributes:attributes]; [[NSFileManager defaultManager] createDirectoryAtPath:[documentPath stringByDeletingLastPathComponent] withIntermediateDirectories:true attributes:nil error:nil]; [documentData writeToFile:documentPath atomically:false]; NSMutableArray *files = [[NSMutableArray alloc] init]; [files addObject:@[documentPath, fileExtension, @(true)]]; UIImage *thumbnailImage = [[UIImage alloc] initWithContentsOfFile:[self pathForLocalImagePath:assetImageMessage.localThumbnailDataPath]]; CGSize thumbnailSize = TGFitSize(thumbnailImage.size, CGSizeMake(90, 90)); NSData *thumbnailData = UIImageJPEGRepresentation(TGScaleImageToPixelSize(thumbnailImage, thumbnailSize), 0.6f); if (thumbnailData != nil) [files addObject:@[thumbnailData, @"jpg", @(false)]]; NSString *thumbnailUrl = [assetImageMessage.imageInfo closestImageUrlWithSize:CGSizeZero resultingSize:NULL]; if (thumbnailUrl != nil) [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/as/media/imageThumbnailUpdated"] resource:thumbnailUrl]; [self uploadFilesWithExtensions:files]; } } error:^(__unused id error) { __strong TGModernSendCommonMessageActor *strongSelf = weakSelf; if (strongSelf != nil) { TGLog(@"Cloud photo load error"); [strongSelf _fail]; } } completed:nil]]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedAssetVideoMessage class]]) { TGPreparedAssetVideoMessage *assetVideoMessage = (TGPreparedAssetVideoMessage *)self.preparedMessage; [self beginUploadProgress]; TGVideoEditAdjustments *adjustments = [TGVideoEditAdjustments editAdjustmentsWithDictionary:assetVideoMessage.adjustments]; bool liveUpload = assetVideoMessage.liveUpload; bool passthrough = assetVideoMessage.passthrough; bool useMediaCache = assetVideoMessage.useMediaCache; SSignal *(^hashSignal)(AVAsset *) = ^(AVAsset *avAsset) { if ([adjustments cropAppliedForAvatar:false] || [adjustments rotationApplied] || [adjustments trimApplied]) return [SSignal single:nil]; return [TGVideoConverter hashSignalForAVAsset:avAsset]; }; if (!assetVideoMessage.document) self.uploadProgressContainsPreDownloads = true; NSString *tempFilePath = TGTemporaryFileName(nil); SSignal *signal = [videoDownloadQueue() enqueue:[[[[TGMediaAssetsLibrary sharedLibrary] assetWithIdentifier:assetVideoMessage.assetIdentifier] mapToSignal:^SSignal *(TGMediaAsset *asset) { if (!assetVideoMessage.document) { return [[TGMediaAssetImageSignals avAssetForVideoAsset:asset allowNetworkAccess:false] catch:^SSignal *(id error) { if (![error isKindOfClass:[NSNumber class]] && !assetVideoMessage.isCloud) return [SSignal fail:error]; return [TGMediaAssetImageSignals avAssetForVideoAsset:asset allowNetworkAccess:true]; }]; } else { if (asset.subtypes & TGMediaAssetSubtypeVideoHighFrameRate) self.uploadProgressContainsPreDownloads = true; return [[TGMediaAssetImageSignals saveUncompressedVideoForAsset:asset toPath:tempFilePath allowNetworkAccess:false] catch:^SSignal *(id error) { if (![error isKindOfClass:[NSNumber class]] && !assetVideoMessage.isCloud) return [SSignal fail:error]; self.uploadProgressContainsPreDownloads = true; return [TGMediaAssetImageSignals saveUncompressedVideoForAsset:asset toPath:tempFilePath allowNetworkAccess:true]; }]; } }] mapToSignal:^SSignal *(id value) { if ([value isKindOfClass:[AVAsset class]]) { AVAsset *avAsset = (AVAsset *)value; SSignal *(^convertSignal)(NSString *) = ^SSignal *(NSString *hash) { assetVideoMessage.videoHash = hash; return [[TGVideoConverter convertSignalForAVAsset:avAsset adjustments:adjustments liveUpload:liveUpload passthrough:passthrough] map:^id(id value) { if ([value isKindOfClass:[NSDictionary class]]) { NSMutableDictionary *dict = [value mutableCopy]; if (hash != nil) dict[@"hash"] = hash; return @{ @"convertResult": dict }; } else if ([value isKindOfClass:[NSNumber class]]) { return @{ @"convertProgress": value }; } return value; }]; }; if (useMediaCache) { return [hashSignal(avAsset) mapToSignal:^SSignal *(NSString *hash) { if (hash != nil && [TGImageDownloadActor serverMediaDataForAssetUrl:hash]) { NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; dict[@"hash"] = hash; return [SSignal single:@{ @"remote": dict }]; } return convertSignal(hash); }]; } else { return convertSignal(nil); } } else if ([value isKindOfClass:[NSString class]]) { NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; dict[@"filePath"] = tempFilePath; dict[@"fileName"] = value; return [SSignal single:@{ @"fileResult": dict }]; } else if ([value isKindOfClass:[NSNumber class]]) { return [SSignal single:@{ @"downloadProgress": value }]; } return [SSignal single:value]; }]]; __weak TGModernSendCommonMessageActor *weakSelf = self; [self.disposables add:[[signal deliverOn:[SQueue wrapConcurrentNativeQueue:[ActionStageInstance() globalStageDispatchQueue]]] startWithNext:^(id next) { __strong TGModernSendCommonMessageActor *strongSelf = weakSelf; if (![next isKindOfClass:[NSDictionary class]]) return; NSDictionary *dict = (NSDictionary *)next; if (dict[@"remote"] != nil) { NSString *hash = dict[@"remote"][@"hash"]; TGVideoMediaAttachment *attachment = [TGImageDownloadActor serverMediaDataForAssetUrl:hash][@"videoAttachment"]; if (attachment != nil) { TLInputMedia$inputMediaDocument *inputMediaDocument = [[TLInputMedia$inputMediaDocument alloc] init]; TLInputDocument$inputDocument *inputDocument = [[TLInputDocument$inputDocument alloc] init]; inputDocument.n_id = attachment.videoId; inputDocument.access_hash = attachment.accessHash; inputMediaDocument.n_id = inputDocument; inputMediaDocument.caption = assetVideoMessage.caption; [self setupFailTimeout:[TGModernSendMessageActor defaultTimeoutInterval]]; self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:inputMediaDocument messageGuid:nil tmpId:assetVideoMessage.randomId replyMessageId:assetVideoMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else { [strongSelf _fail]; } } else if (dict[@"downloadProgress"] != nil) { float value = [dict[@"downloadProgress"] floatValue]; if (!assetVideoMessage.document) value /= 2.0f; [strongSelf updatePreDownloadsProgress:value]; } else if (dict[@"convertProgress"] != nil) { float value = [dict[@"convertProgress"] floatValue]; [strongSelf updatePreDownloadsProgress:0.5f + value / 2.0f]; } else if (dict[@"convertResult"] != nil) { NSDictionary *result = dict[@"convertResult"]; [strongSelf updatePreDownloadsProgress:1.0f]; [strongSelf setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; assetVideoMessage.duration = [result[@"duration"] doubleValue]; assetVideoMessage.dimensions = [result[@"dimensions"] CGSizeValue]; assetVideoMessage.fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:[result[@"fileUrl"] path] error:NULL][NSFileSize] intValue]; TGMessage *updatedMessage = self.preparedMessage.message; [TGDatabaseInstance() updateMessage:self.preparedMessage.mid peerId:_conversationId flags:std::vector() media:updatedMessage.mediaAttachments dispatch:false]; updatedMessage = [TGDatabaseInstance() loadMessageWithMid:self.preparedMessage.mid peerId:_conversationId]; id resource = [[SGraphObjectNode alloc] initWithObject:[[NSArray alloc] initWithObjects:[[NSNumber alloc] initWithInt:self.preparedMessage.mid], updatedMessage, nil]]; [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/tg/conversation/(%lld)/messagesChanged", _conversationId] resource:resource]; [[NSFileManager defaultManager] removeItemAtPath:[assetVideoMessage localVideoPath] error:nil]; [[NSFileManager defaultManager] moveItemAtPath:[result[@"fileUrl"] path] toPath:[assetVideoMessage localVideoPath] error:nil]; [[NSFileManager defaultManager] createSymbolicLinkAtPath:[result[@"fileUrl"] path] withDestinationPath:[assetVideoMessage localVideoPath] error:nil]; NSString *thumbnailUrl = [assetVideoMessage.imageInfo closestImageUrlWithSize:CGSizeZero resultingSize:NULL]; if (thumbnailUrl != nil) [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/as/media/imageThumbnailUpdated"] resource:thumbnailUrl]; UIImage *thumbnailImage = result[@"previewImage"]; if (thumbnailImage == nil) { thumbnailImage = [[UIImage alloc] initWithContentsOfFile:[self pathForLocalImagePath:assetVideoMessage.localThumbnailDataPath]]; } CGSize thumbnailSize = TGFitSize(thumbnailImage.size, CGSizeMake(90, 90)); NSData *thumbnailData = UIImageJPEGRepresentation(TGScaleImageToPixelSize(thumbnailImage, thumbnailSize), 0.6f); NSMutableArray *desc = [[NSMutableArray alloc] initWithArray:@[[assetVideoMessage localVideoPath], @"mp4", @(true)]]; if (result[@"liveUploadData"] != nil) [desc addObject:result[@"liveUploadData"]]; [self uploadFilesWithExtensions:@[desc, @[thumbnailData, @"jpg", @(false)]]]; } else if (dict[@"fileResult"] != nil) { [strongSelf updatePreDownloadsProgress:1.0f]; [strongSelf setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; NSDictionary *result = dict[@"fileResult"]; assetVideoMessage.fileSize = [[[NSFileManager defaultManager] attributesOfItemAtPath:result[@"filePath"] error:NULL][NSFileSize] intValue]; TGMessage *updatedMessage = self.preparedMessage.message; [TGDatabaseInstance() updateMessage:self.preparedMessage.mid peerId:_conversationId flags:std::vector() media:updatedMessage.mediaAttachments dispatch:false]; updatedMessage = [TGDatabaseInstance() loadMessageWithMid:self.preparedMessage.mid peerId:_conversationId]; id resource = [[SGraphObjectNode alloc] initWithObject:[[NSArray alloc] initWithObjects:[[NSNumber alloc] initWithInt:self.preparedMessage.mid], updatedMessage, nil]]; [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/tg/conversation/(%lld)/messagesChanged", _conversationId] resource:resource]; NSString *fileExtension = [assetVideoMessage.fileName pathExtension]; if (fileExtension == nil) fileExtension = @""; NSArray *attributes = assetVideoMessage.attributes; NSString *documentPath = [self filePathForLocalDocumentId:assetVideoMessage.localDocumentId attributes:attributes]; [[NSFileManager defaultManager] createDirectoryAtPath:[documentPath stringByDeletingLastPathComponent] withIntermediateDirectories:true attributes:nil error:nil]; [[NSFileManager defaultManager] moveItemAtPath:result[@"filePath"] toPath:documentPath error:nil]; NSMutableArray *files = [[NSMutableArray alloc] init]; [files addObject:@[documentPath, fileExtension, @(true)]]; UIImage *thumbnailImage = [[UIImage alloc] initWithContentsOfFile:[self pathForLocalImagePath:assetVideoMessage.localThumbnailDataPath]]; CGSize thumbnailSize = TGFitSize(thumbnailImage.size, CGSizeMake(90, 90)); NSData *thumbnailData = UIImageJPEGRepresentation(TGScaleImageToPixelSize(thumbnailImage, thumbnailSize), 0.6f); if (thumbnailData != nil) [files addObject:@[thumbnailData, @"jpg", @(false)]]; NSString *thumbnailUrl = [assetVideoMessage.imageInfo closestImageUrlWithSize:CGSizeZero resultingSize:NULL]; if (thumbnailUrl != nil) [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/as/media/imageThumbnailUpdated"] resource:thumbnailUrl]; [self uploadFilesWithExtensions:files]; } } error:^(__unused id error) { __strong TGModernSendCommonMessageActor *strongSelf = weakSelf; if (strongSelf != nil) { TGLog(@"Cloud photo load error"); [strongSelf _fail]; } } completed:nil]]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedCloudDocumentMessage class]]) { TGPreparedCloudDocumentMessage *cloudDocumentMessage = (TGPreparedCloudDocumentMessage *)self.preparedMessage; bool dispatchThumbnail = false; NSString *documentPath = [self filePathForLocalDocumentId:cloudDocumentMessage.localDocumentId attributes:cloudDocumentMessage.attributes]; NSData *documentData = [[NSData alloc] initWithContentsOfFile:documentPath]; if (documentData == nil) { NSString *documentUrl = [cloudDocumentMessage.documentUrl path]; if ([documentUrl isKindOfClass:[NSURL class]]) documentUrl = [(NSURL *)documentUrl path]; documentData = [[[TGMediaStoreContext instance] temporaryFilesCache] getValueForKey:[documentUrl dataUsingEncoding:NSUTF8StringEncoding]]; if (documentData != nil) { [[NSFileManager defaultManager] createDirectoryAtPath:[documentPath stringByDeletingLastPathComponent] withIntermediateDirectories:true attributes:nil error:nil]; [documentData writeToFile:documentPath atomically:false]; dispatchThumbnail = true; } } if (documentData != nil) { [self _uploadDownloadedData:documentData dispatchThumbnail:dispatchThumbnail]; } else if (cloudDocumentMessage.documentUrl != nil) { [self setupFailTimeout:[TGModernSendCommonMessageActor defaultTimeoutInterval]]; self.uploadProgressContainsPreDownloads = true; NSString *path = [[NSString alloc] initWithFormat:@"/iCloudDownload/(%@)", [TGStringUtils stringByEscapingForActorURL:cloudDocumentMessage.documentUrl.absoluteString]]; [ActionStageInstance() requestActor:path options:@{@"url": cloudDocumentMessage.documentUrl, @"path": documentPath, @"queue": @"messagePreDownloads"} flags:0 watcher:self]; [self beginUploadProgress]; } else [self _fail]; } else [self _fail]; } } - (NSString *)filePathForLocalDocumentId:(int64_t)localDocumentId attributes:(NSArray *)attributes { NSString *directory = nil; directory = [TGPreparedLocalDocumentMessage localDocumentDirectoryForLocalDocumentId:localDocumentId]; NSString *fileName = @"file"; for (id attribute in attributes) { if ([attribute isKindOfClass:[TGDocumentAttributeFilename class]]) { fileName = ((TGDocumentAttributeFilename *)attribute).filename; break; } } NSString *filePath = [directory stringByAppendingPathComponent:[TGDocumentMediaAttachment safeFileNameForFileName:fileName]]; return filePath; } - (NSString *)filePathForLocalImageUrl:(NSString *)localImageUrl { static NSString *filesDirectory = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { filesDirectory = [[TGAppDelegate documentsPath] stringByAppendingPathComponent:@"files"]; }); int64_t localImageId = murMurHash32(localImageUrl); NSString *photoDirectoryName = [[NSString alloc] initWithFormat:@"image-local-%" PRIx64 "", localImageId]; NSString *photoDirectory = [filesDirectory stringByAppendingPathComponent:photoDirectoryName]; NSString *imagePath = [photoDirectory stringByAppendingPathComponent:@"image.jpg"]; return imagePath; } - (NSString *)filePathForRemoteImageId:(int64_t)remoteImageId { static NSString *filesDirectory = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { filesDirectory = [[TGAppDelegate documentsPath] stringByAppendingPathComponent:@"files"]; }); NSString *photoDirectoryName = [[NSString alloc] initWithFormat:@"image-remote-%" PRIx64 "", remoteImageId]; NSString *photoDirectory = [filesDirectory stringByAppendingPathComponent:photoDirectoryName]; NSString *imagePath = [photoDirectory stringByAppendingPathComponent:@"image.jpg"]; return imagePath; } - (void)_fail { std::vector flags; flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagDeliveryState, .value = TGMessageDeliveryStateFailed}); [TGDatabaseInstance() updateMessage:self.preparedMessage.mid peerId:_conversationId flags:flags media:nil dispatch:true]; [ActionStageInstance() dispatchMessageToWatchers:self.path messageType:@"messageDeliveryFailed" message:@{ @"previousMid": @(self.preparedMessage.mid) }]; [super _fail]; } #pragma mark - - (void)_uploadDownloadedData:(NSData *)data dispatchThumbnail:(bool)dispatchThumbnail { if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadImageMessage class]]) { TGPreparedDownloadImageMessage *downloadImageMessage = (TGPreparedDownloadImageMessage *)self.preparedMessage; if (dispatchThumbnail) { NSString *thumbnailUrl = [downloadImageMessage.imageInfo closestImageUrlWithSize:CGSizeZero resultingSize:NULL]; if (thumbnailUrl != nil) { [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/as/media/imageThumbnailUpdated"] resource:thumbnailUrl]; } } [self uploadFilesWithExtensions:@[@[data, @"jpg", @(true)]]]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadDocumentMessage class]]) { TGPreparedDownloadDocumentMessage *downloadDocumentMessage = (TGPreparedDownloadDocumentMessage *)self.preparedMessage; if (dispatchThumbnail) { NSString *thumbnailUrl = [downloadDocumentMessage.thumbnailInfo imageUrlForLargestSize:NULL]; if (thumbnailUrl != nil) { [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/as/media/imageThumbnailUpdated"] resource:thumbnailUrl]; } } TGDocumentAttributeFilename *fileNameAttribute; NSArray *attributes = downloadDocumentMessage.attributes; bool hasImageSizeAttribute = false; bool hasStickerAttribute = false; for (id attribute in attributes) { if ([attribute isKindOfClass:[TGDocumentAttributeFilename class]]) fileNameAttribute = (TGDocumentAttributeFilename *)attribute; if ([attribute isKindOfClass:[TGDocumentAttributeImageSize class]]) hasImageSizeAttribute = true; if ([attribute isKindOfClass:[TGDocumentAttributeVideo class]]) { hasImageSizeAttribute = true; } if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) hasStickerAttribute = true; } NSString *fileExtension = @"gif"; if (fileNameAttribute != nil) fileExtension = [fileNameAttribute.filename pathExtension]; if (fileExtension == nil) fileExtension = @""; if (data == nil) { [self _fail]; return; } NSMutableArray *files = [[NSMutableArray alloc] init]; [files addObject:@[data, fileExtension, @(true)]]; if ([fileExtension isEqualToString:@"webp"]) { CGSize imageSize = CGSizeZero; int width = 0, height = 0; if(WebPGetInfo((uint8_t const *)data.bytes, data.length, &width, &height)) imageSize = CGSizeMake(width, height); NSMutableArray *documentAttributes = [downloadDocumentMessage.attributes mutableCopy]; if (!hasImageSizeAttribute) { if (imageSize.width > FLT_EPSILON && imageSize.height > FLT_EPSILON) [documentAttributes addObject:[[TGDocumentAttributeImageSize alloc] initWithSize:imageSize]]; } if (!hasStickerAttribute) [documentAttributes addObject:[[TGDocumentAttributeSticker alloc] init]]; downloadDocumentMessage.attributes = documentAttributes; } UIImage *image = [[UIImage alloc] initWithData:data]; NSData *thumbnailData = nil; if (image != nil) { image = TGScaleImageToPixelSize(image, TGFitSize(image.size, CGSizeMake(90, 90))); if (image != nil) thumbnailData = UIImageJPEGRepresentation(image, 0.6f); if (thumbnailData != nil) [files addObject:@[thumbnailData, @"jpg", @(false)]]; } [self uploadFilesWithExtensions:files]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedCloudDocumentMessage class]]) { TGPreparedCloudDocumentMessage *cloudDocumentMessage = (TGPreparedCloudDocumentMessage *)self.preparedMessage; if (dispatchThumbnail) { NSString *thumbnailUrl = [cloudDocumentMessage.thumbnailInfo imageUrlForLargestSize:NULL]; if (thumbnailUrl != nil) { [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/as/media/imageThumbnailUpdated"] resource:thumbnailUrl]; } } TGDocumentAttributeFilename *fileNameAttribute; NSArray *attributes = cloudDocumentMessage.attributes; bool hasImageSizeAttribute = false; bool hasStickerAttribute = false; for (id attribute in attributes) { if ([attribute isKindOfClass:[TGDocumentAttributeFilename class]]) fileNameAttribute = (TGDocumentAttributeFilename *)attribute; if ([attribute isKindOfClass:[TGDocumentAttributeImageSize class]]) hasImageSizeAttribute = true; if ([attribute isKindOfClass:[TGDocumentAttributeVideo class]]) { hasImageSizeAttribute = true; } if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) hasStickerAttribute = true; } NSString *fileExtension = @"gif"; if (fileNameAttribute != nil) fileExtension = [fileNameAttribute.filename pathExtension]; if (fileExtension == nil) fileExtension = @""; if (data == nil) { [self _fail]; return; } if ([fileExtension isEqualToString:@"webp"]) { CGSize imageSize = CGSizeZero; int width = 0, height = 0; if(WebPGetInfo((uint8_t const *)data.bytes, data.length, &width, &height)) imageSize = CGSizeMake(width, height); NSMutableArray *documentAttributes = [cloudDocumentMessage.attributes mutableCopy]; if (!hasImageSizeAttribute) { if (imageSize.width > FLT_EPSILON && imageSize.height > FLT_EPSILON) [documentAttributes addObject:[[TGDocumentAttributeImageSize alloc] initWithSize:imageSize]]; } if (!hasStickerAttribute) [documentAttributes addObject:[[TGDocumentAttributeSticker alloc] init]]; cloudDocumentMessage.attributes = documentAttributes; } NSMutableArray *files = [[NSMutableArray alloc] init]; [files addObject:@[data, fileExtension, @(true)]]; UIImage *image = [[UIImage alloc] initWithData:data]; if (image != nil) { NSData *thumbnailData = nil; image = TGScaleImageToPixelSize(image, TGFitSize(image.size, CGSizeMake(90, 90))); if (image != nil) thumbnailData = UIImageJPEGRepresentation(image, 0.6f); if (thumbnailData != nil) [files addObject:@[thumbnailData, @"jpg", @(false)]]; } [self uploadFilesWithExtensions:files]; } else [self _fail]; } - (void)actorCompleted:(int)status path:(NSString *)path result:(id)result { if ([path hasPrefix:@"/temporaryDownload/"]) { if (status == ASStatusSuccess) { if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadDocumentMessage class]]) { NSData *documentData = result; TGPreparedDownloadDocumentMessage *downloadDocumentMessage = (TGPreparedDownloadDocumentMessage *)self.preparedMessage; NSString *documentPath = [self filePathForLocalDocumentId:downloadDocumentMessage.localDocumentId attributes:downloadDocumentMessage.attributes]; [[NSFileManager defaultManager] createDirectoryAtPath:[documentPath stringByDeletingLastPathComponent] withIntermediateDirectories:true attributes:nil error:nil]; [documentData writeToFile:documentPath atomically:false]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadImageMessage class]]) { NSData *imageData = result; TGPreparedDownloadImageMessage *downloadImageMessage = (TGPreparedDownloadImageMessage *)self.preparedMessage; NSString *imagePath = [self filePathForLocalImageUrl:[downloadImageMessage.imageInfo imageUrlForLargestSize:NULL]]; [[NSFileManager defaultManager] createDirectoryAtPath:[imagePath stringByDeletingLastPathComponent] withIntermediateDirectories:true attributes:nil error:nil]; [imageData writeToFile:imagePath atomically:false]; } [self _uploadDownloadedData:result dispatchThumbnail:true]; } else [self _fail]; } else if ([path hasPrefix:@"/iCloudDownload/"]) { if (status == ASStatusSuccess) { TGPreparedCloudDocumentMessage *cloudDocumentMessage = (TGPreparedCloudDocumentMessage *)self.preparedMessage; NSString *documentPath = [self filePathForLocalDocumentId:cloudDocumentMessage.localDocumentId attributes:cloudDocumentMessage.attributes]; NSError *error; NSData *documentData = [NSData dataWithContentsOfURL:[NSURL fileURLWithPath:documentPath] options:NSDataReadingMappedIfSafe error:&error]; [self _uploadDownloadedData:documentData dispatchThumbnail:true]; } else [self _fail]; } [super actorCompleted:status path:path result:result]; } - (void)actorMessageReceived:(NSString *)path messageType:(NSString *)messageType message:(id)message { if ([path hasPrefix:@"/temporaryDownload/"] || [path hasPrefix:@"/iCloudDownload/"]) { [self restartFailTimeoutIfRunning]; [self updatePreDownloadsProgress:[message floatValue]]; } if ([self.superclass instancesRespondToSelector:@selector(actorMessageReceived:messageType:message:)]) [super actorMessageReceived:path messageType:messageType message:message]; } #pragma mark - - (void)uploadsStarted { [self setupFailTimeout:[TGModernSendMessageActor defaultTimeoutInterval]]; } - (void)uploadProgressChanged { [self restartFailTimeoutIfRunning]; } - (NSArray *)attributesForNativeAttributes:(NSArray *)nativeAttributes { NSMutableArray *attributes = [[NSMutableArray alloc] init]; for (id attribute in nativeAttributes) { if ([attribute isKindOfClass:[TGDocumentAttributeFilename class]]) { TLDocumentAttribute$documentAttributeFilename *concreteAttribute = [[TLDocumentAttribute$documentAttributeFilename alloc] init]; concreteAttribute.file_name = ((TGDocumentAttributeFilename *)attribute).filename; [attributes addObject:concreteAttribute]; } else if ([attribute isKindOfClass:[TGDocumentAttributeAnimated class]]) { [attributes addObject:[[TLDocumentAttribute$documentAttributeAnimated alloc] init]]; } else if ([attribute isKindOfClass:[TGDocumentAttributeImageSize class]]) { TLDocumentAttribute$documentAttributeImageSize *concreteAttribute = [[TLDocumentAttribute$documentAttributeImageSize alloc] init]; concreteAttribute.w = (int32_t)((TGDocumentAttributeImageSize *)attribute).size.width; concreteAttribute.h = (int32_t)((TGDocumentAttributeImageSize *)attribute).size.height; [attributes addObject:concreteAttribute]; } else if ([attribute isKindOfClass:[TGDocumentAttributeVideo class]]) { TLDocumentAttribute$documentAttributeVideo *concreteAttribute = [[TLDocumentAttribute$documentAttributeVideo alloc] init]; concreteAttribute.w = (int32_t)((TGDocumentAttributeVideo *)attribute).size.width; concreteAttribute.h = (int32_t)((TGDocumentAttributeVideo *)attribute).size.height; concreteAttribute.duration = (int32_t)((TGDocumentAttributeVideo *)attribute).duration; [attributes addObject:concreteAttribute]; } else if ([attribute isKindOfClass:[TGDocumentAttributeSticker class]]) { [attributes addObject:[[TLDocumentAttribute$documentAttributeSticker alloc] init]]; } else if ([attribute isKindOfClass:[TGDocumentAttributeAudio class]]) { TGDocumentAttributeAudio *audio = attribute; int32_t flags = 0; if (audio.isVoice) { flags |= (1 << 10); } if (audio.title != nil) { flags |= (1 << 0); } if (audio.performer != nil) { flags |= (1 << 1); } if (audio.waveform != nil) { flags |= (1 << 2); } TLDocumentAttribute$documentAttributeAudio *nativeAttribute = [[TLDocumentAttribute$documentAttributeAudio alloc] init]; nativeAttribute.flags = flags; nativeAttribute.duration = audio.duration; nativeAttribute.title = audio.title; nativeAttribute.performer = audio.performer; nativeAttribute.waveform = [audio.waveform bitstream]; [attributes addObject:nativeAttribute]; } } return attributes; } - (void)uploadsCompleted:(NSDictionary *)filePathToUploadedFile { [self restartFailTimeoutIfRunning]; if ([self.preparedMessage isKindOfClass:[TGPreparedLocalImageMessage class]]) { TGPreparedLocalImageMessage *localImageMessage = (TGPreparedLocalImageMessage *)self.preparedMessage; NSDictionary *fileInfo = filePathToUploadedFile[localImageMessage.localImageDataPath]; if (fileInfo != nil) { TLInputMedia$inputMediaUploadedPhoto *uploadedPhoto = [[TLInputMedia$inputMediaUploadedPhoto alloc] init]; uploadedPhoto.file = fileInfo[@"file"]; uploadedPhoto.caption = localImageMessage.caption; self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:uploadedPhoto messageGuid:nil tmpId:localImageMessage.randomId replyMessageId:localImageMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else [self _fail]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedLocalVideoMessage class]]) { TGPreparedLocalVideoMessage *localVideoMessage = (TGPreparedLocalVideoMessage *)self.preparedMessage; NSDictionary *videoFileInfo = filePathToUploadedFile[[localVideoMessage localVideoPath]]; NSDictionary *thumbnailFileInfo = filePathToUploadedFile[@"embedded-data://0"]; if (videoFileInfo != nil && thumbnailFileInfo != nil) { TLInputMedia$inputMediaUploadedThumbDocument *uploadedDocument = [[TLInputMedia$inputMediaUploadedThumbDocument alloc] init]; uploadedDocument.file = videoFileInfo[@"file"]; uploadedDocument.thumb = thumbnailFileInfo[@"file"]; TLDocumentAttribute$documentAttributeVideo *video = [[TLDocumentAttribute$documentAttributeVideo alloc] init]; video.duration = (int32_t)localVideoMessage.duration; video.w = (int32_t)localVideoMessage.videoSize.width; video.h = (int32_t)localVideoMessage.videoSize.height; TLDocumentAttribute$documentAttributeFilename *filename = [[TLDocumentAttribute$documentAttributeFilename alloc] init]; filename.file_name = @"video.mp4"; uploadedDocument.attributes = @[video, filename]; uploadedDocument.caption = localVideoMessage.caption; uploadedDocument.mime_type = @"video/mp4"; self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:uploadedDocument messageGuid:nil tmpId:localVideoMessage.randomId replyMessageId:localVideoMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else [self _fail]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedLocalDocumentMessage class]]) { TGPreparedLocalDocumentMessage *localDocumentMessage = (TGPreparedLocalDocumentMessage *)self.preparedMessage; NSDictionary *documentFileInfo = filePathToUploadedFile[[[localDocumentMessage localDocumentDirectory] stringByAppendingPathComponent:[localDocumentMessage localDocumentFileName]]]; NSDictionary *thumbnailFileInfo = filePathToUploadedFile[@"embedded-data://0"]; if (documentFileInfo != nil) { id uploadedDocument = nil; if (localDocumentMessage.localThumbnailDataPath != nil && thumbnailFileInfo != nil) { TLInputMedia$inputMediaUploadedThumbDocument *thumbUploadedDocument = [[TLInputMedia$inputMediaUploadedThumbDocument alloc] init]; thumbUploadedDocument.file = documentFileInfo[@"file"]; thumbUploadedDocument.attributes = [self attributesForNativeAttributes:localDocumentMessage.attributes]; thumbUploadedDocument.mime_type = localDocumentMessage.mimeType.length == 0 ? @"application/octet-stream" : localDocumentMessage.mimeType; thumbUploadedDocument.thumb = thumbnailFileInfo[@"file"]; uploadedDocument = thumbUploadedDocument; } else { TLInputMedia$inputMediaUploadedDocument *plainUploadedDocument = [[TLInputMedia$inputMediaUploadedDocument alloc] init]; plainUploadedDocument.file = documentFileInfo[@"file"]; plainUploadedDocument.attributes = [self attributesForNativeAttributes:localDocumentMessage.attributes]; plainUploadedDocument.mime_type = localDocumentMessage.mimeType.length == 0 ? @"application/octet-stream" : localDocumentMessage.mimeType; uploadedDocument = plainUploadedDocument; } self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:uploadedDocument messageGuid:nil tmpId:localDocumentMessage.randomId replyMessageId:localDocumentMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else [self _fail]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadImageMessage class]]) { TGPreparedDownloadImageMessage *downloadImageMessage = (TGPreparedDownloadImageMessage *)self.preparedMessage; NSDictionary *fileInfo = filePathToUploadedFile[@"embedded-data://0"]; if (fileInfo != nil) { TLInputMedia$inputMediaUploadedPhoto *uploadedPhoto = [[TLInputMedia$inputMediaUploadedPhoto alloc] init]; uploadedPhoto.file = fileInfo[@"file"]; uploadedPhoto.caption = downloadImageMessage.caption; self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:uploadedPhoto messageGuid:nil tmpId:downloadImageMessage.randomId replyMessageId:downloadImageMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else [self _fail]; } else if ([self.preparedMessage isKindOfClass:[TGPreparedAssetImageMessage class]]) { TGPreparedAssetImageMessage *assetImageMessage = (TGPreparedAssetImageMessage *)self.preparedMessage; if (!assetImageMessage.document) { NSDictionary *fileInfo = filePathToUploadedFile[@"embedded-data://0"]; if (fileInfo != nil) { TLInputMedia$inputMediaUploadedPhoto *uploadedPhoto = [[TLInputMedia$inputMediaUploadedPhoto alloc] init]; uploadedPhoto.file = fileInfo[@"file"]; uploadedPhoto.caption = assetImageMessage.caption; self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:uploadedPhoto messageGuid:nil tmpId:assetImageMessage.randomId replyMessageId:assetImageMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else [self _fail]; } else { NSDictionary *documentFileInfo = filePathToUploadedFile[[[assetImageMessage localDocumentDirectory] stringByAppendingPathComponent:[assetImageMessage localDocumentFileName]]]; NSDictionary *thumbnailFileInfo = filePathToUploadedFile[@"embedded-data://0"]; if (documentFileInfo != nil) { id uploadedDocument = nil; if (assetImageMessage.localThumbnailDataPath != nil && thumbnailFileInfo != nil) { TLInputMedia$inputMediaUploadedThumbDocument *thumbUploadedDocument = [[TLInputMedia$inputMediaUploadedThumbDocument alloc] init]; thumbUploadedDocument.file = documentFileInfo[@"file"]; thumbUploadedDocument.attributes = [self attributesForNativeAttributes:assetImageMessage.attributes]; thumbUploadedDocument.mime_type = assetImageMessage.mimeType.length == 0 ? @"application/octet-stream" : assetImageMessage.mimeType; thumbUploadedDocument.thumb = thumbnailFileInfo[@"file"]; uploadedDocument = thumbUploadedDocument; } else { TLInputMedia$inputMediaUploadedDocument *plainUploadedDocument = [[TLInputMedia$inputMediaUploadedDocument alloc] init]; plainUploadedDocument.file = documentFileInfo[@"file"]; plainUploadedDocument.attributes = [self attributesForNativeAttributes:assetImageMessage.attributes]; plainUploadedDocument.mime_type = assetImageMessage.mimeType.length == 0 ? @"application/octet-stream" : assetImageMessage.mimeType; uploadedDocument = plainUploadedDocument; } self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:uploadedDocument messageGuid:nil tmpId:assetImageMessage.randomId replyMessageId:assetImageMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else [self _fail]; } } else if ([self.preparedMessage isKindOfClass:[TGPreparedAssetVideoMessage class]]) { TGPreparedAssetVideoMessage *assetVideoMessage = (TGPreparedAssetVideoMessage *)self.preparedMessage; if (!assetVideoMessage.document) { NSDictionary *videoFileInfo = filePathToUploadedFile[[assetVideoMessage localVideoPath]]; NSDictionary *thumbnailFileInfo = filePathToUploadedFile[@"embedded-data://0"]; if (videoFileInfo != nil && thumbnailFileInfo != nil) { TLInputMedia$inputMediaUploadedThumbDocument *uploadedDocument = [[TLInputMedia$inputMediaUploadedThumbDocument alloc] init]; uploadedDocument.file = videoFileInfo[@"file"]; uploadedDocument.thumb = thumbnailFileInfo[@"file"]; TLDocumentAttribute$documentAttributeVideo *video = [[TLDocumentAttribute$documentAttributeVideo alloc] init]; video.duration = (int32_t)assetVideoMessage.duration; video.w = (int32_t)assetVideoMessage.dimensions.width; video.h = (int32_t)assetVideoMessage.dimensions.height; TLDocumentAttribute$documentAttributeFilename *filename = [[TLDocumentAttribute$documentAttributeFilename alloc] init]; filename.file_name = @"video.mp4"; uploadedDocument.attributes = @[video, filename]; uploadedDocument.caption = assetVideoMessage.caption; uploadedDocument.mime_type = @"video/mp4"; self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:uploadedDocument messageGuid:nil tmpId:assetVideoMessage.randomId replyMessageId:assetVideoMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else [self _fail]; } else { NSDictionary *documentFileInfo = filePathToUploadedFile[[[assetVideoMessage localDocumentDirectory] stringByAppendingPathComponent:[assetVideoMessage localDocumentFileName]]]; NSDictionary *thumbnailFileInfo = filePathToUploadedFile[@"embedded-data://0"]; if (documentFileInfo != nil) { id uploadedDocument = nil; if (assetVideoMessage.localThumbnailDataPath != nil && thumbnailFileInfo != nil) { TLInputMedia$inputMediaUploadedThumbDocument *thumbUploadedDocument = [[TLInputMedia$inputMediaUploadedThumbDocument alloc] init]; thumbUploadedDocument.file = documentFileInfo[@"file"]; thumbUploadedDocument.attributes = [self attributesForNativeAttributes:assetVideoMessage.attributes]; thumbUploadedDocument.mime_type = assetVideoMessage.mimeType.length == 0 ? @"application/octet-stream" : assetVideoMessage.mimeType; thumbUploadedDocument.thumb = thumbnailFileInfo[@"file"]; uploadedDocument = thumbUploadedDocument; } else { TLInputMedia$inputMediaUploadedDocument *plainUploadedDocument = [[TLInputMedia$inputMediaUploadedDocument alloc] init]; plainUploadedDocument.file = documentFileInfo[@"file"]; plainUploadedDocument.attributes = [self attributesForNativeAttributes:assetVideoMessage.attributes]; plainUploadedDocument.mime_type = assetVideoMessage.mimeType.length == 0 ? @"application/octet-stream" : assetVideoMessage.mimeType; uploadedDocument = plainUploadedDocument; } self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:uploadedDocument messageGuid:nil tmpId:assetVideoMessage.randomId replyMessageId:assetVideoMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else [self _fail]; } } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadDocumentMessage class]] || [self.preparedMessage isKindOfClass:[TGPreparedCloudDocumentMessage class]]) { TGPreparedDownloadDocumentMessage *downloadDocumentMessage = (TGPreparedDownloadDocumentMessage *)self.preparedMessage; NSDictionary *documentFileInfo = filePathToUploadedFile[@"embedded-data://0"]; NSDictionary *thumbnailFileInfo = filePathToUploadedFile[@"embedded-data://1"]; if (documentFileInfo != nil) { id uploadedDocument = nil; if (thumbnailFileInfo != nil) { TLInputMedia$inputMediaUploadedThumbDocument *thumbUploadedDocument = [[TLInputMedia$inputMediaUploadedThumbDocument alloc] init]; thumbUploadedDocument.file = documentFileInfo[@"file"]; thumbUploadedDocument.attributes = [self attributesForNativeAttributes:downloadDocumentMessage.attributes]; thumbUploadedDocument.mime_type = downloadDocumentMessage.mimeType.length == 0 ? @"application/octet-stream" : downloadDocumentMessage.mimeType; thumbUploadedDocument.thumb = thumbnailFileInfo[@"file"]; uploadedDocument = thumbUploadedDocument; } else { TLInputMedia$inputMediaUploadedDocument *plainUploadedDocument = [[TLInputMedia$inputMediaUploadedDocument alloc] init]; plainUploadedDocument.file = documentFileInfo[@"file"]; plainUploadedDocument.attributes = [self attributesForNativeAttributes:downloadDocumentMessage.attributes]; plainUploadedDocument.mime_type = downloadDocumentMessage.mimeType.length == 0 ? @"application/octet-stream" : downloadDocumentMessage.mimeType; uploadedDocument = plainUploadedDocument; } self.cancelToken = [TGTelegraphInstance doConversationSendMedia:_conversationId accessHash:_accessHash media:uploadedDocument messageGuid:nil tmpId:downloadDocumentMessage.randomId replyMessageId:downloadDocumentMessage.replyMessage.mid postAsChannel:_postAsChannel notifyMembers:_notifyMembers actor:self]; } else [self _fail]; } else [self _fail]; [super uploadsCompleted:filePathToUploadedFile]; } #pragma mark - - (void)conversationSendMessageRequestSuccess:(id)result { if ([result isKindOfClass:[TLUpdates$updateShortSentMessage class]]) { TLUpdates$updateShortSentMessage *sentMessage = result; std::vector flags; flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagDeliveryState, .value = TGMessageDeliveryStateDelivered}); flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagMid, .value = sentMessage.n_id}); flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagDate, .value = sentMessage.date}); flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagPts, .value = sentMessage.pts}); bool unread = true; if (_conversationId > 0) { TGUser *user = [TGDatabaseInstance() loadUser:(int)_conversationId]; if (user.kind == TGUserKindBot || user.kind == TGUserKindSmartBot) { flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagUnread, .value = 0}); unread = false; } } TGMessage *updatedMessage = nil; if ([sentMessage.media isKindOfClass:[TLMessageMedia$messageMediaWebPage class]]) { updatedMessage = [[TGDatabaseInstance() loadMessageWithMid:self.preparedMessage.mid peerId:_conversationId] copy]; if (updatedMessage != nil) { NSMutableArray *attachments = [[NSMutableArray alloc] initWithArray:updatedMessage.mediaAttachments]; for (id attachment in attachments) { if ([attachment isKindOfClass:[TGWebPageMediaAttachment class]]) { [attachments removeObject:attachment]; break; } } [attachments addObjectsFromArray:[TGMessage parseTelegraphMedia:sentMessage.media]]; updatedMessage.mediaAttachments = attachments; } } NSArray *entities = [TGMessage parseTelegraphEntities:sentMessage.entities]; if (entities.count != 0) { TGMessageEntitiesAttachment *entitiesAttachment = [[TGMessageEntitiesAttachment alloc] init]; entitiesAttachment.entities = entities; NSMutableArray *attachments = [[NSMutableArray alloc] initWithArray:updatedMessage.mediaAttachments]; for (id attachment in attachments) { if ([attachment isKindOfClass:[TGMessageEntitiesAttachment class]]) { [attachments removeObject:attachment]; break; } } [attachments addObject:entitiesAttachment]; updatedMessage.mediaAttachments = attachments; } [TGDatabaseInstance() updateMessage:self.preparedMessage.mid peerId:_conversationId flags:flags media:updatedMessage.mediaAttachments dispatch:true]; if (self.preparedMessage.randomId != 0) [TGDatabaseInstance() removeTempIds:@[@(self.preparedMessage.randomId)]]; if (TGPeerIdIsChannel(_conversationId)) { [TGChannelManagementSignals updateChannelState:_conversationId pts:sentMessage.pts ptsCount:sentMessage.pts_count]; } else { [[TGTelegramNetworking instance] updatePts:sentMessage.pts ptsCount:sentMessage.pts_count seq:0]; } NSMutableDictionary *resultDict = [[NSMutableDictionary alloc] init]; resultDict[@"previousMid"] = @(self.preparedMessage.mid); resultDict[@"mid"] = @(sentMessage.n_id); resultDict[@"date"] = @(sentMessage.date); if (updatedMessage != nil) resultDict[@"message"] = updatedMessage; resultDict[@"unread"] = @(unread); resultDict[@"pts"] = @(sentMessage.pts); if (updatedMessage == nil) { updatedMessage = [[self.preparedMessage message] copy]; updatedMessage.mid = sentMessage.n_id; updatedMessage.date = sentMessage.date; updatedMessage.outgoing = true; updatedMessage.fromUid = TGTelegraphInstance.clientUserId; updatedMessage.unread = unread; updatedMessage.pts = sentMessage.pts; } [self afterMessageSent:updatedMessage]; int64_t conversationId = _conversationId; id resource = [[SGraphObjectNode alloc] initWithObject:[[NSArray alloc] initWithObjects:[[NSNumber alloc] initWithInt:self.preparedMessage.mid], updatedMessage, nil]]; [self _success:resultDict]; [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/tg/conversation/(%lld)/messagesChanged", conversationId] resource:resource]; } else if ([result isKindOfClass:[TLUpdates class]]) { TLUpdates *updates = result; TLMessage *updateMessage = updates.messages.firstObject; int32_t pts = 1; for (id update in updates.updatesList) { if ([update isKindOfClass:[TLUpdate$updateNewChannelMessage class]]) { pts = ((TLUpdate$updateNewChannelMessage *)update).pts; } } int32_t date = 0; if ([updateMessage isKindOfClass:[TLMessage$modernMessage class]]) date = ((TLMessage$message *)updateMessage).date; else if ([updateMessage isKindOfClass:[TLMessage$modernMessageService class]]) date = ((TLMessage$modernMessageService *)updateMessage).date; bool waitForFileQueue = false; TGMessage *message = [[TGMessage alloc] initWithTelegraphMessageDesc:updateMessage]; message.pts = pts; if (message == nil) [self _fail]; else { if ([self.preparedMessage isKindOfClass:[TGPreparedLocalImageMessage class]]) { TGPreparedLocalImageMessage *localImageMessage = (TGPreparedLocalImageMessage *)self.preparedMessage; NSMutableArray *imageFilePaths = [[NSMutableArray alloc] init]; if (localImageMessage.localImageDataPath != nil) [imageFilePaths addObject:localImageMessage.localImageDataPath]; if (localImageMessage.localThumbnailDataPath != nil) [imageFilePaths addObject:localImageMessage.localThumbnailDataPath]; for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGImageMediaAttachment class]]) { TGImageMediaAttachment *imageAttachment = (TGImageMediaAttachment *)attachment; NSString *imageUrl = [imageAttachment.imageInfo closestImageUrlWithSize:localImageMessage.imageSize resultingSize:NULL]; if (imageUrl != nil && localImageMessage.localImageDataPath != nil) { [[TGRemoteImageView sharedCache] moveToCache:[self pathForLocalImagePath:localImageMessage.localImageDataPath] cacheUrl:imageUrl]; [imageFilePaths removeObject:localImageMessage.localImageDataPath]; [TGImageDownloadActor addUrlRewrite:localImageMessage.localImageDataPath newUrl:imageUrl]; waitForFileQueue = true; } NSString *thumbnailUrl = [imageAttachment.imageInfo closestImageUrlWithSize:localImageMessage.thumbnailSize resultingSize:NULL]; if (thumbnailUrl != nil && localImageMessage.localThumbnailDataPath != nil) { [[TGRemoteImageView sharedCache] moveToCache:[self pathForLocalImagePath:localImageMessage.localThumbnailDataPath] cacheUrl:thumbnailUrl]; [imageFilePaths removeObject:localImageMessage.localThumbnailDataPath]; [TGImageDownloadActor addUrlRewrite:localImageMessage.localThumbnailDataPath newUrl:thumbnailUrl]; waitForFileQueue = true; } if (localImageMessage.assetUrl.length != 0) [TGImageDownloadActor addServerMediaSataForAssetUrl:localImageMessage.assetUrl attachment:imageAttachment]; [TGDatabaseInstance() updateLastUseDateForMediaType:2 mediaId:imageAttachment.imageId messageId:message.mid]; break; } } if (imageFilePaths.count != 0) { NSMutableArray *absolutePathsToRemove = [[NSMutableArray alloc] init]; for (NSString *path in imageFilePaths) { [absolutePathsToRemove addObject:[self pathForLocalImagePath:path]]; } dispatch_async([TGCache diskCacheQueue], ^ { for (NSString *path in absolutePathsToRemove) { [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; } }); } } else if ([self.preparedMessage isKindOfClass:[TGPreparedLocalVideoMessage class]]) { TGPreparedLocalVideoMessage *localVideoMessage = (TGPreparedLocalVideoMessage *)self.preparedMessage; NSMutableArray *dataFilePaths = [[NSMutableArray alloc] init]; if (localVideoMessage.localThumbnailDataPath != nil) [dataFilePaths addObject:localVideoMessage.localThumbnailDataPath]; if ([localVideoMessage localVideoPath] != nil) [dataFilePaths addObject:[localVideoMessage localVideoPath]]; for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGVideoMediaAttachment class]]) { TGVideoMediaAttachment *videoAttachment = (TGVideoMediaAttachment *)attachment; NSString *documentsDirectory = [TGAppDelegate documentsPath]; NSString *videosDirectory = [documentsDirectory stringByAppendingPathComponent:@"video"]; if (![[NSFileManager defaultManager] fileExistsAtPath:videosDirectory]) [[NSFileManager defaultManager] createDirectoryAtPath:videosDirectory withIntermediateDirectories:true attributes:nil error:nil]; NSString *updatedVideoPath = [videosDirectory stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"remote%llx.mov", videoAttachment.videoId]]; [[NSFileManager defaultManager] moveItemAtPath:[localVideoMessage localVideoPath] toPath:updatedVideoPath error:nil]; [dataFilePaths removeObject:[localVideoMessage localVideoPath]]; NSString *remoteUrl = [videoAttachment.videoInfo urlWithQuality:1 actualQuality:NULL actualSize:NULL]; if (remoteUrl != nil) { [TGVideoDownloadActor rewriteLocalFilePath:[[NSString alloc] initWithFormat:@"local-video:local%llx.mov", localVideoMessage.localVideoId] remoteUrl:remoteUrl]; } [[TGRemoteImageView sharedCache] changeCacheItemUrl:[[NSString alloc] initWithFormat:@"video-thumbnail-local%llx.jpg", localVideoMessage.localVideoId] newUrl:[[NSString alloc] initWithFormat:@"video-thumbnail-remote%llx.jpg", videoAttachment.videoId]]; NSString *thumbnailUrl = [videoAttachment.thumbnailInfo closestImageUrlWithSize:localVideoMessage.thumbnailSize resultingSize:NULL]; if (thumbnailUrl != nil && localVideoMessage.localThumbnailDataPath != nil) { [[TGRemoteImageView sharedCache] moveToCache:[self pathForLocalImagePath:localVideoMessage.localThumbnailDataPath] cacheUrl:thumbnailUrl]; [dataFilePaths removeObject:localVideoMessage.localThumbnailDataPath]; [TGImageDownloadActor addUrlRewrite:localVideoMessage.localThumbnailDataPath newUrl:thumbnailUrl]; } if (localVideoMessage.assetUrl.length != 0) [TGImageDownloadActor addServerMediaSataForAssetUrl:localVideoMessage.assetUrl attachment:videoAttachment]; [TGDatabaseInstance() updateLastUseDateForMediaType:1 mediaId:videoAttachment.videoId messageId:message.mid]; } } if (dataFilePaths.count != 0) { NSMutableArray *absolutePathsToRemove = [[NSMutableArray alloc] init]; for (NSString *path in dataFilePaths) { [absolutePathsToRemove addObject:[self pathForLocalImagePath:path]]; } dispatch_async([TGCache diskCacheQueue], ^ { for (NSString *path in absolutePathsToRemove) { [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; } }); } } else if ([self.preparedMessage isKindOfClass:[TGPreparedLocalDocumentMessage class]]) { TGPreparedLocalDocumentMessage *localDocumentMessage = (TGPreparedLocalDocumentMessage *)self.preparedMessage; NSMutableArray *dataFilePaths = [[NSMutableArray alloc] init]; if (localDocumentMessage.localThumbnailDataPath != nil) [dataFilePaths addObject:localDocumentMessage.localThumbnailDataPath]; if ([localDocumentMessage localDocumentDirectory] != nil) [dataFilePaths addObject:[localDocumentMessage localDocumentDirectory]]; for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { TGDocumentMediaAttachment *documentAttachment = (TGDocumentMediaAttachment *)attachment; if (documentAttachment.thumbnailInfo != nil && localDocumentMessage.localThumbnailDataPath != nil) { NSString *thumbnailUri = [[documentAttachment thumbnailInfo] imageUrlForLargestSize:NULL]; if (thumbnailUri != nil) { [[TGRemoteImageView sharedCache] moveToCache:[self pathForLocalImagePath:localDocumentMessage.localThumbnailDataPath] cacheUrl:thumbnailUri]; [dataFilePaths removeObject:localDocumentMessage.localThumbnailDataPath]; } } NSString *updatedDocumentDirectory = [TGPreparedLocalDocumentMessage localDocumentDirectoryForDocumentId:documentAttachment.documentId]; [[NSFileManager defaultManager] removeItemAtPath:updatedDocumentDirectory error:nil]; [[NSFileManager defaultManager] moveItemAtPath:[localDocumentMessage localDocumentDirectory] toPath:updatedDocumentDirectory error:nil]; [TGDatabaseInstance() updateLastUseDateForMediaType:3 mediaId:documentAttachment.documentId messageId:message.mid]; } } if (dataFilePaths.count != 0) { NSMutableArray *absolutePathsToRemove = [[NSMutableArray alloc] init]; for (NSString *path in dataFilePaths) { [absolutePathsToRemove addObject:[self pathForLocalImagePath:path]]; } dispatch_async([TGCache diskCacheQueue], ^ { for (NSString *path in absolutePathsToRemove) { [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; } }); } } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadImageMessage class]]) { TGPreparedDownloadImageMessage *downloadImageMessage = (TGPreparedDownloadImageMessage *)self.preparedMessage; for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGImageMediaAttachment class]]) { TGImageMediaAttachment *imageAttachment = (TGImageMediaAttachment *)attachment; NSString *localImageUrl = [downloadImageMessage.imageInfo imageUrlForLargestSize:NULL]; NSString *localImageDirectory = [[self filePathForLocalImageUrl:localImageUrl] stringByDeletingLastPathComponent]; NSString *updatedImageDirectory = [[self filePathForRemoteImageId:imageAttachment.imageId] stringByDeletingLastPathComponent]; [[NSFileManager defaultManager] removeItemAtPath:updatedImageDirectory error:nil]; [[NSFileManager defaultManager] moveItemAtPath:localImageDirectory toPath:updatedImageDirectory error:nil]; [TGModernSendCommonMessageActor setRemoteImageForRemoteUrl:localImageUrl image:imageAttachment]; [TGDatabaseInstance() updateLastUseDateForMediaType:2 mediaId:imageAttachment.imageId messageId:message.mid]; break; } } } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadDocumentMessage class]]) { TGPreparedDownloadDocumentMessage *downloadDocumentMessage = (TGPreparedDownloadDocumentMessage *)self.preparedMessage; for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { TGDocumentMediaAttachment *documentAttachment = (TGDocumentMediaAttachment *)attachment; NSString *updatedDocumentDirectory = [TGPreparedLocalDocumentMessage localDocumentDirectoryForDocumentId:documentAttachment.documentId]; NSString *localDirectory = [TGPreparedLocalDocumentMessage localDocumentDirectoryForLocalDocumentId:downloadDocumentMessage.localDocumentId]; [[NSFileManager defaultManager] removeItemAtPath:updatedDocumentDirectory error:nil]; [[NSFileManager defaultManager] moveItemAtPath:localDirectory toPath:updatedDocumentDirectory error:nil]; if (downloadDocumentMessage.giphyId != nil) [TGModernSendCommonMessageActor setRemoteDocumentForGiphyId:downloadDocumentMessage.giphyId document:documentAttachment]; [TGDatabaseInstance() updateLastUseDateForMediaType:3 mediaId:documentAttachment.documentId messageId:message.mid]; } } } else if ([self.preparedMessage isKindOfClass:[TGPreparedAssetImageMessage class]]) { TGPreparedAssetImageMessage *assetImageMessage = (TGPreparedAssetImageMessage *)self.preparedMessage; if (!assetImageMessage.document) { for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGImageMediaAttachment class]]) { TGImageMediaAttachment *imageAttachment = (TGImageMediaAttachment *)attachment; NSString *localImageUrl = [assetImageMessage.imageInfo imageUrlForLargestSize:NULL]; NSString *localImageDirectory = [[self filePathForLocalImageUrl:localImageUrl] stringByDeletingLastPathComponent]; NSString *updatedImageDirectory = [[self filePathForRemoteImageId:imageAttachment.imageId] stringByDeletingLastPathComponent]; [[NSFileManager defaultManager] removeItemAtPath:updatedImageDirectory error:nil]; [[NSFileManager defaultManager] moveItemAtPath:localImageDirectory toPath:updatedImageDirectory error:nil]; [TGModernSendCommonMessageActor setRemoteImageForRemoteUrl:localImageUrl image:imageAttachment]; if (assetImageMessage.useMediaCache && assetImageMessage.imageHash.length != 0) [TGImageDownloadActor addServerMediaSataForAssetUrl:assetImageMessage.imageHash attachment:imageAttachment]; [TGDatabaseInstance() updateLastUseDateForMediaType:2 mediaId:imageAttachment.imageId messageId:message.mid]; } } } else { NSMutableArray *dataFilePaths = [[NSMutableArray alloc] init]; if (assetImageMessage.localThumbnailDataPath != nil) [dataFilePaths addObject:assetImageMessage.localThumbnailDataPath]; if ([assetImageMessage localDocumentDirectory] != nil) [dataFilePaths addObject:[assetImageMessage localDocumentDirectory]]; for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { TGDocumentMediaAttachment *documentAttachment = (TGDocumentMediaAttachment *)attachment; if (documentAttachment.thumbnailInfo != nil && assetImageMessage.localThumbnailDataPath != nil) { NSString *thumbnailUri = [[documentAttachment thumbnailInfo] imageUrlForLargestSize:NULL]; if (thumbnailUri != nil) { [[TGRemoteImageView sharedCache] moveToCache:[self pathForLocalImagePath:assetImageMessage.localThumbnailDataPath] cacheUrl:thumbnailUri]; [dataFilePaths removeObject:assetImageMessage.localThumbnailDataPath]; } } NSString *updatedDocumentDirectory = [TGPreparedLocalDocumentMessage localDocumentDirectoryForDocumentId:documentAttachment.documentId]; [[NSFileManager defaultManager] removeItemAtPath:updatedDocumentDirectory error:nil]; [[NSFileManager defaultManager] moveItemAtPath:[assetImageMessage localDocumentDirectory] toPath:updatedDocumentDirectory error:nil]; [TGDatabaseInstance() updateLastUseDateForMediaType:3 mediaId:documentAttachment.documentId messageId:message.mid]; } } if (dataFilePaths.count != 0) { NSMutableArray *absolutePathsToRemove = [[NSMutableArray alloc] init]; for (NSString *path in dataFilePaths) { [absolutePathsToRemove addObject:[self pathForLocalImagePath:path]]; } dispatch_async([TGCache diskCacheQueue], ^ { for (NSString *path in absolutePathsToRemove) { [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; } }); } } } else if ([self.preparedMessage isKindOfClass:[TGPreparedAssetVideoMessage class]]) { TGPreparedAssetVideoMessage *assetVideoMessage = (TGPreparedAssetVideoMessage *)self.preparedMessage; if (!assetVideoMessage.document) { NSMutableArray *dataFilePaths = [[NSMutableArray alloc] init]; if (assetVideoMessage.localThumbnailDataPath != nil) [dataFilePaths addObject:assetVideoMessage.localThumbnailDataPath]; if ([assetVideoMessage localVideoPath] != nil) [dataFilePaths addObject:[assetVideoMessage localVideoPath]]; for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGVideoMediaAttachment class]]) { TGVideoMediaAttachment *videoAttachment = (TGVideoMediaAttachment *)attachment; NSString *documentsDirectory = [TGAppDelegate documentsPath]; NSString *videosDirectory = [documentsDirectory stringByAppendingPathComponent:@"video"]; if (![[NSFileManager defaultManager] fileExistsAtPath:videosDirectory]) [[NSFileManager defaultManager] createDirectoryAtPath:videosDirectory withIntermediateDirectories:true attributes:nil error:nil]; NSString *updatedVideoPath = [videosDirectory stringByAppendingPathComponent:[[NSString alloc] initWithFormat:@"remote%llx.mov", videoAttachment.videoId]]; [[NSFileManager defaultManager] moveItemAtPath:[assetVideoMessage localVideoPath] toPath:updatedVideoPath error:nil]; [dataFilePaths removeObject:[assetVideoMessage localVideoPath]]; NSString *remoteUrl = [videoAttachment.videoInfo urlWithQuality:1 actualQuality:NULL actualSize:NULL]; if (remoteUrl != nil) { [TGVideoDownloadActor rewriteLocalFilePath:[[NSString alloc] initWithFormat:@"local-video:local%llx.mov", assetVideoMessage.localVideoId] remoteUrl:remoteUrl]; } [[TGRemoteImageView sharedCache] changeCacheItemUrl:[[NSString alloc] initWithFormat:[assetVideoMessage localThumbnailDataPath], assetVideoMessage.localVideoId] newUrl:[[NSString alloc] initWithFormat:@"video-thumbnail-remote%llx.jpg", videoAttachment.videoId]]; NSString *thumbnailUrl = [videoAttachment.thumbnailInfo closestImageUrlWithSize:CGSizeZero resultingSize:NULL]; if (thumbnailUrl != nil && assetVideoMessage.localThumbnailDataPath != nil) { [[TGRemoteImageView sharedCache] moveToCache:[self pathForLocalImagePath:assetVideoMessage.localThumbnailDataPath] cacheUrl:thumbnailUrl]; [dataFilePaths removeObject:assetVideoMessage.localThumbnailDataPath]; [TGImageDownloadActor addUrlRewrite:assetVideoMessage.localThumbnailDataPath newUrl:thumbnailUrl]; } if (assetVideoMessage.useMediaCache && assetVideoMessage.videoHash.length != 0) [TGImageDownloadActor addServerMediaSataForAssetUrl:assetVideoMessage.videoHash attachment:videoAttachment]; [TGDatabaseInstance() updateLastUseDateForMediaType:1 mediaId:videoAttachment.videoId messageId:message.mid]; } } if (dataFilePaths.count != 0) { NSMutableArray *absolutePathsToRemove = [[NSMutableArray alloc] init]; for (NSString *path in dataFilePaths) { [absolutePathsToRemove addObject:[self pathForLocalImagePath:path]]; } dispatch_async([TGCache diskCacheQueue], ^ { for (NSString *path in absolutePathsToRemove) { [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; } }); } } else { NSMutableArray *dataFilePaths = [[NSMutableArray alloc] init]; if (assetVideoMessage.localThumbnailDataPath != nil) [dataFilePaths addObject:assetVideoMessage.localThumbnailDataPath]; if ([assetVideoMessage localDocumentDirectory] != nil) [dataFilePaths addObject:[assetVideoMessage localDocumentDirectory]]; for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { TGDocumentMediaAttachment *documentAttachment = (TGDocumentMediaAttachment *)attachment; if (documentAttachment.thumbnailInfo != nil && assetVideoMessage.localThumbnailDataPath != nil) { NSString *thumbnailUri = [[documentAttachment thumbnailInfo] imageUrlForLargestSize:NULL]; if (thumbnailUri != nil) { [[TGRemoteImageView sharedCache] moveToCache:[self pathForLocalImagePath:assetVideoMessage.localThumbnailDataPath] cacheUrl:thumbnailUri]; [dataFilePaths removeObject:assetVideoMessage.localThumbnailDataPath]; } } NSString *updatedDocumentDirectory = [TGPreparedLocalDocumentMessage localDocumentDirectoryForDocumentId:documentAttachment.documentId]; [[NSFileManager defaultManager] removeItemAtPath:updatedDocumentDirectory error:nil]; [[NSFileManager defaultManager] moveItemAtPath:[assetVideoMessage localDocumentDirectory] toPath:updatedDocumentDirectory error:nil]; [TGDatabaseInstance() updateLastUseDateForMediaType:3 mediaId:documentAttachment.documentId messageId:message.mid]; } } if (dataFilePaths.count != 0) { NSMutableArray *absolutePathsToRemove = [[NSMutableArray alloc] init]; for (NSString *path in dataFilePaths) { [absolutePathsToRemove addObject:[self pathForLocalImagePath:path]]; } dispatch_async([TGCache diskCacheQueue], ^ { for (NSString *path in absolutePathsToRemove) { [[NSFileManager defaultManager] removeItemAtPath:path error:nil]; } }); } } } else if ([self.preparedMessage isKindOfClass:[TGPreparedCloudDocumentMessage class]]) { TGPreparedCloudDocumentMessage *cloudDocumentMessage = (TGPreparedCloudDocumentMessage *)self.preparedMessage; for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { TGDocumentMediaAttachment *documentAttachment = (TGDocumentMediaAttachment *)attachment; NSString *updatedDocumentDirectory = [TGPreparedLocalDocumentMessage localDocumentDirectoryForDocumentId:documentAttachment.documentId]; NSString *localDirectory = [TGPreparedLocalDocumentMessage localDocumentDirectoryForLocalDocumentId:cloudDocumentMessage.localDocumentId]; [[NSFileManager defaultManager] removeItemAtPath:updatedDocumentDirectory error:nil]; [[NSFileManager defaultManager] moveItemAtPath:localDirectory toPath:updatedDocumentDirectory error:nil]; [TGDatabaseInstance() updateLastUseDateForMediaType:3 mediaId:documentAttachment.documentId messageId:message.mid]; } } } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadExternalGifMessage class]]) { TGPreparedDownloadExternalGifMessage *externalGifMessage = (TGPreparedDownloadExternalGifMessage *)self.preparedMessage; NSString *previousFileName = nil; for (id attribute in externalGifMessage.attributes) { if ([attribute isKindOfClass:[TGDocumentAttributeFilename class]]) { previousFileName = ((TGDocumentAttributeFilename *)attribute).filename; break; } } for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { TGDocumentMediaAttachment *documentAttachment = (TGDocumentMediaAttachment *)attachment; NSString *updatedDocumentDirectory = [TGPreparedLocalDocumentMessage localDocumentDirectoryForDocumentId:documentAttachment.documentId]; NSString *previousDocumentDirectory = [TGPreparedLocalDocumentMessage localDocumentDirectoryForLocalDocumentId:externalGifMessage.localDocumentId]; [[NSFileManager defaultManager] createDirectoryAtPath:updatedDocumentDirectory withIntermediateDirectories:true attributes:nil error:NULL]; for (NSString *fileName in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:previousDocumentDirectory error:nil]) { NSString *updatedFileName = fileName; if (previousFileName != nil && [previousFileName isEqualToString:fileName]) { updatedFileName = documentAttachment.safeFileName; } [[NSFileManager defaultManager] copyItemAtPath:[previousDocumentDirectory stringByAppendingPathComponent:fileName] toPath:[updatedDocumentDirectory stringByAppendingPathComponent:updatedFileName] error:nil]; } [[NSFileManager defaultManager] removeItemAtPath:previousDocumentDirectory error:nil]; [TGDatabaseInstance() updateLastUseDateForMediaType:3 mediaId:documentAttachment.documentId messageId:message.mid]; } } } else if ([self.preparedMessage isKindOfClass:[TGPreparedDownloadExternalImageMessage class]]) { TGPreparedDownloadExternalImageMessage *externalImageMessage = (TGPreparedDownloadExternalImageMessage *)self.preparedMessage; for (TGMediaAttachment *attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGImageMediaAttachment class]]) { TGImageMediaAttachment *imageAttachment = (TGImageMediaAttachment *)attachment; NSString *localImageUrl = [externalImageMessage.imageInfo imageUrlForLargestSize:NULL]; NSString *localImageDirectory = [[self filePathForLocalImageUrl:localImageUrl] stringByDeletingLastPathComponent]; NSString *updatedImageDirectory = [[self filePathForRemoteImageId:imageAttachment.imageId] stringByDeletingLastPathComponent]; [[NSFileManager defaultManager] createDirectoryAtPath:updatedImageDirectory withIntermediateDirectories:true attributes:nil error:NULL]; for (NSString *fileName in [[NSFileManager defaultManager] contentsOfDirectoryAtPath:localImageDirectory error:nil]) { NSString *updatedFileName = fileName; [[NSFileManager defaultManager] copyItemAtPath:[localImageDirectory stringByAppendingPathComponent:fileName] toPath:[updatedImageDirectory stringByAppendingPathComponent:updatedFileName] error:nil]; } [[NSFileManager defaultManager] removeItemAtPath:localImageDirectory error:nil]; [TGModernSendCommonMessageActor setRemoteImageForRemoteUrl:localImageUrl image:imageAttachment]; [TGDatabaseInstance() updateLastUseDateForMediaType:2 mediaId:imageAttachment.imageId messageId:message.mid]; break; } } } std::vector flags; flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagDeliveryState, .value = TGMessageDeliveryStateDelivered}); int32_t maxPts = 0; [updates maxPtsAndCount:&maxPts ptsCount:NULL]; flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagPts, .value = maxPts}); bool unread = true; if (TGPeerIdIsChannel(_conversationId)) { flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagUnread, .value = 0}); unread = false; } else { if (_conversationId > 0) { TGUser *user = [TGDatabaseInstance() loadUser:(int)_conversationId]; if (user.kind == TGUserKindBot || user.kind == TGUserKindSmartBot) { flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagUnread, .value = 0}); unread = false; } } } flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagMid, .value = updateMessage.n_id}); if (date != 0) flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagDate, .value = date}); [TGDatabaseInstance() updateMessage:self.preparedMessage.mid peerId:_conversationId flags:flags media:message.mediaAttachments dispatch:true]; if (self.preparedMessage.randomId != 0) [TGDatabaseInstance() removeTempIds:@[@(self.preparedMessage.randomId)]]; int64_t conversationId = _conversationId; id resource = [[SGraphObjectNode alloc] initWithObject:[[NSArray alloc] initWithObjects:[[NSNumber alloc] initWithInt:self.preparedMessage.mid], message, nil]]; dispatch_block_t completion = ^{ [self _success:@{ @"previousMid": @(self.preparedMessage.mid), @"mid": @(updateMessage.n_id), @"date": @(date), @"message": message, @"unread": @(unread), @"pts": @(message.pts) }]; [self afterMessageSent:message]; [ActionStageInstance() dispatchResource:[[NSString alloc] initWithFormat:@"/tg/conversation/(%lld)/messagesChanged", conversationId] resource:resource]; [[TGTelegramNetworking instance] addUpdates:updates]; }; if (waitForFileQueue) dispatch_async([TGCache diskCacheQueue], completion); else completion(); } } else [self _fail]; } - (void)afterMessageSent:(TGMessage *)message { for (id attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGForwardedMessageMediaAttachment class]]) { return; } } for (id attachment in message.mediaAttachments) { if ([attachment isKindOfClass:[TGDocumentMediaAttachment class]]) { TGDocumentMediaAttachment *document = attachment; if ([document isAnimated] && ([document.mimeType isEqualToString:@"video/mp4"])) { if (document.documentId != 0) { [TGRecentGifsSignal addRemoteRecentGifFromDocuments:@[document]]; } } break; } } } - (void)conversationSendMessageQuickAck { if (_shouldPostAlmostDeliveredMessage) { std::vector flags; flags.push_back((TGDatabaseMessageFlagValue){.flag = TGDatabaseMessageFlagDeliveryState, .value = TGMessageDeliveryStateDelivered}); [TGDatabaseInstance() updateMessage:self.preparedMessage.mid peerId:_conversationId flags:flags media:nil dispatch:true]; [ActionStageInstance() dispatchMessageToWatchers:self.path messageType:@"messageAlmostDelivered" message:@{ @"previousMid": @(self.preparedMessage.mid) }]; } } - (void)conversationSendMessageRequestFailed:(NSString *)errorText { if ([errorText isEqualToString:@"PEER_FLOOD"]) { TGDispatchOnMainThread(^{ static CFAbsoluteTime lastErrorTime = 0.0; if (CFAbsoluteTimeGetCurrent() - lastErrorTime >= 1.0) { lastErrorTime = CFAbsoluteTimeGetCurrent(); [[[TGAlertView alloc] initWithTitle:nil message:TGLocalized(@"Conversation.SendMessageErrorFlood") cancelButtonTitle:TGLocalized(@"Generic.ErrorMoreInfo") okButtonTitle:TGLocalized(@"Common.OK") completionBlock:^(bool okButtonPressed) { if (!okButtonPressed) { [[UIApplication sharedApplication] openURL:[NSURL URLWithString:@"https://telegram.org/faq#can-39t-send-messages-to-non-contacts"]]; } }] show]; } }); } [self _fail]; } @end