mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-19 20:21:26 +00:00
Merge branch 'group_calls' of https://github.com/peter-iakovlev/TelegramCoreDev into group_calls
This commit is contained in:
commit
c353091c51
@ -32,8 +32,6 @@
|
||||
C25638021E79E7FC00311607 /* TwoStepVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */; };
|
||||
C26A37EF1E5E0C41006977AC /* ChannelParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BB7C591E5C8074001527C3 /* ChannelParticipants.swift */; };
|
||||
C27982511E72C97800262BFD /* MacosLegacy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27982501E72C97800262BFD /* MacosLegacy.swift */; };
|
||||
C27982531E73077800262BFD /* SecretChatStateBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27982521E73077800262BFD /* SecretChatStateBridge.swift */; };
|
||||
C27982541E73078400262BFD /* SecretChatStateBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = C27982521E73077800262BFD /* SecretChatStateBridge.swift */; };
|
||||
C28725421EF967E700613564 /* NotificationInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */; };
|
||||
C28725431EF967E700613564 /* NotificationInfoMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */; };
|
||||
C29340F31F5080FA0074991E /* UpdateGroupSpecificStickerset.swift in Sources */ = {isa = PBXBuildFile; fileRef = C29340F21F5080FA0074991E /* UpdateGroupSpecificStickerset.swift */; };
|
||||
@ -103,6 +101,11 @@
|
||||
D0177B7B1DF8A16C00A5083A /* SecretChatState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */; };
|
||||
D018D3371E648ACF00C5E089 /* CreateChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* CreateChannel.swift */; };
|
||||
D018D3381E648ACF00C5E089 /* CreateChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018D3361E648ACF00C5E089 /* CreateChannel.swift */; };
|
||||
D018EE002044939F00CBB130 /* SecretApiLayer73.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */; };
|
||||
D018EE0220458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */; };
|
||||
D018EE0320458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */; };
|
||||
D018EE052045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */; };
|
||||
D018EE062045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */; };
|
||||
D019B1CC1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */; };
|
||||
D019B1CD1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */ = {isa = PBXBuildFile; fileRef = D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */; };
|
||||
D01A21A61F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01A21A51F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift */; };
|
||||
@ -556,6 +559,8 @@
|
||||
D0E652201E3A364A004EEA91 /* UpdateAccountPeerName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */; };
|
||||
D0E817492010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */; };
|
||||
D0E8174A2010E7E300B82BBB /* ChannelAdminEventLogContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */; };
|
||||
D0E8B8B32044706300605593 /* ForwardGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8B8B22044706300605593 /* ForwardGame.swift */; };
|
||||
D0E8B8B42044706300605593 /* ForwardGame.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8B8B22044706300605593 /* ForwardGame.swift */; };
|
||||
D0F02CE51E9926C40065DEE2 /* ManagedConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */; };
|
||||
D0F02CE61E9926C50065DEE2 /* ManagedConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */; };
|
||||
D0F3A89F1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */; };
|
||||
@ -588,6 +593,8 @@
|
||||
D0F8C39E20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */; };
|
||||
D0F8C3A02017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */; };
|
||||
D0F8C3A12017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */; };
|
||||
D0FA08BB2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */; };
|
||||
D0FA08BC2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */; };
|
||||
D0FA0ABD1E76C908005BB9B7 /* TwoStepVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */; };
|
||||
D0FA35051EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; };
|
||||
D0FA35061EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; };
|
||||
@ -644,7 +651,6 @@
|
||||
C23BC3861E9BE3CA00D79F92 /* ImportContact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportContact.swift; sourceTree = "<group>"; };
|
||||
C251D7421E65E50500283EDE /* StickerSetInstallation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerSetInstallation.swift; sourceTree = "<group>"; };
|
||||
C27982501E72C97800262BFD /* MacosLegacy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MacosLegacy.swift; sourceTree = "<group>"; };
|
||||
C27982521E73077800262BFD /* SecretChatStateBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatStateBridge.swift; sourceTree = "<group>"; };
|
||||
C28725411EF967E700613564 /* NotificationInfoMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationInfoMessageAttribute.swift; sourceTree = "<group>"; };
|
||||
C29340F21F5080FA0074991E /* UpdateGroupSpecificStickerset.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateGroupSpecificStickerset.swift; sourceTree = "<group>"; };
|
||||
C2E064671ECEEF0A00387BB8 /* TelegramMediaInvoice.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaInvoice.swift; sourceTree = "<group>"; };
|
||||
@ -674,6 +680,9 @@
|
||||
D017495F1E118FC30057C89A /* AccountIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountIntermediateState.swift; sourceTree = "<group>"; };
|
||||
D0177B7A1DF8A16C00A5083A /* SecretChatState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatState.swift; sourceTree = "<group>"; };
|
||||
D018D3361E648ACF00C5E089 /* CreateChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateChannel.swift; sourceTree = "<group>"; };
|
||||
D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretApiLayer73.swift; sourceTree = "<group>"; };
|
||||
D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretChatLayerNegotiation.swift; sourceTree = "<group>"; };
|
||||
D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CkeckPeerChatServiceActions.swift; sourceTree = "<group>"; };
|
||||
D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatRekeySession.swift; sourceTree = "<group>"; };
|
||||
D01A21A51F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeSavedStickersOperation.swift; sourceTree = "<group>"; };
|
||||
D01A21A81F38CDDC00DDA104 /* ManagedSynchronizeSavedStickersOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeSavedStickersOperations.swift; sourceTree = "<group>"; };
|
||||
@ -938,6 +947,7 @@
|
||||
D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingChatContextResultMessageAttribute.swift; sourceTree = "<group>"; };
|
||||
D0E6521E1E3A364A004EEA91 /* UpdateAccountPeerName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateAccountPeerName.swift; sourceTree = "<group>"; };
|
||||
D0E817482010E7E300B82BBB /* ChannelAdminEventLogContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAdminEventLogContext.swift; sourceTree = "<group>"; };
|
||||
D0E8B8B22044706300605593 /* ForwardGame.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ForwardGame.swift; sourceTree = "<group>"; };
|
||||
D0F02CE41E9926C40065DEE2 /* ManagedConfigurationUpdates.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedConfigurationUpdates.swift; sourceTree = "<group>"; };
|
||||
D0F3A89E1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronizeChatInputStateOperation.swift; sourceTree = "<group>"; };
|
||||
D0F3A8A11E82C65E00B4C64C /* ManagedSynchronizeChatInputStateOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeChatInputStateOperations.swift; sourceTree = "<group>"; };
|
||||
@ -949,6 +959,7 @@
|
||||
D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = "<group>"; };
|
||||
D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupFeedPeers.swift; sourceTree = "<group>"; };
|
||||
D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalTelegramCoreConfiguration.swift; sourceTree = "<group>"; };
|
||||
D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentPrivacySettings.swift; sourceTree = "<group>"; };
|
||||
D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoStepVerification.swift; sourceTree = "<group>"; };
|
||||
D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheStorageSettings.swift; sourceTree = "<group>"; };
|
||||
D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectCacheUsageStats.swift; sourceTree = "<group>"; };
|
||||
@ -1020,6 +1031,7 @@
|
||||
D01C7ED51EF5E468008305F1 /* ProxySettings.swift */,
|
||||
D0B167221F9F972E00976B40 /* LoggingSettings.swift */,
|
||||
D0AF32301FACEDEC0097362B /* CoreSettings.swift */,
|
||||
D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */,
|
||||
);
|
||||
name = Settings;
|
||||
sourceTree = "<group>";
|
||||
@ -1300,6 +1312,7 @@
|
||||
children = (
|
||||
D0FA8BB51E223C16001E855B /* SecretApiLayer8.swift */,
|
||||
D0448C981E268F9A005A61A7 /* SecretApiLayer46.swift */,
|
||||
D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */,
|
||||
D03B0D541D631A6900955575 /* Api.swift */,
|
||||
D03B0D551D631A6900955575 /* Buffer.swift */,
|
||||
D03B0D561D631A6900955575 /* Download.swift */,
|
||||
@ -1364,6 +1377,7 @@
|
||||
D0642EF81F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift */,
|
||||
D0DB7F021F43030C00591D48 /* InstallInteractiveReadMessagesAction.swift */,
|
||||
D0B85AC41F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift */,
|
||||
D0E8B8B22044706300605593 /* ForwardGame.swift */,
|
||||
);
|
||||
name = Messages;
|
||||
sourceTree = "<group>";
|
||||
@ -1595,6 +1609,7 @@
|
||||
D02395D51F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift */,
|
||||
D0C44B601FC616E200227BE0 /* SearchGroupMembers.swift */,
|
||||
D0F8C39C20178B9B00236FC5 /* GroupFeedPeers.swift */,
|
||||
D018EE042045E95000CBB130 /* CkeckPeerChatServiceActions.swift */,
|
||||
);
|
||||
name = Peers;
|
||||
sourceTree = "<group>";
|
||||
@ -1615,7 +1630,7 @@
|
||||
D0FA8BA91E1FB76E001E855B /* ManagedSecretChatOutgoingOperations.swift */,
|
||||
D019B1CB1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift */,
|
||||
D00C7CEA1E37A8540080C3D5 /* SetSecretChatMessageAutoremoveTimeoutInteractively.swift */,
|
||||
C27982521E73077800262BFD /* SecretChatStateBridge.swift */,
|
||||
D018EE0120458E1E00CBB130 /* SecretChatLayerNegotiation.swift */,
|
||||
);
|
||||
name = "Secret Chats";
|
||||
sourceTree = "<group>";
|
||||
@ -1827,11 +1842,13 @@
|
||||
D03B0CBD1D62234300955575 /* Regex.swift in Sources */,
|
||||
D00BDA191EE593D600C64C5E /* TelegramChannelAdminRights.swift in Sources */,
|
||||
D0B843B91DA7FF30005F29E1 /* NBMetadataCoreTest.m in Sources */,
|
||||
D018EE002044939F00CBB130 /* SecretApiLayer73.swift in Sources */,
|
||||
D09A2FE61D7CD4940018FB72 /* TelegramChannel.swift in Sources */,
|
||||
D03B0D0E1D62255C00955575 /* UpdateGroup.swift in Sources */,
|
||||
D053B4181F18DE4F00E2D58A /* AuthorSignatureMessageAttribute.swift in Sources */,
|
||||
D0F3A89F1E82C65400B4C64C /* SynchronizeChatInputStateOperation.swift in Sources */,
|
||||
D01AC9231DD5E9A200E8160F /* ApplyUpdateMessage.swift in Sources */,
|
||||
D0E8B8B32044706300605593 /* ForwardGame.swift in Sources */,
|
||||
D01A21AC1F38D10E00DDA104 /* SavedStickerItem.swift in Sources */,
|
||||
D0642EF91F3E05D700792790 /* EarliestUnseenPersonalMentionMessage.swift in Sources */,
|
||||
D03B0CF71D62250800955575 /* TelegramMediaImage.swift in Sources */,
|
||||
@ -2042,6 +2059,7 @@
|
||||
D05A32E41E6F0B2E002760B4 /* RecentAccountSessions.swift in Sources */,
|
||||
D01A21A61F38CDC700DDA104 /* SynchronizeSavedStickersOperation.swift in Sources */,
|
||||
D03E5E0C1E55E02D0029569A /* LoggedOutAccountAttribute.swift in Sources */,
|
||||
D018EE052045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */,
|
||||
D0F3A8A51E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */,
|
||||
D03B0CD71D62245300955575 /* TelegramGroup.swift in Sources */,
|
||||
D0B8438C1DA7CF50005F29E1 /* BotInfo.swift in Sources */,
|
||||
@ -2053,18 +2071,19 @@
|
||||
D0F3A8A81E82CD7D00B4C64C /* UpdatePeerChatInterfaceState.swift in Sources */,
|
||||
D03B0CE21D62249B00955575 /* InlineBotMessageAttribute.swift in Sources */,
|
||||
D0AB0B9A1D666520002C78E7 /* ManagedSynchronizePeerReadStates.swift in Sources */,
|
||||
C27982531E73077800262BFD /* SecretChatStateBridge.swift in Sources */,
|
||||
D03B0D5B1D631A6900955575 /* Buffer.swift in Sources */,
|
||||
D0B843891DA7AB96005F29E1 /* ExportedInvitation.swift in Sources */,
|
||||
D03B0E441D631E6600955575 /* NetworkLogging.m in Sources */,
|
||||
D0528E651E65C82400E2FEF5 /* UpdateContactName.swift in Sources */,
|
||||
D03121021DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift in Sources */,
|
||||
D018EE0220458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */,
|
||||
D0C48F391E8138DF0075317D /* ArchivedStickerPacksInfo.swift in Sources */,
|
||||
C239BE971E62EE1E00C2C453 /* LoadMessagesIfNecessary.swift in Sources */,
|
||||
D03B0CC11D62235000955575 /* StringFormat.swift in Sources */,
|
||||
D0B85AC51F6B2B9400B8B5CE /* RecentlyUsedHashtags.swift in Sources */,
|
||||
D0B843C31DA7FF30005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */,
|
||||
C2366C861E4F403C0097CCFF /* AddressNames.swift in Sources */,
|
||||
D0FA08BB2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */,
|
||||
D0F8C39D20178B9B00236FC5 /* GroupFeedPeers.swift in Sources */,
|
||||
D0B843C11DA7FF30005F29E1 /* NBPhoneMetaData.m in Sources */,
|
||||
D0528E601E65B94E00E2FEF5 /* SingleMessageView.swift in Sources */,
|
||||
@ -2106,7 +2125,6 @@
|
||||
files = (
|
||||
C29340F41F5081280074991E /* UpdateGroupSpecificStickerset.swift in Sources */,
|
||||
C25638021E79E7FC00311607 /* TwoStepVerification.swift in Sources */,
|
||||
C27982541E73078400262BFD /* SecretChatStateBridge.swift in Sources */,
|
||||
D00DBBD81E64E41100DB5485 /* CreateSecretChat.swift in Sources */,
|
||||
C2FD33EC1E696C79008D13D4 /* GroupsInCommon.swift in Sources */,
|
||||
C239BE9D1E630CB300C2C453 /* UpdatePinnedMessage.swift in Sources */,
|
||||
@ -2144,6 +2162,7 @@
|
||||
D0B418B81D7E05A6004562A4 /* ContactManagement.swift in Sources */,
|
||||
D0E23DE01E8082A400B9B6D2 /* ArchivedStickerPacks.swift in Sources */,
|
||||
D050F2521E4A59C200988324 /* JoinLink.swift in Sources */,
|
||||
D018EE0320458E1E00CBB130 /* SecretChatLayerNegotiation.swift in Sources */,
|
||||
D0F7B1E91E045C87007EB8A5 /* PeerCommands.swift in Sources */,
|
||||
D00D97C81E32901700E5C2B6 /* PeerInputActivity.swift in Sources */,
|
||||
D0754D2B1EEE10FC00884F6E /* BotPaymentForm.swift in Sources */,
|
||||
@ -2215,6 +2234,7 @@
|
||||
D0FA35091EA632E400E56FFA /* CollectCacheUsageStats.swift in Sources */,
|
||||
D001F3F31E128A1C007A8C60 /* UpdateMessageService.swift in Sources */,
|
||||
D0C50E351E93A86600F62E39 /* CallSessionManager.swift in Sources */,
|
||||
D018EE062045E95000CBB130 /* CkeckPeerChatServiceActions.swift in Sources */,
|
||||
D0B8442D1DAB91E0005F29E1 /* NBMetadataCoreTest.m in Sources */,
|
||||
D0C27B431F4B58C000A4E170 /* PeerSpecificStickerPack.swift in Sources */,
|
||||
D0B844131DAB91CD005F29E1 /* StringFormat.swift in Sources */,
|
||||
@ -2283,6 +2303,7 @@
|
||||
C210DD631FBDB90800F673D8 /* SourceReferenceMessageAttribute.swift in Sources */,
|
||||
D0B8440F1DAB91CD005F29E1 /* Either.swift in Sources */,
|
||||
D0DC35511DE36908000195EB /* RequestChatContextResults.swift in Sources */,
|
||||
D0FA08BC2046B37900DD23FC /* ContentPrivacySettings.swift in Sources */,
|
||||
D08CAA8D1ED81EDF0000FDA8 /* Localizations.swift in Sources */,
|
||||
D0F7B1EC1E045C87007EB8A5 /* SearchPeers.swift in Sources */,
|
||||
D001F3EF1E128A1C007A8C60 /* AccountIntermediateState.swift in Sources */,
|
||||
@ -2292,6 +2313,7 @@
|
||||
D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */,
|
||||
D0BC387C1E40D2880044D6FE /* TogglePeerChatPinned.swift in Sources */,
|
||||
D0528E6B1E65DD2100E2FEF5 /* WebpagePreview.swift in Sources */,
|
||||
D0E8B8B42044706300605593 /* ForwardGame.swift in Sources */,
|
||||
D0B844111DAB91CD005F29E1 /* Regex.swift in Sources */,
|
||||
D0B844321DAB91E0005F29E1 /* NBPhoneMetaDataGenerator.m in Sources */,
|
||||
D0BEAF5E1E54941B00BD963D /* Authorization.swift in Sources */,
|
||||
|
||||
@ -253,6 +253,7 @@ private var declaredEncodables: Void = {
|
||||
declareEncodable(LoggingSettings.self, f: { LoggingSettings(decoder: $0) })
|
||||
declareEncodable(CachedLocalizationInfos.self, f: { CachedLocalizationInfos(decoder: $0) })
|
||||
declareEncodable(SynchronizeGroupedPeersOperation.self, f: { SynchronizeGroupedPeersOperation(decoder: $0) })
|
||||
declareEncodable(ContentPrivacySettings.self, f: { ContentPrivacySettings(decoder: $0) })
|
||||
|
||||
return
|
||||
}()
|
||||
|
||||
@ -701,7 +701,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
|
||||
case let .webPageEmpty(id):
|
||||
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
||||
default:
|
||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
|
||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
|
||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||
}
|
||||
}
|
||||
@ -838,7 +838,7 @@ private func finalStateWithUpdates(account: Account, state: AccountMutableState,
|
||||
case let .webPageEmpty(id):
|
||||
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
||||
default:
|
||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
|
||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
|
||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||
}
|
||||
}
|
||||
@ -1539,7 +1539,7 @@ private func pollChannel(_ account: Account, peer: Peer, state: AccountMutableSt
|
||||
case let .webPageEmpty(id):
|
||||
updatedState.updateMedia(MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), media: nil)
|
||||
default:
|
||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage) {
|
||||
if let webpage = telegramMediaWebpageFromApiWebpage(apiWebpage, url: nil) {
|
||||
updatedState.updateMedia(webpage.webpageId, media: webpage)
|
||||
}
|
||||
}
|
||||
@ -1975,7 +1975,7 @@ func replayFinalState(accountPeerId: PeerId, mediaBox: MediaBox, modifier: Modif
|
||||
peerIdsWithAddedSecretMessages.insert(peerId)
|
||||
}
|
||||
case let .ReadSecretOutbox(peerId, maxTimestamp, actionTimestamp):
|
||||
applyOutgoingReadMaxIndex(modifier: modifier, index: MessageIndex.upperBound(peerId: peerId, timestamp: maxTimestamp, namespace: Namespaces.Message.Local), beginAt: actionTimestamp)
|
||||
applyOutgoingReadMaxIndex(modifier: modifier, index: MessageIndex.upperBound(peerId: peerId, timestamp: maxTimestamp, namespace: Namespaces.Message.Local), beginCountdownAt: actionTimestamp)
|
||||
case let .AddPeerInputActivity(chatPeerId, peerId, activity):
|
||||
if let peerId = peerId {
|
||||
if updatedTypingActivities[chatPeerId] == nil {
|
||||
|
||||
@ -44,11 +44,46 @@ public func applyMaxReadIndexInteractively(postbox: Postbox, network: Network, s
|
||||
}
|
||||
}
|
||||
|
||||
func applyOutgoingReadMaxIndex(modifier: Modifier, index: MessageIndex, beginAt timestamp: Int32) {
|
||||
func applyOutgoingReadMaxIndex(modifier: Modifier, index: MessageIndex, beginCountdownAt timestamp: Int32) {
|
||||
let messageIds = modifier.applyOutgoingReadMaxIndex(index)
|
||||
if index.id.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
for id in messageIds {
|
||||
applySecretOutgoingMessageReadActions(modifier: modifier, id: id, beginCountdownAt: timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func maybeReadSecretOutgoingMessage(modifier: Modifier, index: MessageIndex) {
|
||||
guard index.id.peerId.namespace == Namespaces.Peer.SecretChat else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
guard index.id.namespace == Namespaces.Message.Local else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
guard let combinedState = modifier.getCombinedPeerReadState(index.id.peerId) else {
|
||||
return
|
||||
}
|
||||
|
||||
if combinedState.isOutgoingMessageIndexRead(index) {
|
||||
applySecretOutgoingMessageReadActions(modifier: modifier, id: index.id, beginCountdownAt: index.timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
func applySecretOutgoingMessageReadActions(modifier: Modifier, id: MessageId, beginCountdownAt timestamp: Int32) {
|
||||
guard id.peerId.namespace == Namespaces.Peer.SecretChat else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
guard id.namespace == Namespaces.Message.Local else {
|
||||
assertionFailure()
|
||||
return
|
||||
}
|
||||
|
||||
if let message = modifier.getMessage(id), !message.flags.contains(.Incoming) {
|
||||
if message.flags.intersection([.Unsent, .Sending, .Failed]).isEmpty {
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
||||
if (attribute.countdownBeginTime == nil || attribute.countdownBeginTime == 0) && !message.containsSecretMedia {
|
||||
@ -74,4 +109,3 @@ func applyOutgoingReadMaxIndex(modifier: Modifier, index: MessageIndex, beginAt
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +55,10 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
||||
var sentStickers: [TelegramMediaFile] = []
|
||||
var sentGifs: [TelegramMediaFile] = []
|
||||
|
||||
if let updatedTimestamp = updatedTimestamp {
|
||||
modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([message.id]), timestamp: updatedTimestamp)
|
||||
}
|
||||
|
||||
modifier.updateMessage(message.id, update: { currentMessage in
|
||||
let updatedId: MessageId
|
||||
if let messageId = messageId {
|
||||
@ -140,9 +144,6 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
||||
|
||||
return .update(StoreMessage(id: updatedId, globallyUniqueId: nil, groupingKey: currentMessage.groupingKey, timestamp: updatedTimestamp ?? currentMessage.timestamp, flags: [], tags: tags, globalTags: globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: text, attributes: attributes, media: media))
|
||||
})
|
||||
if let updatedTimestamp = updatedTimestamp {
|
||||
modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, timestamp: updatedTimestamp)
|
||||
}
|
||||
for file in sentStickers {
|
||||
modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20)
|
||||
}
|
||||
@ -198,6 +199,10 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
|
||||
|
||||
var updatedGroupingKey: Int64?
|
||||
|
||||
if let latestIndex = mapping.last?.1 {
|
||||
modifier.offsetPendingMessagesTimestamps(lowerBound: latestIndex.id, excludeIds: Set(mapping.map { $0.0.id }), timestamp: latestIndex.timestamp)
|
||||
}
|
||||
|
||||
for (message, _, updatedMessage) in mapping {
|
||||
modifier.updateMessage(message.id, update: { currentMessage in
|
||||
let updatedId: MessageId
|
||||
@ -259,10 +264,6 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
|
||||
modifier.updateMessageGroupingKeysAtomically(mapping.map { $0.1.id }, groupingKey: updatedGroupingKey)
|
||||
}
|
||||
|
||||
if let latestIndex = mapping.last?.1 {
|
||||
modifier.offsetPendingMessagesTimestamps(lowerBound: latestIndex.id, timestamp: latestIndex.timestamp)
|
||||
}
|
||||
|
||||
for file in sentStickers {
|
||||
modifier.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 20)
|
||||
}
|
||||
|
||||
@ -138,7 +138,7 @@ public struct BotPaymentForm {
|
||||
public let canSaveCredentials: Bool
|
||||
public let passwordMissing: Bool
|
||||
public let invoice: BotPaymentInvoice
|
||||
public let providerId: Int32
|
||||
public let providerId: PeerId
|
||||
public let url: String
|
||||
public let nativeProvider: BotPaymentNativeProvider?
|
||||
public let savedInfo: BotPaymentRequestedInfo?
|
||||
@ -229,7 +229,7 @@ public func fetchBotPaymentForm(postbox: Postbox, network: Network, messageId: M
|
||||
parsedSavedCredentials = .card(id: id, title: title)
|
||||
}
|
||||
}
|
||||
return BotPaymentForm(canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, providerId: providerId, url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials)
|
||||
return BotPaymentForm(canSaveCredentials: (flags & (1 << 2)) != 0, passwordMissing: (flags & (1 << 3)) != 0, invoice: parsedInvoice, providerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: providerId), url: url, nativeProvider: parsedNativeProvider, savedInfo: parsedSavedInfo, savedCredentials: parsedSavedCredentials)
|
||||
}
|
||||
} |> mapError { _ -> BotPaymentFormRequestError in return .generic }
|
||||
}
|
||||
|
||||
@ -9,12 +9,47 @@ import Foundation
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public enum CallSessionError {
|
||||
public enum CallSessionError: Equatable {
|
||||
case generic
|
||||
case privacyRestricted
|
||||
case notSupportedByPeer
|
||||
case serverProvided(String)
|
||||
case disconnected
|
||||
|
||||
public static func ==(lhs: CallSessionError, rhs: CallSessionError) -> Bool {
|
||||
switch lhs {
|
||||
case .generic:
|
||||
if case .generic = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .privacyRestricted:
|
||||
if case .privacyRestricted = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .notSupportedByPeer:
|
||||
if case .notSupportedByPeer = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .serverProvided(text):
|
||||
if case .serverProvided(text) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case .disconnected:
|
||||
if case .disconnected = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum CallSessionEndedType {
|
||||
@ -23,9 +58,26 @@ public enum CallSessionEndedType {
|
||||
case missed
|
||||
}
|
||||
|
||||
public enum CallSessionTerminationReason {
|
||||
public enum CallSessionTerminationReason: Equatable {
|
||||
case ended(CallSessionEndedType)
|
||||
case error(CallSessionError)
|
||||
|
||||
public static func ==(lhs: CallSessionTerminationReason, rhs: CallSessionTerminationReason) -> Bool {
|
||||
switch lhs {
|
||||
case let .ended(type):
|
||||
if case .ended(type) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
case let .error(error):
|
||||
if case .error(error) = rhs {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ReportCallRating {
|
||||
@ -127,6 +179,8 @@ private final class CallSessionContext {
|
||||
var state: CallSessionInternalState
|
||||
let subscribers = Bag<(CallSession) -> Void>()
|
||||
|
||||
let acknowledgeIncomingCallDisposable = MetaDisposable()
|
||||
|
||||
var isEmpty: Bool {
|
||||
if case .terminated = self.state {
|
||||
return self.subscribers.isEmpty
|
||||
@ -140,6 +194,10 @@ private final class CallSessionContext {
|
||||
self.isOutgoing = isOutgoing
|
||||
self.state = state
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.acknowledgeIncomingCallDisposable.dispose()
|
||||
}
|
||||
}
|
||||
|
||||
private final class CallSessionManagerContext {
|
||||
@ -252,7 +310,9 @@ private final class CallSessionManagerContext {
|
||||
|
||||
if randomStatus == 0 {
|
||||
let internalId = CallSessionInternalId()
|
||||
self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: false, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b))
|
||||
let context = CallSessionContext(peerId: peerId, isOutgoing: false, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b))
|
||||
self.contexts[internalId] = context
|
||||
context.acknowledgeIncomingCallDisposable.set(self.network.request(Api.functions.phone.receivedCall(peer: .inputPhoneCall(id: stableId, accessHash: accessHash))).start())
|
||||
self.contextIdByStableId[stableId] = internalId
|
||||
self.contextUpdated(internalId: internalId)
|
||||
self.ringingStatesUpdated()
|
||||
|
||||
21
TelegramCore/CkeckPeerChatServiceActions.swift
Normal file
21
TelegramCore/CkeckPeerChatServiceActions.swift
Normal file
@ -0,0 +1,21 @@
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public func checkPeerChatServiceActions(postbox: Postbox, peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return postbox.modify { modifier -> Void in
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
if let state = modifier.getPeerChatState(peerId) as? SecretChatState {
|
||||
let updatedState = secretChatCheckLayerNegotiationIfNeeded(modifier: modifier, peerId: peerId, state: state)
|
||||
if state != updatedState {
|
||||
modifier.setPeerChatState(peerId, state: updatedState)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
66
TelegramCore/ContentPrivacySettings.swift
Normal file
66
TelegramCore/ContentPrivacySettings.swift
Normal file
@ -0,0 +1,66 @@
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
import MtProtoKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
|
||||
public final class ContentPrivacySettings: PreferencesEntry, Equatable {
|
||||
public let enableSecretChatWebpagePreviews: Bool?
|
||||
|
||||
public static var defaultSettings = ContentPrivacySettings(enableSecretChatWebpagePreviews: nil)
|
||||
|
||||
public init(enableSecretChatWebpagePreviews: Bool?) {
|
||||
self.enableSecretChatWebpagePreviews = enableSecretChatWebpagePreviews
|
||||
}
|
||||
|
||||
public init(decoder: PostboxDecoder) {
|
||||
self.enableSecretChatWebpagePreviews = decoder.decodeOptionalInt32ForKey("enableSecretChatWebpagePreviews").flatMap { $0 != 0 }
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
if let enableSecretChatWebpagePreviews = self.enableSecretChatWebpagePreviews {
|
||||
encoder.encodeInt32(enableSecretChatWebpagePreviews ? 1 : 0, forKey: "enableSecretChatWebpagePreviews")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "enableSecretChatWebpagePreviews")
|
||||
}
|
||||
}
|
||||
|
||||
public func withUpdatedEnableSecretChatWebpagePreviews(_ enableSecretChatWebpagePreviews: Bool) -> ContentPrivacySettings {
|
||||
return ContentPrivacySettings(enableSecretChatWebpagePreviews: enableSecretChatWebpagePreviews)
|
||||
}
|
||||
|
||||
public func isEqual(to: PreferencesEntry) -> Bool {
|
||||
guard let to = to as? ContentPrivacySettings else {
|
||||
return false
|
||||
}
|
||||
|
||||
return self == to
|
||||
}
|
||||
|
||||
public static func ==(lhs: ContentPrivacySettings, rhs: ContentPrivacySettings) -> Bool {
|
||||
if lhs.enableSecretChatWebpagePreviews != rhs.enableSecretChatWebpagePreviews {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
public func updateContentPrivacySettings(postbox: Postbox, _ f: @escaping (ContentPrivacySettings) -> ContentPrivacySettings) -> Signal<Void, NoError> {
|
||||
return postbox.modify { modifier -> Void in
|
||||
var updated: ContentPrivacySettings?
|
||||
modifier.updatePreferencesEntry(key: PreferencesKeys.contentPrivacySettings, { current in
|
||||
if let current = current as? ContentPrivacySettings {
|
||||
updated = f(current)
|
||||
return updated
|
||||
} else {
|
||||
updated = f(ContentPrivacySettings.defaultSettings)
|
||||
return updated
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -36,7 +36,7 @@ public func deleteMessagesInteractively(postbox: Postbox, messageIds: [MessageId
|
||||
case .basicLayer:
|
||||
layer = .layer8
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
||||
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||
}
|
||||
if let layer = layer {
|
||||
var globallyUniqueIds: [Int64] = []
|
||||
@ -84,7 +84,7 @@ public func clearHistoryInteractively(postbox: Postbox, peerId: PeerId) -> Signa
|
||||
case .basicLayer:
|
||||
layer = .layer8
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
||||
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||
}
|
||||
|
||||
if let layer = layer {
|
||||
|
||||
27
TelegramCore/ForwardGame.swift
Normal file
27
TelegramCore/ForwardGame.swift
Normal file
@ -0,0 +1,27 @@
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
public func forwardGameWithScore(account: Account, messageId: MessageId, to peerId: PeerId) -> Signal<Void, NoError> {
|
||||
return account.postbox.modify { modifier -> Signal<Void, NoError> in
|
||||
if let message = modifier.getMessage(messageId), let fromPeer = modifier.getPeer(messageId.peerId), let fromInputPeer = apiInputPeer(fromPeer), let toPeer = modifier.getPeer(peerId), let toInputPeer = apiInputPeer(toPeer) {
|
||||
return account.network.request(Api.functions.messages.forwardMessages(flags: 1 << 8, fromPeer: fromInputPeer, id: [messageId.id], randomId: [arc4random64()], toPeer: toInputPeer))
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<Api.Updates?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { updates -> Signal<Void, NoError> in
|
||||
if let updates = updates {
|
||||
account.stateManager.addUpdates(updates)
|
||||
}
|
||||
return .complete()
|
||||
}
|
||||
}
|
||||
return .complete()
|
||||
} |> switchToLatest
|
||||
}
|
||||
@ -6,21 +6,41 @@ import Foundation
|
||||
#endif
|
||||
|
||||
public class InlineBotMessageAttribute: MessageAttribute {
|
||||
public let peerId: PeerId
|
||||
public let peerId: PeerId?
|
||||
public let title: String?
|
||||
|
||||
public var associatedPeerIds: [PeerId] {
|
||||
return [self.peerId]
|
||||
if let peerId = self.peerId {
|
||||
return [peerId]
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
init(peerId: PeerId) {
|
||||
init(peerId: PeerId?, title: String?) {
|
||||
self.peerId = peerId
|
||||
self.title = title
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.peerId = PeerId(decoder.decodeInt64ForKey("i", orElse: 0))
|
||||
if let peerId = decoder.decodeOptionalInt64ForKey("i") {
|
||||
self.peerId = PeerId(peerId)
|
||||
} else {
|
||||
self.peerId = nil
|
||||
}
|
||||
self.title = decoder.decodeOptionalStringForKey("t")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt64(self.peerId.toInt64(), forKey: "i")
|
||||
if let peerId = self.peerId {
|
||||
encoder.encodeInt64(peerId.toInt64(), forKey: "i")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "i")
|
||||
}
|
||||
if let title = self.title {
|
||||
encoder.encodeString(title, forKey: "t")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "t")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,7 +234,7 @@ private func initialHandshakeAccept(postbox: Postbox, network: Network, peerId:
|
||||
case .basicLayer:
|
||||
layer = .layer8
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
||||
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||
}
|
||||
if let layer = layer {
|
||||
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: layer, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
|
||||
@ -326,6 +326,7 @@ private func pfsAcceptKey(postbox: Postbox, network: Network, peerId: PeerId, la
|
||||
private enum BoxedDecryptedMessage {
|
||||
case layer8(SecretApi8.DecryptedMessage)
|
||||
case layer46(SecretApi46.DecryptedMessage)
|
||||
case layer73(SecretApi73.DecryptedMessage)
|
||||
|
||||
func serialize(_ buffer: Buffer, role: SecretChatRole, sequenceInfo: SecretChatOperationSequenceInfo?) {
|
||||
switch self {
|
||||
@ -350,6 +351,26 @@ private enum BoxedDecryptedMessage {
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
let _ = message.serialize(buffer, true)
|
||||
case let .layer73(message):
|
||||
buffer.appendInt32(0x1be31789)
|
||||
let randomBytes = malloc(15)!
|
||||
arc4random_buf(randomBytes, 15)
|
||||
serializeBytes(Buffer(memory: randomBytes, size: 15, capacity: 15, freeWhenDone: false), buffer: buffer, boxed: false)
|
||||
free(randomBytes)
|
||||
buffer.appendInt32(73)
|
||||
|
||||
if let sequenceInfo = sequenceInfo {
|
||||
let inSeqNo = sequenceInfo.topReceivedOperationIndex * 2 + (role == .creator ? 0 : 1)
|
||||
let outSeqNo = sequenceInfo.operationIndex * 2 + (role == .creator ? 1 : 0)
|
||||
buffer.appendInt32(inSeqNo)
|
||||
buffer.appendInt32(outSeqNo)
|
||||
} else {
|
||||
buffer.appendInt32(0)
|
||||
buffer.appendInt32(0)
|
||||
assertionFailure()
|
||||
}
|
||||
|
||||
let _ = message.serialize(buffer, true)
|
||||
}
|
||||
}
|
||||
@ -450,8 +471,93 @@ private func decryptedAttributes46(_ attributes: [TelegramMediaFileAttribute]) -
|
||||
return result
|
||||
}
|
||||
|
||||
private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, uploadedFile: SecretChatOutgoingFile?, layer: SecretChatLayer) -> BoxedDecryptedMessage {
|
||||
var media: Media? = message.media.first
|
||||
private func decryptedAttributes73(_ attributes: [TelegramMediaFileAttribute]) -> [SecretApi73.DocumentAttribute] {
|
||||
var result: [SecretApi73.DocumentAttribute] = []
|
||||
for attribute in attributes {
|
||||
switch attribute {
|
||||
case let .FileName(fileName):
|
||||
result.append(.documentAttributeFilename(fileName: fileName))
|
||||
case .Animated:
|
||||
result.append(.documentAttributeAnimated)
|
||||
case let .Sticker(displayText, packReference, _):
|
||||
var stickerSet: SecretApi73.InputStickerSet = .inputStickerSetEmpty
|
||||
if let packReference = packReference, case let .name(name) = packReference {
|
||||
stickerSet = .inputStickerSetShortName(shortName: name)
|
||||
}
|
||||
result.append(.documentAttributeSticker(alt: displayText, stickerset: stickerSet))
|
||||
case let .ImageSize(size):
|
||||
result.append(.documentAttributeImageSize(w: Int32(size.width), h: Int32(size.height)))
|
||||
case let .Video(duration, size, videoFlags):
|
||||
var flags: Int32 = 0
|
||||
if videoFlags.contains(.instantRoundVideo) {
|
||||
flags |= 1 << 0
|
||||
}
|
||||
result.append(.documentAttributeVideo(flags: flags, duration: Int32(duration), w: Int32(size.width), h: Int32(size.height)))
|
||||
case let .Audio(isVoice, duration, title, performer, waveform):
|
||||
var flags: Int32 = 0
|
||||
if isVoice {
|
||||
flags |= (1 << 10)
|
||||
}
|
||||
if let _ = title {
|
||||
flags |= Int32(1 << 0)
|
||||
}
|
||||
if let _ = performer {
|
||||
flags |= Int32(1 << 1)
|
||||
}
|
||||
var waveformBuffer: Buffer?
|
||||
if let waveform = waveform {
|
||||
flags |= Int32(1 << 2)
|
||||
waveformBuffer = Buffer(data: waveform.makeData())
|
||||
}
|
||||
result.append(.documentAttributeAudio(flags: flags, duration: Int32(duration), title: title, performer: performer, waveform: waveformBuffer))
|
||||
case .HasLinkedStickers:
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func decryptedEntities73(_ entities: [MessageTextEntity]?) -> [SecretApi73.MessageEntity]? {
|
||||
guard let entities = entities else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result: [SecretApi73.MessageEntity] = []
|
||||
for entity in entities {
|
||||
switch entity.type {
|
||||
case .Unknown:
|
||||
break
|
||||
case .Mention:
|
||||
result.append(.messageEntityMention(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
||||
case .Hashtag:
|
||||
result.append(.messageEntityHashtag(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
||||
case .BotCommand:
|
||||
result.append(.messageEntityBotCommand(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
||||
case .Url:
|
||||
result.append(.messageEntityUrl(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
||||
case .Email:
|
||||
result.append(.messageEntityEmail(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
||||
case .Bold:
|
||||
result.append(.messageEntityBold(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
||||
case .Italic:
|
||||
result.append(.messageEntityItalic(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
||||
case .Code:
|
||||
result.append(.messageEntityCode(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count)))
|
||||
case .Pre:
|
||||
result.append(.messageEntityPre(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count), language: ""))
|
||||
case let .TextUrl(url):
|
||||
result.append(.messageEntityTextUrl(offset: Int32(entity.range.lowerBound), length: Int32(entity.range.count), url: url))
|
||||
case .TextMention:
|
||||
break
|
||||
case .PhoneNumber:
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func boxedDecryptedMessage(modifier: Modifier, message: Message, globallyUniqueId: Int64, uploadedFile: SecretChatOutgoingFile?, thumbnailData: [MediaId: Data], layer: SecretChatLayer) -> BoxedDecryptedMessage {
|
||||
let media: Media? = message.media.first
|
||||
var messageAutoremoveTimeout: Int32 = 0
|
||||
var replyGlobalId: Int64? = nil
|
||||
var flags: Int32 = 0
|
||||
@ -465,30 +571,83 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
|
||||
}
|
||||
}
|
||||
|
||||
var viaBotName: String?
|
||||
var entities: [MessageTextEntity]?
|
||||
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
||||
messageAutoremoveTimeout = attribute.timeout
|
||||
break
|
||||
} else if let attribute = attribute as? InlineBotMessageAttribute {
|
||||
if let title = attribute.title {
|
||||
viaBotName = title
|
||||
} else if let peerId = attribute.peerId, let peer = modifier.getPeer(peerId), let addressName = peer.addressName {
|
||||
viaBotName = addressName
|
||||
}
|
||||
} else if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
entities = attribute.entities
|
||||
}
|
||||
}
|
||||
|
||||
if let media = media {
|
||||
if let image = media as? TelegramMediaImage, let uploadedFile = uploadedFile, let largestRepresentation = largestImageRepresentation(image.representations) {
|
||||
let thumbW: Int32
|
||||
let thumbH: Int32
|
||||
let thumb: Buffer
|
||||
if let smallestRepresentation = smallestImageRepresentation(image.representations), let data = thumbnailData[image.imageId] {
|
||||
thumbW = Int32(smallestRepresentation.dimensions.width)
|
||||
thumbH = Int32(smallestRepresentation.dimensions.height)
|
||||
thumb = Buffer(data: data)
|
||||
} else {
|
||||
thumbW = 90
|
||||
thumbH = 90
|
||||
thumb = Buffer()
|
||||
}
|
||||
|
||||
switch layer {
|
||||
case .layer8:
|
||||
let randomBytesData = malloc(15)!
|
||||
arc4random_buf(randomBytesData, 15)
|
||||
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
||||
|
||||
let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: Buffer(), thumbW: 90, thumbH: 90, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
|
||||
let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
|
||||
|
||||
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia))
|
||||
case .layer46:
|
||||
let decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: Buffer(), thumbW: 90, thumbH: 90, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "")
|
||||
if let _ = viaBotName {
|
||||
flags |= (1 << 11)
|
||||
}
|
||||
let decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "")
|
||||
flags |= (1 << 9)
|
||||
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId))
|
||||
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
|
||||
case .layer73:
|
||||
if let _ = viaBotName {
|
||||
flags |= (1 << 11)
|
||||
}
|
||||
let decryptedEntites = entities.flatMap(decryptedEntities73)
|
||||
if let _ = decryptedEntites {
|
||||
flags |= (1 << 7)
|
||||
}
|
||||
let decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaPhoto(thumb: thumb, thumbW: thumbW, thumbH: thumbH, w: Int32(largestRepresentation.dimensions.width), h: Int32(largestRepresentation.dimensions.height), size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), caption: "")
|
||||
flags |= (1 << 9)
|
||||
if message.groupingKey != nil {
|
||||
flags |= (1 << 17)
|
||||
}
|
||||
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
|
||||
}
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
let thumbW: Int32
|
||||
let thumbH: Int32
|
||||
let thumb: Buffer
|
||||
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let data = thumbnailData[file.fileId] {
|
||||
thumbW = Int32(smallestRepresentation.dimensions.width)
|
||||
thumbH = Int32(smallestRepresentation.dimensions.height)
|
||||
thumb = Buffer(data: data)
|
||||
} else {
|
||||
thumbW = 0
|
||||
thumbH = 0
|
||||
thumb = Buffer()
|
||||
}
|
||||
|
||||
switch layer {
|
||||
case .layer8:
|
||||
if let uploadedFile = uploadedFile {
|
||||
@ -496,7 +655,7 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
|
||||
arc4random_buf(randomBytesData, 15)
|
||||
let randomBytes = Buffer(memory: randomBytesData, size: 15, capacity: 15, freeWhenDone: true)
|
||||
|
||||
let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: Buffer(), thumbW: 0, thumbH: 0, fileName: file.fileName ?? "file", mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
|
||||
let decryptedMedia = SecretApi8.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, fileName: file.fileName ?? "file", mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
|
||||
|
||||
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: decryptedMedia))
|
||||
}
|
||||
@ -517,17 +676,98 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
|
||||
if let voiceDuration = voiceDuration {
|
||||
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: voiceDuration, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
|
||||
} else {
|
||||
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: Buffer(), thumbW: 0, thumbH: 0, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes46(file.attributes), caption: "")
|
||||
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes46(file.attributes), caption: "")
|
||||
}
|
||||
} else {
|
||||
if let resource = file.resource as? CloudDocumentMediaResource, let size = file.size {
|
||||
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: SecretApi46.PhotoSize.photoSizeEmpty(type: "s"), dcId: Int32(resource.datacenterId), attributes: decryptedAttributes46(file.attributes))
|
||||
let thumb: SecretApi46.PhotoSize
|
||||
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let thumbResource = smallestRepresentation.resource as? CloudFileMediaResource {
|
||||
thumb = .photoSize(type: "s", location: .fileLocation(dcId: Int32(thumbResource.datacenterId), volumeId: thumbResource.volumeId, localId: thumbResource.localId, secret: thumbResource.secret), w: Int32(smallestRepresentation.dimensions.width), h: Int32(smallestRepresentation.dimensions.height), size: thumbResource.size.flatMap(Int32.init) ?? 0)
|
||||
} else {
|
||||
thumb = SecretApi46.PhotoSize.photoSizeEmpty(type: "s")
|
||||
}
|
||||
decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: thumb, dcId: Int32(resource.datacenterId), attributes: decryptedAttributes46(file.attributes))
|
||||
}
|
||||
}
|
||||
|
||||
if let decryptedMedia = decryptedMedia {
|
||||
if let _ = viaBotName {
|
||||
flags |= (1 << 11)
|
||||
}
|
||||
flags |= (1 << 9)
|
||||
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId))
|
||||
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
|
||||
}
|
||||
case .layer73:
|
||||
var decryptedMedia: SecretApi73.DecryptedMessageMedia?
|
||||
|
||||
if let uploadedFile = uploadedFile {
|
||||
var voiceDuration: Int32?
|
||||
for attribute in file.attributes {
|
||||
if case let .Audio(isVoice, duration, _, _, _) = attribute {
|
||||
if isVoice {
|
||||
voiceDuration = Int32(duration)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let voiceDuration = voiceDuration {
|
||||
decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaAudio(duration: voiceDuration, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv))
|
||||
} else {
|
||||
decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaDocument(thumb: thumb, thumbW: thumbW, thumbH: thumbH, mimeType: file.mimeType, size: uploadedFile.size, key: Buffer(data: uploadedFile.key.aesKey), iv: Buffer(data: uploadedFile.key.aesIv), attributes: decryptedAttributes73(file.attributes), caption: "")
|
||||
}
|
||||
} else {
|
||||
if let resource = file.resource as? CloudDocumentMediaResource, let size = file.size {
|
||||
let thumb: SecretApi73.PhotoSize
|
||||
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations), let thumbResource = smallestRepresentation.resource as? CloudFileMediaResource {
|
||||
thumb = .photoSize(type: "s", location: .fileLocation(dcId: Int32(thumbResource.datacenterId), volumeId: thumbResource.volumeId, localId: thumbResource.localId, secret: thumbResource.secret), w: Int32(smallestRepresentation.dimensions.width), h: Int32(smallestRepresentation.dimensions.height), size: thumbResource.size.flatMap(Int32.init) ?? 0)
|
||||
} else {
|
||||
thumb = SecretApi73.PhotoSize.photoSizeEmpty(type: "s")
|
||||
}
|
||||
decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaExternalDocument(id: resource.fileId, accessHash: resource.accessHash, date: 0, mimeType: file.mimeType, size: Int32(size), thumb: thumb, dcId: Int32(resource.datacenterId), attributes: decryptedAttributes73(file.attributes))
|
||||
}
|
||||
}
|
||||
|
||||
if let decryptedMedia = decryptedMedia {
|
||||
if let _ = viaBotName {
|
||||
flags |= (1 << 11)
|
||||
}
|
||||
let decryptedEntites = entities.flatMap(decryptedEntities73)
|
||||
if let _ = decryptedEntites {
|
||||
flags |= (1 << 7)
|
||||
}
|
||||
flags |= (1 << 9)
|
||||
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
|
||||
}
|
||||
}
|
||||
} else if let webpage = media as? TelegramMediaWebpage {
|
||||
var url: String?
|
||||
if case let .Loaded(content) = webpage.content {
|
||||
url = content.url
|
||||
}
|
||||
|
||||
if let url = url, !url.isEmpty {
|
||||
switch layer {
|
||||
case .layer8:
|
||||
break
|
||||
case .layer46:
|
||||
if let _ = viaBotName {
|
||||
flags |= (1 << 11)
|
||||
}
|
||||
let decryptedMedia = SecretApi46.DecryptedMessageMedia.decryptedMessageMediaWebPage(url: url)
|
||||
flags |= (1 << 9)
|
||||
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
|
||||
case .layer73:
|
||||
if let _ = viaBotName {
|
||||
flags |= (1 << 11)
|
||||
}
|
||||
let decryptedEntites = entities.flatMap(decryptedEntities73)
|
||||
if let _ = decryptedEntites {
|
||||
flags |= (1 << 7)
|
||||
}
|
||||
let decryptedMedia = SecretApi73.DecryptedMessageMedia.decryptedMessageMediaWebPage(url: url)
|
||||
flags |= (1 << 9)
|
||||
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: decryptedMedia, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -541,7 +781,19 @@ private func boxedDecryptedMessage(message: Message, globallyUniqueId: Int64, up
|
||||
|
||||
return .layer8(.decryptedMessage(randomId: globallyUniqueId, randomBytes: randomBytes, message: message.text, media: .decryptedMessageMediaEmpty))
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: nil, viaBotName: nil, replyToRandomId: replyGlobalId))
|
||||
if let _ = viaBotName {
|
||||
flags |= (1 << 11)
|
||||
}
|
||||
return .layer46(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: nil, viaBotName: viaBotName, replyToRandomId: replyGlobalId))
|
||||
case .layer73:
|
||||
if let _ = viaBotName {
|
||||
flags |= (1 << 11)
|
||||
}
|
||||
let decryptedEntites = entities.flatMap(decryptedEntities73)
|
||||
if let _ = decryptedEntites {
|
||||
flags |= (1 << 7)
|
||||
}
|
||||
return .layer73(.decryptedMessage(flags: flags, randomId: globallyUniqueId, ttl: messageAutoremoveTimeout, message: message.text, media: .decryptedMessageMediaEmpty, entities: decryptedEntites, viaBotName: viaBotName, replyToRandomId: replyGlobalId, groupedId: message.groupingKey))
|
||||
}
|
||||
}
|
||||
|
||||
@ -557,6 +809,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionDeleteMessages(randomIds: globallyUniqueIds)))
|
||||
}
|
||||
case let .screenshotMessages(layer, actionGloballyUniqueId, globallyUniqueIds):
|
||||
switch layer {
|
||||
@ -568,6 +822,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionScreenshotMessages(randomIds: globallyUniqueIds)))
|
||||
}
|
||||
case let .clearHistory(layer, actionGloballyUniqueId):
|
||||
switch layer {
|
||||
@ -578,11 +834,15 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionFlushHistory))
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionFlushHistory))
|
||||
}
|
||||
case let .resendOperations(layer, actionGloballyUniqueId, fromSeqNo, toSeqNo):
|
||||
switch layer {
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionResend(startSeqNo: fromSeqNo, endSeqNo: toSeqNo)))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionResend(startSeqNo: fromSeqNo, endSeqNo: toSeqNo)))
|
||||
}
|
||||
case let .reportLayerSupport(layer, actionGloballyUniqueId, layerSupport):
|
||||
switch layer {
|
||||
@ -594,31 +854,43 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNotifyLayer(layer: layerSupport)))
|
||||
}
|
||||
case let .pfsRequestKey(layer, actionGloballyUniqueId, rekeySessionId, gA):
|
||||
switch layer {
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionRequestKey(exchangeId: rekeySessionId, gA: Buffer(buffer: gA))))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionRequestKey(exchangeId: rekeySessionId, gA: Buffer(buffer: gA))))
|
||||
}
|
||||
case let .pfsAcceptKey(layer, actionGloballyUniqueId, rekeySessionId, gB, keyFingerprint):
|
||||
switch layer {
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAcceptKey(exchangeId: rekeySessionId, gB: Buffer(buffer: gB), keyFingerprint: keyFingerprint)))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAcceptKey(exchangeId: rekeySessionId, gB: Buffer(buffer: gB), keyFingerprint: keyFingerprint)))
|
||||
}
|
||||
case let .pfsAbortSession(layer, actionGloballyUniqueId, rekeySessionId):
|
||||
switch layer {
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAbortKey(exchangeId: rekeySessionId)))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionAbortKey(exchangeId: rekeySessionId)))
|
||||
}
|
||||
case let .pfsCommitKey(layer, actionGloballyUniqueId, rekeySessionId, keyFingerprint):
|
||||
switch layer {
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionCommitKey(exchangeId: rekeySessionId, keyFingerprint: keyFingerprint)))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionCommitKey(exchangeId: rekeySessionId, keyFingerprint: keyFingerprint)))
|
||||
}
|
||||
case let .noop(layer, actionGloballyUniqueId):
|
||||
switch layer {
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionNoop))
|
||||
}
|
||||
case let .readMessageContents(layer, actionGloballyUniqueId, globallyUniqueIds):
|
||||
switch layer {
|
||||
@ -630,6 +902,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionReadMessages(randomIds: globallyUniqueIds)))
|
||||
}
|
||||
case let .setMessageAutoremoveTimeout(layer, actionGloballyUniqueId, timeout, _):
|
||||
switch layer {
|
||||
@ -641,6 +915,8 @@ private func boxedDecryptedSecretMessageAction(action: SecretMessageAction) -> B
|
||||
return .layer8(.decryptedMessageService(randomId: actionGloballyUniqueId, randomBytes: randomBytes, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
|
||||
case .layer46:
|
||||
return .layer46(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
|
||||
case .layer73:
|
||||
return .layer73(.decryptedMessageService(randomId: actionGloballyUniqueId, action: .decryptedMessageActionSetMessageTTL(ttlSeconds: timeout)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -681,7 +957,7 @@ private func replaceOutgoingOperationWithEmptyMessage(modifier: Modifier, peerId
|
||||
case .basicLayer:
|
||||
layer = .layer8
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
||||
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||
}
|
||||
}
|
||||
if let layer = layer {
|
||||
@ -699,11 +975,57 @@ private func replaceOutgoingOperationWithEmptyMessage(modifier: Modifier, peerId
|
||||
}
|
||||
}
|
||||
|
||||
private func resourceThumbnailData(mediaBox: MediaBox, resource: MediaResource, mediaId: MediaId) -> Signal<(MediaId, Data)?, NoError> {
|
||||
return mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false))
|
||||
|> take(1)
|
||||
|> map { data -> (MediaId, Data)? in
|
||||
if data.complete, data.size < 1024 * 16, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) {
|
||||
return (mediaId, content)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func messageWithThumbnailData(mediaBox: MediaBox, message: Message) -> Signal<[MediaId: Data], NoError> {
|
||||
var signals: [Signal<(MediaId, Data)?, NoError>] = []
|
||||
for media in message.media {
|
||||
if let image = media as? TelegramMediaImage {
|
||||
if let smallestRepresentation = smallestImageRepresentation(image.representations) {
|
||||
signals.append(resourceThumbnailData(mediaBox: mediaBox, resource: smallestRepresentation.resource, mediaId: image.imageId))
|
||||
}
|
||||
} else if let file = media as? TelegramMediaFile {
|
||||
if let smallestRepresentation = smallestImageRepresentation(file.previewRepresentations) {
|
||||
signals.append(resourceThumbnailData(mediaBox: mediaBox, resource: smallestRepresentation.resource, mediaId: file.fileId))
|
||||
}
|
||||
}
|
||||
}
|
||||
return combineLatest(signals)
|
||||
|> map { values in
|
||||
var result: [MediaId: Data] = [:]
|
||||
for value in values {
|
||||
if let value = value {
|
||||
result[value.0] = value.1
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
private func sendMessage(postbox: Postbox, network: Network, messageId: MessageId, file: SecretChatOutgoingFile?, tagLocalIndex: Int32, wasDelivered: Bool, layer: SecretChatLayer) -> Signal<Void, NoError> {
|
||||
return postbox.modify { modifier -> Signal<[MediaId: Data], NoError> in
|
||||
if let message = modifier.getMessage(messageId) {
|
||||
return messageWithThumbnailData(mediaBox: postbox.mediaBox, message: message)
|
||||
} else {
|
||||
return .single([:])
|
||||
}
|
||||
}
|
||||
|> switchToLatest
|
||||
|> mapToSignal { thumbnailData -> Signal<Void, NoError> in
|
||||
return postbox.modify { modifier -> Signal<Void, NoError> in
|
||||
if let state = modifier.getPeerChatState(messageId.peerId) as? SecretChatState, let peer = modifier.getPeer(messageId.peerId) as? TelegramSecretChat {
|
||||
if let message = modifier.getMessage(messageId), let globallyUniqueId = message.globallyUniqueId {
|
||||
let decryptedMessage = boxedDecryptedMessage(message: message, globallyUniqueId: globallyUniqueId, uploadedFile: file, layer: layer)
|
||||
let decryptedMessage = boxedDecryptedMessage(modifier: modifier, message: message, globallyUniqueId: globallyUniqueId, uploadedFile: file, thumbnailData: thumbnailData, layer: layer)
|
||||
return sendBoxedDecryptedMessage(postbox: postbox, network: network, peer: peer, state: state, operationIndex: tagLocalIndex, decryptedMessage: decryptedMessage, globallyUniqueId: globallyUniqueId, file: file, asService: wasDelivered, wasDelivered: wasDelivered)
|
||||
|> mapToSignal { result in
|
||||
return postbox.modify { modifier -> Void in
|
||||
@ -712,16 +1034,22 @@ private func sendMessage(postbox: Postbox, network: Network, messageId: MessageI
|
||||
} else {
|
||||
markOutgoingOperationAsCompleted(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil)
|
||||
}
|
||||
modifier.updateMessage(message.id, update: { currentMessage in
|
||||
var flags = StoreMessageFlags(currentMessage.flags)
|
||||
|
||||
var timestamp = message.timestamp
|
||||
if let result = result {
|
||||
switch result {
|
||||
case let .sentEncryptedMessage(date):
|
||||
timestamp = date
|
||||
case let .sentEncryptedFile(date, file):
|
||||
case let .sentEncryptedFile(date, _):
|
||||
timestamp = date
|
||||
}
|
||||
}
|
||||
|
||||
modifier.offsetPendingMessagesTimestamps(lowerBound: message.id, excludeIds: Set([messageId]), timestamp: timestamp)
|
||||
|
||||
modifier.updateMessage(message.id, update: { currentMessage in
|
||||
var flags = StoreMessageFlags(currentMessage.flags)
|
||||
if let _ = result {
|
||||
flags.remove(.Unsent)
|
||||
flags.remove(.Sending)
|
||||
} else {
|
||||
@ -731,19 +1059,25 @@ private func sendMessage(postbox: Postbox, network: Network, messageId: MessageI
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
|
||||
})
|
||||
|
||||
maybeReadSecretOutgoingMessage(modifier: modifier, index: MessageIndex(id: message.id, timestamp: timestamp))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assertionFailure()
|
||||
return .never()
|
||||
replaceOutgoingOperationWithEmptyMessage(modifier: modifier, peerId: messageId.peerId, tagLocalIndex: tagLocalIndex, globallyUniqueId: arc4random64())
|
||||
modifier.deleteMessages([messageId])
|
||||
//assertionFailure()
|
||||
return .complete()
|
||||
}
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} |> switchToLatest
|
||||
}
|
||||
}
|
||||
|
||||
private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId: PeerId, action: SecretMessageAction, tagLocalIndex: Int32, wasDelivered: Bool) -> Signal<Void, NoError> {
|
||||
return postbox.modify { modifier -> Signal<Void, NoError> in
|
||||
@ -758,6 +1092,7 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
|
||||
markOutgoingOperationAsCompleted(modifier: modifier, peerId: peerId, tagLocalIndex: tagLocalIndex, forceRemove: result == nil)
|
||||
}
|
||||
if let messageId = action.messageId {
|
||||
var resultTimestamp: Int32?
|
||||
modifier.updateMessage(messageId, update: { currentMessage in
|
||||
var flags = StoreMessageFlags(currentMessage.flags)
|
||||
var timestamp = currentMessage.timestamp
|
||||
@ -773,12 +1108,17 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
|
||||
} else {
|
||||
flags = [.Failed]
|
||||
}
|
||||
resultTimestamp = timestamp
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
}
|
||||
return .update(StoreMessage(id: currentMessage.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: timestamp, flags: flags, tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
|
||||
})
|
||||
|
||||
if let resultTimestamp = resultTimestamp {
|
||||
maybeReadSecretOutgoingMessage(modifier: modifier, index: MessageIndex(id: messageId, timestamp: resultTimestamp))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -791,12 +1131,23 @@ private func sendServiceActionMessage(postbox: Postbox, network: Network, peerId
|
||||
private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer: TelegramSecretChat, state: SecretChatState, operationIndex: Int32, decryptedMessage: BoxedDecryptedMessage, globallyUniqueId: Int64, file: SecretChatOutgoingFile?, asService: Bool, wasDelivered: Bool) -> Signal<Api.messages.SentEncryptedMessage?, NoError> {
|
||||
let payload = Buffer()
|
||||
var sequenceInfo: SecretChatOperationSequenceInfo?
|
||||
var maybeKey: SecretChatKey?
|
||||
var maybeParameters: SecretChatEncryptionParameters?
|
||||
|
||||
let mode: SecretChatEncryptionMode
|
||||
switch decryptedMessage {
|
||||
case .layer8, .layer46:
|
||||
mode = .v1
|
||||
default:
|
||||
mode = .v2(role: state.role)
|
||||
}
|
||||
|
||||
switch state.embeddedState {
|
||||
case .terminated, .handshake:
|
||||
break
|
||||
case .basicLayer:
|
||||
maybeKey = state.keychain.indefinitelyValidKey()
|
||||
if let key = state.keychain.indefinitelyValidKey() {
|
||||
maybeParameters = SecretChatEncryptionParameters(key: key, mode: mode)
|
||||
}
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
let topReceivedOperationIndex: Int32
|
||||
if let topProcessedCanonicalIncomingOperationIndex = sequenceState.topProcessedCanonicalIncomingOperationIndex {
|
||||
@ -805,18 +1156,20 @@ private func sendBoxedDecryptedMessage(postbox: Postbox, network: Network, peer:
|
||||
topReceivedOperationIndex = -1
|
||||
}
|
||||
let canonicalOperationIndex = sequenceState.canonicalOutgoingOperationIndex(operationIndex)
|
||||
maybeKey = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalOperationIndex)
|
||||
Logger.shared.log("SecretChat", "sending message with index \(canonicalOperationIndex) key \(String(describing: maybeKey?.fingerprint))")
|
||||
if let key = state.keychain.latestKey(validForSequenceBasedCanonicalIndex: canonicalOperationIndex) {
|
||||
maybeParameters = SecretChatEncryptionParameters(key: key, mode: mode)
|
||||
}
|
||||
Logger.shared.log("SecretChat", "sending message with index \(canonicalOperationIndex) key \(String(describing: maybeParameters?.key.fingerprint))")
|
||||
sequenceInfo = SecretChatOperationSequenceInfo(topReceivedOperationIndex: topReceivedOperationIndex, operationIndex: canonicalOperationIndex)
|
||||
}
|
||||
|
||||
guard let key = maybeKey else {
|
||||
guard let parameters = maybeParameters else {
|
||||
Logger.shared.log("SecretChat", "no valid key found")
|
||||
return .single(nil)
|
||||
}
|
||||
|
||||
decryptedMessage.serialize(payload, role: state.role, sequenceInfo: sequenceInfo)
|
||||
let encryptedPayload = encryptedMessageContents(key: key, data: MemoryBuffer(payload))
|
||||
let encryptedPayload = encryptedMessageContents(parameters: parameters, data: MemoryBuffer(payload))
|
||||
let sendMessage: Signal<Api.messages.SentEncryptedMessage, MTRpcError>
|
||||
let inputPeer = Api.InputEncryptedChat.inputEncryptedChat(chatId: peer.id.id, accessHash: peer.accessHash)
|
||||
|
||||
|
||||
@ -46,7 +46,7 @@ public func markMessageContentAsConsumedInteractively(postbox: Postbox, messageI
|
||||
case .basicLayer:
|
||||
layer = .layer8
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
||||
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -25,6 +25,7 @@ public struct Namespaces {
|
||||
public static let CloudSecretFile: Int32 = 10
|
||||
public static let CloudGame: Int32 = 11
|
||||
public static let CloudInvoice: Int32 = 12
|
||||
public static let LocalWebpage: Int32 = 13
|
||||
}
|
||||
|
||||
public struct Peer {
|
||||
@ -132,6 +133,7 @@ private enum PreferencesKeyValues: Int32 {
|
||||
case proxySettings = 5
|
||||
case loggingSettings = 6
|
||||
case coreSettings = 7
|
||||
case contentPrivacySettings = 8
|
||||
}
|
||||
|
||||
public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey {
|
||||
@ -188,4 +190,10 @@ public struct PreferencesKeys {
|
||||
key.setInt32(0, value: PreferencesKeyValues.coreSettings.rawValue)
|
||||
return key
|
||||
}()
|
||||
|
||||
public static let contentPrivacySettings: ValueBoxKey = {
|
||||
let key = ValueBoxKey(length: 4)
|
||||
key.setInt32(0, value: PreferencesKeyValues.contentPrivacySettings.rawValue)
|
||||
return key
|
||||
}()
|
||||
}
|
||||
|
||||
@ -12,114 +12,10 @@ private func aspectFitSize(_ size: CGSize, to: CGSize) -> CGSize {
|
||||
return CGSize(width: floor(size.width * scale), height: floor(size.height * scale))
|
||||
}
|
||||
|
||||
/*
|
||||
if ([result isKindOfClass:[TGBotContextMediaResult class]]) {
|
||||
TGBotContextMediaResult *concreteResult = (TGBotContextMediaResult *)result;
|
||||
if ([concreteResult.type isEqualToString:@"game"]) {
|
||||
TGGameMediaAttachment *gameMedia = [[TGGameMediaAttachment alloc] initWithGameId:0 accessHash:0 shortName:nil title:concreteResult.title gameDescription:concreteResult.resultDescription photo:concreteResult.photo document:concreteResult.document];
|
||||
[strongSelf->_companion controllerWantsToSendGame:gameMedia asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
|
||||
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
|
||||
} else if (concreteResult.document != nil) {
|
||||
TGDocumentAttributeVideo *video = nil;
|
||||
bool isAnimated = false;
|
||||
for (id attribute in concreteResult.document.attributes) {
|
||||
if ([attribute isKindOfClass:[TGDocumentAttributeVideo class]]) {
|
||||
video = attribute;
|
||||
} else if ([attribute isKindOfClass:[TGDocumentAttributeAnimated class]]) {
|
||||
isAnimated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (video != nil && !isAnimated) {
|
||||
TGVideoMediaAttachment *videoMedia = [[TGVideoMediaAttachment alloc] init];
|
||||
videoMedia = [[TGVideoMediaAttachment alloc] init];
|
||||
videoMedia.videoId = concreteResult.document.documentId;
|
||||
videoMedia.accessHash = concreteResult.document.accessHash;
|
||||
videoMedia.duration = video.duration;
|
||||
videoMedia.dimensions = video.size;
|
||||
videoMedia.thumbnailInfo = concreteResult.document.thumbnailInfo;
|
||||
TGVideoInfo *videoInfo = [[TGVideoInfo alloc] init];
|
||||
[videoInfo addVideoWithQuality:1 url:[[NSString alloc] initWithFormat:@"video:%lld:%lld:%d:%d", videoMedia.videoId, videoMedia.accessHash, concreteResult.document.datacenterId, concreteResult.document.size] size:concreteResult.document.size];
|
||||
videoMedia.videoInfo = videoInfo;
|
||||
[strongSelf->_companion controllerWantsToSendRemoteVideoWithMedia:videoMedia asReplyToMessageId:[strongSelf currentReplyMessageId] text:concreteMessage.caption botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
|
||||
} else {
|
||||
[strongSelf->_companion controllerWantsToSendRemoteDocument:concreteResult.document asReplyToMessageId:[strongSelf currentReplyMessageId] text:concreteMessage.caption botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
|
||||
}
|
||||
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
|
||||
} else if (concreteResult.photo != nil) {
|
||||
[strongSelf->_companion controllerWantsToSendRemoteImage:concreteResult.photo text:concreteMessage.caption asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
|
||||
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
|
||||
}
|
||||
} else if ([result isKindOfClass:[TGBotContextExternalResult class]]) {
|
||||
TGBotContextExternalResult *concreteResult = (TGBotContextExternalResult *)result;
|
||||
if ([concreteResult.type isEqualToString:@"gif"]) {
|
||||
TGExternalGifSearchResult *externalGifSearchResult = [[TGExternalGifSearchResult alloc] initWithUrl:concreteResult.url originalUrl:concreteResult.originalUrl thumbnailUrl:concreteResult.thumbUrl size:concreteResult.size];
|
||||
id description = [strongSelf->_companion documentDescriptionFromExternalGifSearchResult:externalGifSearchResult text:concreteMessage.caption botContextResult:botContextResult];
|
||||
if (description != nil) {
|
||||
[strongSelf->_companion controllerWantsToSendImagesWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId] botReplyMarkup:concreteMessage.replyMarkup];
|
||||
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
|
||||
[TGRecentContextBotsSignal addRecentBot:results.userId];
|
||||
}
|
||||
} else if ([concreteResult.type isEqualToString:@"photo"]) {
|
||||
TGExternalImageSearchResult *externalImageSearchResult = [[TGExternalImageSearchResult alloc] initWithUrl:concreteResult.url originalUrl:concreteResult.originalUrl thumbnailUrl:concreteResult.thumbUrl title:concreteResult.title size:concreteResult.size];
|
||||
id description = [strongSelf->_companion imageDescriptionFromExternalImageSearchResult:externalImageSearchResult text:concreteMessage.caption botContextResult:botContextResult];
|
||||
if (description != nil) {
|
||||
[strongSelf->_companion controllerWantsToSendImagesWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId] botReplyMarkup:concreteMessage.replyMarkup];
|
||||
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
|
||||
[TGRecentContextBotsSignal addRecentBot:results.userId];
|
||||
}
|
||||
} else if ([concreteResult.type isEqualToString:@"audio"] || [concreteResult.type isEqualToString:@"voice"] || [concreteResult.type isEqualToString:@"file"]) {
|
||||
id description = [strongSelf->_companion documentDescriptionFromBotContextResult:concreteResult text:concreteMessage.caption botContextResult:botContextResult];
|
||||
if (description != nil) {
|
||||
[strongSelf->_companion controllerWantsToSendImagesWithDescriptions:@[description] asReplyToMessageId:[strongSelf currentReplyMessageId] botReplyMarkup:concreteMessage.replyMarkup];
|
||||
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
|
||||
[TGRecentContextBotsSignal addRecentBot:results.userId];
|
||||
}
|
||||
} else {
|
||||
if (![_companion allowMessageForwarding] && !TGAppDelegateInstance.allowSecretWebpages) {
|
||||
for (id result in [TGMessage textCheckingResultsForText:concreteMessage.caption highlightMentionsAndTags:false highlightCommands:false entities:nil]) {
|
||||
if ([result isKindOfClass:[NSTextCheckingResult class]] && ((NSTextCheckingResult *)result).resultType == NSTextCheckingTypeLink) {
|
||||
[_companion maybeAskForSecretWebpages];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[strongSelf->_companion controllerWantsToSendTextMessage:concreteMessage.caption entities:@[] asReplyToMessageId:[strongSelf currentReplyMessageId] withAttachedMessages:[strongSelf currentForwardMessages] disableLinkPreviews:false botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
|
||||
}
|
||||
}
|
||||
} else if ([result.sendMessage isKindOfClass:[TGBotContextResultSendMessageText class]]) {
|
||||
TGBotContextResultSendMessageText *concreteMessage = (TGBotContextResultSendMessageText *)result.sendMessage;
|
||||
|
||||
if (![_companion allowMessageForwarding] && !TGAppDelegateInstance.allowSecretWebpages) {
|
||||
for (id result in [TGMessage textCheckingResultsForText:concreteMessage.message highlightMentionsAndTags:false highlightCommands:false entities:nil]) {
|
||||
if ([result isKindOfClass:[NSTextCheckingResult class]] && ((NSTextCheckingResult *)result).resultType == NSTextCheckingTypeLink) {
|
||||
[_companion maybeAskForSecretWebpages];
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[strongSelf->_companion controllerWantsToSendTextMessage:concreteMessage.message entities:concreteMessage.entities asReplyToMessageId:[strongSelf currentReplyMessageId] withAttachedMessages:[strongSelf currentForwardMessages] disableLinkPreviews:false botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
|
||||
} else if ([result.sendMessage isKindOfClass:[TGBotContextResultSendMessageGeo class]]) {
|
||||
TGBotContextResultSendMessageGeo *concreteMessage = (TGBotContextResultSendMessageGeo *)result.sendMessage;
|
||||
[strongSelf->_companion controllerWantsToSendMapWithLatitude:concreteMessage.location.latitude longitude:concreteMessage.location.longitude venue:concreteMessage.location.venue asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
|
||||
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
|
||||
} else if ([result.sendMessage isKindOfClass:[TGBotContextResultSendMessageContact class]]) {
|
||||
TGBotContextResultSendMessageContact *concreteMessage = (TGBotContextResultSendMessageContact *)result.sendMessage;
|
||||
TGUser *contactUser = [[TGUser alloc] init];
|
||||
contactUser.firstName = concreteMessage.contact.firstName;
|
||||
contactUser.lastName = concreteMessage.contact.lastName;
|
||||
contactUser.phoneNumber = concreteMessage.contact.phoneNumber;
|
||||
[strongSelf->_companion controllerWantsToSendContact:contactUser asReplyToMessageId:[strongSelf currentReplyMessageId] botContextResult:botContextResult botReplyMarkup:concreteMessage.replyMarkup];
|
||||
[strongSelf->_inputTextPanel.inputField setText:@"" animated:true];
|
||||
}
|
||||
}*/
|
||||
|
||||
public func outgoingMessageWithChatContextResult(_ results: ChatContextResultCollection, _ result: ChatContextResult) -> EnqueueMessage? {
|
||||
var attributes: [MessageAttribute] = []
|
||||
attributes.append(OutgoingChatContextResultMessageAttribute(queryId: results.queryId, id: result.id))
|
||||
attributes.append(InlineBotMessageAttribute(peerId: results.botId))
|
||||
attributes.append(InlineBotMessageAttribute(peerId: results.botId, title: nil))
|
||||
|
||||
switch result.message {
|
||||
case let .auto(caption, entities, replyMarkup):
|
||||
|
||||
@ -509,9 +509,11 @@ public final class PendingMessageManager {
|
||||
messages.sort { MessageIndex($0.0) < MessageIndex($1.0) }
|
||||
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
//assertionFailure()
|
||||
for (message, content) in messages {
|
||||
PendingMessageManager.sendSecretMessageContent(modifier: modifier, message: message, content: content)
|
||||
}
|
||||
|
||||
return failMessages(postbox: postbox, ids: group.map { $0.0 })
|
||||
return .complete()
|
||||
} else if let peer = modifier.getPeer(peerId), let inputPeer = apiInputPeer(peer) {
|
||||
var isForward = false
|
||||
var replyMessageId: Int32?
|
||||
@ -637,13 +639,7 @@ public final class PendingMessageManager {
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
private func sendMessageContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, messageId: MessageId, content: PendingMessageUploadedContent) -> Signal<Void, NoError> {
|
||||
return postbox.modify { [weak self] modifier -> Signal<Void, NoError> in
|
||||
guard let message = modifier.getMessage(messageId) else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
if messageId.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
private static func sendSecretMessageContent(modifier: Modifier, message: Message, content: PendingMessageUploadedContent) {
|
||||
var secretFile: SecretChatOutgoingFile?
|
||||
switch content {
|
||||
case let .secretMedia(file, size, key):
|
||||
@ -655,7 +651,7 @@ public final class PendingMessageManager {
|
||||
}
|
||||
|
||||
var layer: SecretChatLayer?
|
||||
let state = modifier.getPeerChatState(messageId.peerId) as? SecretChatState
|
||||
let state = modifier.getPeerChatState(message.id.peerId) as? SecretChatState
|
||||
if let state = state {
|
||||
switch state.embeddedState {
|
||||
case .terminated, .handshake:
|
||||
@ -663,7 +659,7 @@ public final class PendingMessageManager {
|
||||
case .basicLayer:
|
||||
layer = .layer8
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
||||
layer = sequenceState.layerNegotiationState.activeLayer.secretChatLayer
|
||||
}
|
||||
}
|
||||
|
||||
@ -694,11 +690,11 @@ public final class PendingMessageManager {
|
||||
}
|
||||
|
||||
if !sentAsAction {
|
||||
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: messageId.peerId, operation: .sendMessage(layer: layer, id: messageId, file: secretFile), state: state)
|
||||
let updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: message.id.peerId, operation: .sendMessage(layer: layer, id: message.id, file: secretFile), state: state)
|
||||
if updatedState != state {
|
||||
modifier.setPeerChatState(messageId.peerId, state: updatedState)
|
||||
modifier.setPeerChatState(message.id.peerId, state: updatedState)
|
||||
}
|
||||
modifier.updateMessage(messageId, update: { currentMessage in
|
||||
modifier.updateMessage(message.id, update: { currentMessage in
|
||||
var flags = StoreMessageFlags(message.flags)
|
||||
if !flags.contains(.Failed) {
|
||||
flags.insert(.Sending)
|
||||
@ -711,7 +707,7 @@ public final class PendingMessageManager {
|
||||
})
|
||||
}
|
||||
} else {
|
||||
modifier.updateMessage(messageId, update: { currentMessage in
|
||||
modifier.updateMessage(message.id, update: { currentMessage in
|
||||
var storeForwardInfo: StoreMessageForwardInfo?
|
||||
if let forwardInfo = currentMessage.forwardInfo {
|
||||
storeForwardInfo = StoreMessageForwardInfo(authorId: forwardInfo.author.id, sourceId: forwardInfo.source?.id, sourceMessageId: forwardInfo.sourceMessageId, date: forwardInfo.date, authorSignature: forwardInfo.authorSignature)
|
||||
@ -719,6 +715,16 @@ public final class PendingMessageManager {
|
||||
return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func sendMessageContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, messageId: MessageId, content: PendingMessageUploadedContent) -> Signal<Void, NoError> {
|
||||
return postbox.modify { [weak self] modifier -> Signal<Void, NoError> in
|
||||
guard let message = modifier.getMessage(messageId) else {
|
||||
return .complete()
|
||||
}
|
||||
|
||||
if messageId.peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
PendingMessageManager.sendSecretMessageContent(modifier: modifier, message: message, content: content)
|
||||
return .complete()
|
||||
} else if let peer = modifier.getPeer(messageId.peerId), let inputPeer = apiInputPeer(peer) {
|
||||
var uniqueId: Int64 = 0
|
||||
|
||||
@ -40,7 +40,9 @@ func messageContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods
|
||||
var autoremoveAttribute: AutoremoveTimeoutMessageAttribute?
|
||||
for attribute in attributes {
|
||||
if let attribute = attribute as? OutgoingChatContextResultMessageAttribute {
|
||||
if peerId.namespace != Namespaces.Peer.SecretChat {
|
||||
contextResult = attribute
|
||||
}
|
||||
} else if let attribute = attribute as? AutoremoveTimeoutMessageAttribute {
|
||||
autoremoveAttribute = attribute
|
||||
}
|
||||
@ -105,7 +107,11 @@ private enum PredownloadedResource {
|
||||
case none
|
||||
}
|
||||
|
||||
private func maybePredownloadedImageResource(postbox: Postbox, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
|
||||
private func maybePredownloadedImageResource(postbox: Postbox, peerId: PeerId, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
return .single(.none)
|
||||
}
|
||||
|
||||
return Signal<Signal<PredownloadedResource, PendingMessageUploadError>, PendingMessageUploadError> { subscriber in
|
||||
let data = postbox.mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)).start(next: { data in
|
||||
if data.complete {
|
||||
@ -154,7 +160,11 @@ private func maybePredownloadedImageResource(postbox: Postbox, resource: MediaRe
|
||||
} |> switchToLatest
|
||||
}
|
||||
|
||||
private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
|
||||
private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, peerId: PeerId, resource: MediaResource) -> Signal<PredownloadedResource, PendingMessageUploadError> {
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
return .single(.none)
|
||||
}
|
||||
|
||||
return auxiliaryMethods.fetchResourceMediaReferenceHash(resource)
|
||||
|> mapToSignal { hash -> Signal<PredownloadedResource, NoError> in
|
||||
if let hash = hash {
|
||||
@ -185,7 +195,7 @@ private func maybeCacheUploadedResource(postbox: Postbox, key: CachedSentMediaRe
|
||||
|
||||
private func uploadedMediaImageContent(network: Network, postbox: Postbox, peerId: PeerId, image: TelegramMediaImage, text: String, autoremoveAttribute: AutoremoveTimeoutMessageAttribute?) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
|
||||
if let largestRepresentation = largestImageRepresentation(image.representations) {
|
||||
let predownloadedResource: Signal<PredownloadedResource, PendingMessageUploadError> = maybePredownloadedImageResource(postbox: postbox, resource: largestRepresentation.resource)
|
||||
let predownloadedResource: Signal<PredownloadedResource, PendingMessageUploadError> = maybePredownloadedImageResource(postbox: postbox, peerId: peerId, resource: largestRepresentation.resource)
|
||||
return predownloadedResource
|
||||
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
var referenceKey: CachedSentMediaReferenceKey?
|
||||
@ -324,9 +334,14 @@ private enum UploadedMediaTransform {
|
||||
case done(Media?)
|
||||
}
|
||||
|
||||
private enum UploadedMediaThumbnailResult {
|
||||
case file(Api.InputFile)
|
||||
case none
|
||||
}
|
||||
|
||||
private enum UploadedMediaThumbnail {
|
||||
case pending
|
||||
case done(Api.InputFile?)
|
||||
case done(UploadedMediaThumbnailResult)
|
||||
}
|
||||
|
||||
private func uploadedThumbnail(network: Network, postbox: Postbox, image: TelegramMediaImageRepresentation) -> Signal<Api.InputFile?, PendingMessageUploadError> {
|
||||
@ -359,7 +374,7 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA
|
||||
}
|
||||
|
||||
private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], file: TelegramMediaFile) -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> {
|
||||
return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, resource: file.resource) |> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource) |> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
var referenceKey: CachedSentMediaReferenceKey?
|
||||
switch result {
|
||||
case let .media(media):
|
||||
@ -433,27 +448,40 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
return .single(.pending)
|
||||
case let .done(media):
|
||||
if let media = media as? TelegramMediaFile, let smallestThumbnail = smallestImageRepresentation(media.previewRepresentations) {
|
||||
if peerId.namespace == Namespaces.Peer.SecretChat {
|
||||
return .single(.done(.none))
|
||||
} else {
|
||||
return uploadedThumbnail(network: network, postbox: postbox, image: smallestThumbnail)
|
||||
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
||||
|> map { result in
|
||||
return .done(result)
|
||||
if let result = result {
|
||||
return .done(.file(result))
|
||||
} else {
|
||||
return .done(.none)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return .single(.done(nil))
|
||||
return .single(.done(.none))
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return combineLatest(upload, thumbnail)
|
||||
|> mapToSignal { content, media -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
|> mapToSignal { content, thumbnailResult -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
switch content {
|
||||
case let .progress(progress):
|
||||
return .single(.progress(progress))
|
||||
case let .inputFile(inputFile):
|
||||
if case let .done(thumbnail) = media {
|
||||
if case let .done(thumbnail) = thumbnailResult {
|
||||
var flags: Int32 = 0
|
||||
|
||||
if let _ = thumbnail {
|
||||
var thumbnailFile: Api.InputFile?
|
||||
if case let .file(file) = thumbnail {
|
||||
thumbnailFile = file
|
||||
}
|
||||
|
||||
if let thumbnailFile = thumbnailFile {
|
||||
flags |= 1 << 2
|
||||
}
|
||||
|
||||
@ -465,10 +493,8 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
}
|
||||
}
|
||||
|
||||
//flags |= 1 << 3
|
||||
|
||||
if ttlSeconds != nil {
|
||||
return .single(.content(.media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnail, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds), text)))
|
||||
return .single(.content(.media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds), text)))
|
||||
}
|
||||
|
||||
return postbox.modify { modifier -> Api.InputPeer? in
|
||||
@ -477,7 +503,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
||||
|> mapToSignal { inputPeer -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
if let inputPeer = inputPeer {
|
||||
return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: .inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnail, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds)))
|
||||
return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: .inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds)))
|
||||
|> mapError { _ -> PendingMessageUploadError in return .generic }
|
||||
|> mapToSignal { result -> Signal<PendingMessageUploadedContentResult, PendingMessageUploadError> in
|
||||
switch result {
|
||||
@ -498,7 +524,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili
|
||||
return .complete()
|
||||
}
|
||||
case let .inputSecretFile(file, size, key):
|
||||
if case .done = media {
|
||||
if case .done = thumbnailResult {
|
||||
return .single(.content(.secretMedia(file, size, key)))
|
||||
} else {
|
||||
return .complete()
|
||||
|
||||
@ -45,6 +45,10 @@ private func parsedServiceAction(_ operation: SecretChatIncomingDecryptedOperati
|
||||
if let parsedObject = SecretApi46.parse(Buffer(bufferNoCopy: operation.contents)), let apiMessage = parsedObject as? SecretApi46.DecryptedMessage {
|
||||
return SecretChatServiceAction(apiMessage)
|
||||
}
|
||||
case .layer73:
|
||||
if let parsedObject = SecretApi73.parse(Buffer(bufferNoCopy: operation.contents)), let apiMessage = parsedObject as? SecretApi73.DecryptedMessage {
|
||||
return SecretChatServiceAction(apiMessage)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -134,6 +138,18 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
|
||||
} else {
|
||||
throw MessageParsingError.contentParsingError
|
||||
}
|
||||
case .layer73:
|
||||
if let parsedObject = SecretApi73.parse(Buffer(bufferNoCopy: operation.contents)), let apiMessage = parsedObject as? SecretApi73.DecryptedMessage {
|
||||
if let (parsedMessage, parsedResources) = parseMessage(peerId: peerId, authorId: updatedPeer.regularPeerId, tagLocalIndex: entry.tagLocalIndex, timestamp: operation.timestamp, apiMessage: apiMessage, file: operation.file, messageIdForGloballyUniqueMessageId: { id in
|
||||
return modifier.messageIdForGloballyUniqueMessageId(peerId: peerId, id: id)
|
||||
}) {
|
||||
message = parsedMessage
|
||||
resources = parsedResources
|
||||
}
|
||||
serviceAction = SecretChatServiceAction(apiMessage)
|
||||
} else {
|
||||
throw MessageParsingError.contentParsingError
|
||||
}
|
||||
}
|
||||
|
||||
switch updatedState.embeddedState {
|
||||
@ -195,10 +211,14 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
|
||||
case .handshake:
|
||||
throw MessageParsingError.invalidChatState
|
||||
case .basicLayer:
|
||||
if layerSupport >= 46 {
|
||||
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: 46, locallyRequestedLayer: 46, remotelyRequestedLayer: layerSupport), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
|
||||
if layerSupport >= 73 {
|
||||
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: .layer73, locallyRequestedLayer: 73, remotelyRequestedLayer: layerSupport), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
|
||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState))
|
||||
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer46, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
|
||||
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer73, actionGloballyUniqueId: arc4random64(), layerSupport: 73), state: updatedState)
|
||||
} else if layerSupport >= 46 {
|
||||
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: .layer46, locallyRequestedLayer: 46, remotelyRequestedLayer: layerSupport), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
|
||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState))
|
||||
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer46, actionGloballyUniqueId: arc4random64(), layerSupport: 73), state: updatedState)
|
||||
} else {
|
||||
throw MessageParsingError.contentParsingError
|
||||
}
|
||||
@ -206,7 +226,8 @@ func processSecretChatIncomingDecryptedOperations(mediaBox: MediaBox, modifier:
|
||||
if sequenceState.layerNegotiationState.remotelyRequestedLayer != layerSupport {
|
||||
let updatedNegotiationState = sequenceState.layerNegotiationState.withUpdatedRemotelyRequestedLayer(layerSupport)
|
||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedLayerNegotiationState(updatedNegotiationState)))
|
||||
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer46, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
|
||||
|
||||
updatedState = secretChatCheckLayerNegotiationIfNeeded(modifier: modifier, peerId: peerId, state: updatedState)
|
||||
}
|
||||
}
|
||||
case let .setMessageAutoremoveTimeout(timeout):
|
||||
@ -378,6 +399,42 @@ extension SecretChatServiceAction {
|
||||
}
|
||||
}
|
||||
|
||||
extension SecretChatServiceAction {
|
||||
init?(_ apiMessage: SecretApi73.DecryptedMessage) {
|
||||
switch apiMessage {
|
||||
case .decryptedMessage:
|
||||
return nil
|
||||
case let .decryptedMessageService(_, action):
|
||||
switch action {
|
||||
case let .decryptedMessageActionDeleteMessages(randomIds):
|
||||
self = .deleteMessages(globallyUniqueIds: randomIds)
|
||||
case .decryptedMessageActionFlushHistory:
|
||||
self = .clearHistory
|
||||
case let .decryptedMessageActionNotifyLayer(layer):
|
||||
self = .reportLayerSupport(layer)
|
||||
case let .decryptedMessageActionReadMessages(randomIds):
|
||||
self = .markMessagesContentAsConsumed(globallyUniqueIds: randomIds)
|
||||
case .decryptedMessageActionScreenshotMessages:
|
||||
return nil
|
||||
case let .decryptedMessageActionSetMessageTTL(ttlSeconds):
|
||||
self = .setMessageAutoremoveTimeout(ttlSeconds)
|
||||
case let .decryptedMessageActionResend(startSeqNo, endSeqNo):
|
||||
self = .resendOperations(fromSeq: startSeqNo, toSeq: endSeqNo)
|
||||
case let .decryptedMessageActionRequestKey(exchangeId, gA):
|
||||
self = .rekeyAction(.pfsRequestKey(rekeySessionId: exchangeId, gA: MemoryBuffer(gA)))
|
||||
case let .decryptedMessageActionAcceptKey(exchangeId, gB, keyFingerprint):
|
||||
self = .rekeyAction(.pfsAcceptKey(rekeySessionId: exchangeId, gB: MemoryBuffer(gB), keyFingerprint: keyFingerprint))
|
||||
case let .decryptedMessageActionCommitKey(exchangeId, keyFingerprint):
|
||||
self = .rekeyAction(.pfsCommitKey(rekeySessionId: exchangeId, keyFingerprint: keyFingerprint))
|
||||
case let .decryptedMessageActionAbortKey(exchangeId):
|
||||
self = .rekeyAction(.pfsAbortSession(rekeySessionId: exchangeId))
|
||||
case .decryptedMessageActionNoop:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension StoreMessage {
|
||||
convenience init?(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi8.DecryptedMessage, file: SecretChatFileReference?) {
|
||||
switch apiMessage {
|
||||
@ -431,16 +488,83 @@ extension TelegramMediaFileAttribute {
|
||||
self = .Sticker(displayText: alt, packReference: packReference, maskData: nil)
|
||||
case let .documentAttributeVideo(duration, w, h):
|
||||
self = .Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: [])
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TelegramMediaFileAttribute {
|
||||
init?(_ apiAttribute: SecretApi73.DocumentAttribute) {
|
||||
switch apiAttribute {
|
||||
case .documentAttributeAnimated:
|
||||
self = .Animated
|
||||
case let .documentAttributeAudio(flags, duration, title, performer, waveform):
|
||||
let isVoice = (flags & (1 << 10)) != 0
|
||||
var waveformBuffer: MemoryBuffer?
|
||||
if let waveform = waveform {
|
||||
let memory = malloc(waveform.size)!
|
||||
memcpy(memory, waveform.data, waveform.size)
|
||||
waveformBuffer = MemoryBuffer(memory: memory, capacity: waveform.size, length: waveform.size, freeWhenDone: true)
|
||||
}
|
||||
self = .Audio(isVoice: isVoice, duration: Int(duration), title: title, performer: performer, waveform: waveformBuffer)
|
||||
case let .documentAttributeFilename(fileName):
|
||||
self = .FileName(fileName: fileName)
|
||||
case let .documentAttributeImageSize(w, h):
|
||||
self = .ImageSize(size: CGSize(width: CGFloat(w), height: CGFloat(h)))
|
||||
case let .documentAttributeSticker(alt, stickerset):
|
||||
let packReference: StickerPackReference?
|
||||
switch stickerset {
|
||||
case .inputStickerSetEmpty:
|
||||
packReference = nil
|
||||
case let .inputStickerSetShortName(shortName):
|
||||
packReference = .name(shortName)
|
||||
}
|
||||
self = .Sticker(displayText: alt, packReference: packReference, maskData: nil)
|
||||
case let .documentAttributeVideo(flags, duration, w, h):
|
||||
var videoFlags: TelegramMediaVideoFlags = []
|
||||
if (flags & (1 << 0)) != 0 {
|
||||
videoFlags.insert(.instantRoundVideo)
|
||||
}
|
||||
self = .Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: videoFlags)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func parseEntities(_ entities: [SecretApi46.MessageEntity]?) -> TextEntitiesMessageAttribute {
|
||||
var result: [MessageTextEntity] = []
|
||||
if let entities = entities {
|
||||
for entity in entities {
|
||||
switch entity {
|
||||
case let .messageEntityMention(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Mention))
|
||||
case let .messageEntityHashtag(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Hashtag))
|
||||
case let .messageEntityBotCommand(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BotCommand))
|
||||
case let .messageEntityUrl(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Url))
|
||||
case let .messageEntityEmail(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Email))
|
||||
case let .messageEntityBold(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Bold))
|
||||
case let .messageEntityItalic(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Italic))
|
||||
case let .messageEntityCode(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Code))
|
||||
case let .messageEntityPre(offset, length, _):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Pre))
|
||||
case let .messageEntityTextUrl(offset, length, url):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .TextUrl(url: url)))
|
||||
case .messageEntityUnknown:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return TextEntitiesMessageAttribute(entities: result)
|
||||
}
|
||||
|
||||
private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi46.DecryptedMessage, file: SecretChatFileReference?, messageIdForGloballyUniqueMessageId: (Int64) -> MessageId?) -> (StoreMessage, [(MediaResource, Data)])? {
|
||||
switch apiMessage {
|
||||
case let .decryptedMessage(flags, randomId, ttl, message, media, entities, viaBotName, replyToRandomId):
|
||||
case let .decryptedMessage(_, randomId, ttl, message, media, entities, viaBotName, replyToRandomId):
|
||||
var text = message
|
||||
var parsedMedia: [Media] = []
|
||||
var attributes: [MessageAttribute] = []
|
||||
@ -450,6 +574,12 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
||||
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttl, countdownBeginTime: nil))
|
||||
}
|
||||
|
||||
attributes.append(parseEntities(entities))
|
||||
|
||||
if let viaBotName = viaBotName, !viaBotName.isEmpty {
|
||||
attributes.append(InlineBotMessageAttribute(peerId: nil, title: viaBotName))
|
||||
}
|
||||
|
||||
if let media = media {
|
||||
switch media {
|
||||
case let .decryptedMessageMediaPhoto(thumb, thumbW, thumbH, w, h, size, key, iv, caption):
|
||||
@ -497,7 +627,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
||||
text = caption
|
||||
}
|
||||
if let file = file {
|
||||
var parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []), .FileName(fileName: "video.mov")]
|
||||
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []), .FileName(fileName: "video.mov")]
|
||||
var previewRepresentations: [TelegramMediaImageRepresentation] = []
|
||||
if thumb.size != 0 {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
@ -507,18 +637,49 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
||||
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
|
||||
parsedMedia.append(fileMedia)
|
||||
}
|
||||
case let .decryptedMessageMediaExternalDocument(id, accessHash, date, mimeType, size, thumb, dcId, attributes):
|
||||
case let .decryptedMessageMediaExternalDocument(id, accessHash, _, mimeType, size, thumb, dcId, attributes):
|
||||
var parsedAttributes: [TelegramMediaFileAttribute] = []
|
||||
for attribute in attributes {
|
||||
if let parsedAttribute = TelegramMediaFileAttribute(attribute) {
|
||||
parsedAttributes.append(parsedAttribute)
|
||||
}
|
||||
}
|
||||
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
|
||||
parsedMedia.append(fileMedia)
|
||||
var previewRepresentations: [TelegramMediaImageRepresentation] = []
|
||||
switch thumb {
|
||||
case let .photoSize(_, location, w, h, size):
|
||||
switch location {
|
||||
case let .fileLocation(dcId, volumeId, localId, secret):
|
||||
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size))))
|
||||
case .fileLocationUnavailable:
|
||||
break
|
||||
}
|
||||
case let .photoCachedSize(_, location, w, h, bytes):
|
||||
if bytes.size > 0 {
|
||||
switch location {
|
||||
case let .fileLocation(dcId, volumeId, localId, secret):
|
||||
let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size)
|
||||
resources.append((resource, bytes.makeData()))
|
||||
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource))
|
||||
case .fileLocationUnavailable:
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
|
||||
parsedMedia.append(fileMedia)
|
||||
case let .decryptedMessageMediaWebPage(url):
|
||||
parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url)))
|
||||
case let .decryptedMessageMediaGeoPoint(lat, long):
|
||||
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))
|
||||
case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId):
|
||||
parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)))
|
||||
case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId):
|
||||
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil))
|
||||
case .decryptedMessageMediaEmpty:
|
||||
break
|
||||
}
|
||||
}
|
||||
if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) {
|
||||
attributes.append(ReplyMessageAttribute(messageId: replyMessageId))
|
||||
@ -564,3 +725,214 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func parseEntities(_ entities: [SecretApi73.MessageEntity]?) -> TextEntitiesMessageAttribute {
|
||||
var result: [MessageTextEntity] = []
|
||||
if let entities = entities {
|
||||
for entity in entities {
|
||||
switch entity {
|
||||
case let .messageEntityMention(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Mention))
|
||||
case let .messageEntityHashtag(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Hashtag))
|
||||
case let .messageEntityBotCommand(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .BotCommand))
|
||||
case let .messageEntityUrl(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Url))
|
||||
case let .messageEntityEmail(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Email))
|
||||
case let .messageEntityBold(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Bold))
|
||||
case let .messageEntityItalic(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Italic))
|
||||
case let .messageEntityCode(offset, length):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Code))
|
||||
case let .messageEntityPre(offset, length, _):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .Pre))
|
||||
case let .messageEntityTextUrl(offset, length, url):
|
||||
result.append(MessageTextEntity(range: Int(offset) ..< Int(offset + length), type: .TextUrl(url: url)))
|
||||
case .messageEntityUnknown:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return TextEntitiesMessageAttribute(entities: result)
|
||||
}
|
||||
|
||||
private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi73.DecryptedMessage, file: SecretChatFileReference?, messageIdForGloballyUniqueMessageId: (Int64) -> MessageId?) -> (StoreMessage, [(MediaResource, Data)])? {
|
||||
switch apiMessage {
|
||||
case let .decryptedMessage(_, randomId, ttl, message, media, entities, viaBotName, replyToRandomId, groupedId):
|
||||
var text = message
|
||||
var parsedMedia: [Media] = []
|
||||
var attributes: [MessageAttribute] = []
|
||||
var resources: [(MediaResource, Data)] = []
|
||||
|
||||
if ttl > 0 {
|
||||
attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttl, countdownBeginTime: nil))
|
||||
}
|
||||
|
||||
attributes.append(parseEntities(entities))
|
||||
|
||||
if let viaBotName = viaBotName, !viaBotName.isEmpty {
|
||||
attributes.append(InlineBotMessageAttribute(peerId: nil, title: viaBotName))
|
||||
}
|
||||
|
||||
if let media = media {
|
||||
switch media {
|
||||
case let .decryptedMessageMediaPhoto(thumb, thumbW, thumbH, w, h, size, key, iv, caption):
|
||||
if !caption.isEmpty {
|
||||
text = caption
|
||||
}
|
||||
if let file = file {
|
||||
var representations: [TelegramMediaImageRepresentation] = []
|
||||
if thumb.size != 0 {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource))
|
||||
resources.append((resource, thumb.makeData()))
|
||||
}
|
||||
representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size)))
|
||||
let image = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudSecretImage, id: file.id), representations: representations, reference: nil)
|
||||
parsedMedia.append(image)
|
||||
}
|
||||
case let .decryptedMessageMediaAudio(duration, mimeType, size, key, iv):
|
||||
if let file = file {
|
||||
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: [TelegramMediaFileAttribute.Audio(isVoice: true, duration: Int(duration), title: nil, performer: nil, waveform: nil)])
|
||||
parsedMedia.append(fileMedia)
|
||||
}
|
||||
case let .decryptedMessageMediaDocument(thumb, thumbW, thumbH, mimeType, size, key, iv, attributes, caption):
|
||||
if !caption.isEmpty {
|
||||
text = caption
|
||||
}
|
||||
if let file = file {
|
||||
var parsedAttributes: [TelegramMediaFileAttribute] = []
|
||||
for attribute in attributes {
|
||||
if let parsedAttribute = TelegramMediaFileAttribute(attribute) {
|
||||
parsedAttributes.append(parsedAttribute)
|
||||
}
|
||||
}
|
||||
var previewRepresentations: [TelegramMediaImageRepresentation] = []
|
||||
if thumb.size != 0 {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource))
|
||||
resources.append((resource, thumb.makeData()))
|
||||
}
|
||||
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
|
||||
parsedMedia.append(fileMedia)
|
||||
}
|
||||
case let .decryptedMessageMediaVideo(thumb, thumbW, thumbH, duration, mimeType, w, h, size, key, iv, caption):
|
||||
if !caption.isEmpty {
|
||||
text = caption
|
||||
}
|
||||
if let file = file {
|
||||
let parsedAttributes: [TelegramMediaFileAttribute] = [.Video(duration: Int(duration), size: CGSize(width: CGFloat(w), height: CGFloat(h)), flags: []), .FileName(fileName: "video.mov")]
|
||||
var previewRepresentations: [TelegramMediaImageRepresentation] = []
|
||||
if thumb.size != 0 {
|
||||
let resource = LocalFileMediaResource(fileId: arc4random64())
|
||||
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource))
|
||||
resources.append((resource, thumb.makeData()))
|
||||
}
|
||||
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
|
||||
parsedMedia.append(fileMedia)
|
||||
}
|
||||
case let .decryptedMessageMediaExternalDocument(id, accessHash, date, mimeType, size, thumb, dcId, attributes):
|
||||
var parsedAttributes: [TelegramMediaFileAttribute] = []
|
||||
for attribute in attributes {
|
||||
if let parsedAttribute = TelegramMediaFileAttribute(attribute) {
|
||||
parsedAttributes.append(parsedAttribute)
|
||||
}
|
||||
}
|
||||
var previewRepresentations: [TelegramMediaImageRepresentation] = []
|
||||
switch thumb {
|
||||
case let .photoSize(_, location, w, h, size):
|
||||
switch location {
|
||||
case let .fileLocation(dcId, volumeId, localId, secret):
|
||||
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size))))
|
||||
case .fileLocationUnavailable:
|
||||
break
|
||||
}
|
||||
case let .photoCachedSize(_, location, w, h, bytes):
|
||||
if bytes.size > 0 {
|
||||
switch location {
|
||||
case let .fileLocation(dcId, volumeId, localId, secret):
|
||||
let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size)
|
||||
resources.append((resource, bytes.makeData()))
|
||||
previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource))
|
||||
case .fileLocationUnavailable:
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: parsedAttributes)
|
||||
parsedMedia.append(fileMedia)
|
||||
case let .decryptedMessageMediaWebPage(url):
|
||||
parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url)))
|
||||
case let .decryptedMessageMediaGeoPoint(lat, long):
|
||||
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil))
|
||||
case let .decryptedMessageMediaContact(phoneNumber, firstName, lastName, userId):
|
||||
parsedMedia.append(TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: userId == 0 ? nil : PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)))
|
||||
case let .decryptedMessageMediaVenue(lat, long, title, address, provider, venueId):
|
||||
parsedMedia.append(TelegramMediaMap(latitude: lat, longitude: long, geoPlace: nil, venue: MapVenue(title: title, address: address, provider: provider, id: venueId, type: nil), liveBroadcastingTimeout: nil))
|
||||
case .decryptedMessageMediaEmpty:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var groupingKey: Int64?
|
||||
if let groupedId = groupedId {
|
||||
inner: for media in parsedMedia {
|
||||
if let _ = media as? TelegramMediaImage {
|
||||
groupingKey = groupedId
|
||||
break inner
|
||||
} else if let _ = media as? TelegramMediaFile {
|
||||
groupingKey = groupedId
|
||||
break inner
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) {
|
||||
attributes.append(ReplyMessageAttribute(messageId: replyMessageId))
|
||||
}
|
||||
|
||||
var entitiesAttribute: TextEntitiesMessageAttribute?
|
||||
for attribute in attributes {
|
||||
if let attribute = attribute as? TextEntitiesMessageAttribute {
|
||||
entitiesAttribute = attribute
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
let (tags, globalTags) = tagsForStoreMessage(incoming: true, attributes: attributes, media: parsedMedia, textEntities: entitiesAttribute?.entities)
|
||||
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: groupingKey, timestamp: timestamp, flags: [.Incoming], tags: tags, globalTags: globalTags, localTags: [], forwardInfo: nil, authorId: authorId, text: text, attributes: attributes, media: parsedMedia), resources)
|
||||
case let .decryptedMessageService(randomId, action):
|
||||
switch action {
|
||||
case .decryptedMessageActionDeleteMessages:
|
||||
return nil
|
||||
case .decryptedMessageActionFlushHistory:
|
||||
return nil
|
||||
case .decryptedMessageActionNotifyLayer:
|
||||
return nil
|
||||
case .decryptedMessageActionReadMessages:
|
||||
return nil
|
||||
case .decryptedMessageActionScreenshotMessages:
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .historyScreenshot)]), [])
|
||||
case let .decryptedMessageActionSetMessageTTL(ttlSeconds):
|
||||
return (StoreMessage(id: MessageId(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, id: tagLocalIndex), globallyUniqueId: randomId, groupingKey: nil, timestamp: timestamp, flags: [.Incoming], tags: [], globalTags: [], localTags: [], forwardInfo: nil, authorId: authorId, text: "", attributes: [], media: [TelegramMediaAction(action: .messageAutoremoveTimeoutUpdated(ttlSeconds))]), [])
|
||||
case .decryptedMessageActionResend:
|
||||
return nil
|
||||
case .decryptedMessageActionRequestKey:
|
||||
return nil
|
||||
case .decryptedMessageActionAcceptKey:
|
||||
return nil
|
||||
case .decryptedMessageActionCommitKey:
|
||||
return nil
|
||||
case .decryptedMessageActionAbortKey:
|
||||
return nil
|
||||
case .decryptedMessageActionNoop:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,10 @@ func processSecretChatIncomingEncryptedOperations(modifier: Modifier, peerId: Pe
|
||||
modifier.operationLogEnumerateEntries(peerId: peerId, tag: OperationLogTags.SecretIncomingEncrypted, { entry in
|
||||
if let operation = entry.contents as? SecretChatIncomingEncryptedOperation {
|
||||
if let key = updatedState.keychain.key(fingerprint: operation.keyFingerprint) {
|
||||
withDecryptedMessageContents(key: key, data: operation.contents, { decryptedContents in
|
||||
var decryptedContents = withDecryptedMessageContents(parameters: SecretChatEncryptionParameters(key: key, mode: .v2(role: updatedState.role)), data: operation.contents)
|
||||
if decryptedContents == nil {
|
||||
decryptedContents = withDecryptedMessageContents(parameters: SecretChatEncryptionParameters(key: key, mode: .v1), data: operation.contents)
|
||||
}
|
||||
if let decryptedContents = decryptedContents {
|
||||
withExtendedLifetime(decryptedContents, {
|
||||
let buffer = BufferReader(Buffer(bufferNoCopy: decryptedContents))
|
||||
@ -88,7 +91,8 @@ func processSecretChatIncomingEncryptedOperations(modifier: Modifier, peerId: Pe
|
||||
guard let sequenceInfo = sequenceInfo else {
|
||||
throw MessagePreParsingError.protocolViolation
|
||||
}
|
||||
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: parsedLayer, locallyRequestedLayer: nil, remotelyRequestedLayer: nil), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
|
||||
|
||||
let sequenceBasedLayerState = SecretChatSequenceBasedLayerState(layerNegotiationState: SecretChatLayerNegotiationState(activeLayer: secretChatCommonSupportedLayer(remoteLayer: parsedLayer), locallyRequestedLayer: nil, remotelyRequestedLayer: nil), rekeyState: nil, baseIncomingOperationIndex: entry.tagLocalIndex, baseOutgoingOperationIndex: modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing), topProcessedCanonicalIncomingOperationIndex: nil)
|
||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceBasedLayerState))
|
||||
modifier.setPeerChatState(peerId, state: updatedState)
|
||||
entryTagLocalIndex = .manual(sequenceBasedLayerState.baseIncomingOperationIndex + sequenceInfo.operationIndex)
|
||||
@ -126,7 +130,6 @@ func processSecretChatIncomingEncryptedOperations(modifier: Modifier, peerId: Pe
|
||||
Logger.shared.log("SecretChat", "peerId \(peerId) couldn't decrypt message content")
|
||||
removeTagLocalIndices.append(entry.tagLocalIndex)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Logger.shared.log("SecretChat", "peerId \(peerId) key \(operation.keyFingerprint) doesn't exist")
|
||||
}
|
||||
|
||||
1524
TelegramCore/SecretApiLayer73.swift
Normal file
1524
TelegramCore/SecretApiLayer73.swift
Normal file
File diff suppressed because it is too large
Load Diff
@ -7,7 +7,9 @@ import Foundation
|
||||
import MtProtoKitDynamic
|
||||
#endif
|
||||
|
||||
private func messageKey(key: SecretChatKey, msgKey: UnsafeRawPointer) -> (aesKey: Data, aesIv: Data) {
|
||||
private func messageKey(key: SecretChatKey, msgKey: UnsafeRawPointer, mode: SecretChatEncryptionMode) -> (aesKey: Data, aesIv: Data) {
|
||||
switch mode {
|
||||
case .v1:
|
||||
let x: Int = 0
|
||||
|
||||
var sha1AData = Data()
|
||||
@ -74,25 +76,58 @@ private func messageKey(key: SecretChatKey, msgKey: UnsafeRawPointer) -> (aesKey
|
||||
}
|
||||
}
|
||||
return (aesKey, aesIv)
|
||||
case let .v2(role):
|
||||
var xValue: Int
|
||||
switch role {
|
||||
case .creator:
|
||||
xValue = 0
|
||||
case .participant:
|
||||
xValue = 8
|
||||
}
|
||||
|
||||
func withDecryptedMessageContents(key: SecretChatKey, data: MemoryBuffer, _ f: (MemoryBuffer?) -> Void) {
|
||||
assert(key.key.length == 256)
|
||||
var sha256_a_data = Data()
|
||||
sha256_a_data.append(msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
|
||||
sha256_a_data.append(key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: xValue), count: 36)
|
||||
|
||||
let sha256_a = MTSha256(sha256_a_data)!
|
||||
|
||||
var sha256_b_data = Data()
|
||||
sha256_b_data.append(key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 40 + xValue), count: 36)
|
||||
sha256_b_data.append(msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
|
||||
|
||||
let sha256_b = MTSha256(sha256_b_data)!
|
||||
|
||||
var aesKey = Data()
|
||||
aesKey.append(sha256_a.subdata(in: 0 ..< (0 + 8)))
|
||||
aesKey.append(sha256_b.subdata(in: 8 ..< (8 + 16)))
|
||||
aesKey.append(sha256_a.subdata(in: 24 ..< (24 + 8)))
|
||||
|
||||
var aesIv = Data()
|
||||
aesIv.append(sha256_b.subdata(in: 0 ..< (0 + 8)))
|
||||
aesIv.append(sha256_a.subdata(in: 8 ..< (8 + 16)))
|
||||
aesIv.append(sha256_b.subdata(in: 24 ..< (24 + 8)))
|
||||
|
||||
return (aesKey, aesIv)
|
||||
}
|
||||
}
|
||||
|
||||
func withDecryptedMessageContents(parameters: SecretChatEncryptionParameters, data: MemoryBuffer) -> MemoryBuffer? {
|
||||
assert(parameters.key.key.length == 256)
|
||||
|
||||
if data.length < 4 + 16 + 16 {
|
||||
f(nil)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
let msgKey = data.memory.advanced(by: 8)
|
||||
|
||||
let (aesKey, aesIv) = messageKey(key: key, msgKey: msgKey)
|
||||
switch parameters.mode {
|
||||
case .v1:
|
||||
let (aesKey, aesIv) = messageKey(key: parameters.key, msgKey: msgKey, mode: parameters.mode)
|
||||
|
||||
let decryptedData = MTAesDecrypt(Data(bytes: data.memory.advanced(by: 8 + 16), count: data.length - (8 + 16)), aesKey, aesIv)!
|
||||
|
||||
if decryptedData.count < 4 * 3 {
|
||||
f(nil)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
var payloadLength: Int32 = 0
|
||||
@ -102,8 +137,7 @@ func withDecryptedMessageContents(key: SecretChatKey, data: MemoryBuffer, _ f: (
|
||||
|
||||
let paddingLength = decryptedData.count - (Int(payloadLength) + 4)
|
||||
if Int(payloadLength) > decryptedData.count - 4 || paddingLength > 16 {
|
||||
f(nil)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
let calculatedMsgKeyData = MTSubdataSha1(decryptedData, 0, UInt(payloadLength) + 4)!
|
||||
@ -112,16 +146,85 @@ func withDecryptedMessageContents(key: SecretChatKey, data: MemoryBuffer, _ f: (
|
||||
}
|
||||
|
||||
if !msgKeyMatches {
|
||||
f(nil)
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
let result = decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
|
||||
return Data(bytes: bytes.advanced(by: 4), count: Int(payloadLength))
|
||||
}
|
||||
return MemoryBuffer(data: result)
|
||||
case let .v2(role):
|
||||
let senderRole: SecretChatRole
|
||||
switch role {
|
||||
case .creator:
|
||||
senderRole = .participant
|
||||
case .participant:
|
||||
senderRole = .creator
|
||||
}
|
||||
let (aesKey, aesIv) = messageKey(key: parameters.key, msgKey: msgKey, mode: .v2(role: senderRole))
|
||||
|
||||
let decryptedData = MTAesDecrypt(Data(bytes: data.memory.advanced(by: 8 + 16), count: data.length - (8 + 16)), aesKey, aesIv)!
|
||||
|
||||
if decryptedData.count < 4 * 3 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var payloadLength: Int32 = 0
|
||||
decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Void in
|
||||
f(MemoryBuffer(memory: UnsafeMutablePointer(mutating: bytes.advanced(by: 4)), capacity: Int(payloadLength), length: Int(payloadLength), freeWhenDone: false))
|
||||
memcpy(&payloadLength, bytes, 4)
|
||||
}
|
||||
|
||||
let paddingLength = decryptedData.count - (Int(payloadLength) + 4)
|
||||
|
||||
let xValue: Int
|
||||
switch role {
|
||||
case .creator:
|
||||
xValue = 8
|
||||
case .participant:
|
||||
xValue = 0
|
||||
}
|
||||
|
||||
var keyLargeData = Data()
|
||||
keyLargeData.append(parameters.key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 88 + xValue), count: 32)
|
||||
keyLargeData.append(decryptedData)
|
||||
|
||||
let keyLarge = MTSha256(keyLargeData)!
|
||||
let localMessageKey = keyLarge.subdata(in: 8 ..< (8 + 16))
|
||||
|
||||
let msgKeyData = Data(bytes: msgKey.assumingMemoryBound(to: UInt8.self), count: 16)
|
||||
|
||||
if Int(payloadLength) <= 0 || Int(payloadLength) > decryptedData.count - 4 || paddingLength < 12 || paddingLength > 1024 {
|
||||
|
||||
if localMessageKey != msgKeyData {
|
||||
Logger.shared.log("SecretChatEncryption", "message key doesn't match (length check)")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if localMessageKey != msgKeyData {
|
||||
Logger.shared.log("SecretChatEncryption", "message key doesn't match")
|
||||
return nil
|
||||
}
|
||||
|
||||
let result = decryptedData.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> Data in
|
||||
return Data(bytes: bytes.advanced(by: 4), count: Int(payloadLength))
|
||||
}
|
||||
return MemoryBuffer(data: result)
|
||||
}
|
||||
}
|
||||
|
||||
func encryptedMessageContents(key: SecretChatKey, data: MemoryBuffer) -> Data {
|
||||
enum SecretChatEncryptionMode {
|
||||
case v1
|
||||
case v2(role: SecretChatRole)
|
||||
}
|
||||
|
||||
struct SecretChatEncryptionParameters {
|
||||
let key: SecretChatKey
|
||||
let mode: SecretChatEncryptionMode
|
||||
}
|
||||
|
||||
func encryptedMessageContents(parameters: SecretChatEncryptionParameters, data: MemoryBuffer) -> Data {
|
||||
var payloadLength: Int32 = Int32(data.length)
|
||||
var payloadData = Data()
|
||||
withUnsafeBytes(of: &payloadLength, { bytes -> Void in
|
||||
@ -129,6 +232,8 @@ func encryptedMessageContents(key: SecretChatKey, data: MemoryBuffer) -> Data {
|
||||
})
|
||||
payloadData.append(data.memory.assumingMemoryBound(to: UInt8.self), count: data.length)
|
||||
|
||||
switch parameters.mode {
|
||||
case .v1:
|
||||
var msgKey = MTSha1(payloadData)!
|
||||
msgKey.replaceSubrange(0 ..< (msgKey.count - 16), with: Data())
|
||||
|
||||
@ -146,12 +251,70 @@ func encryptedMessageContents(key: SecretChatKey, data: MemoryBuffer) -> Data {
|
||||
}
|
||||
|
||||
let (aesKey, aesIv) = msgKey.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> (Data, Data) in
|
||||
return messageKey(key: key, msgKey: bytes)
|
||||
return messageKey(key: parameters.key, msgKey: bytes, mode: parameters.mode)
|
||||
}
|
||||
|
||||
let encryptedData = MTAesEncrypt(payloadData, aesKey, aesIv)!
|
||||
var encryptedPayload = Data()
|
||||
var keyFingerprint: Int64 = key.fingerprint
|
||||
var keyFingerprint: Int64 = parameters.key.fingerprint
|
||||
withUnsafeBytes(of: &keyFingerprint, { bytes -> Void in
|
||||
encryptedPayload.append(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: 8)
|
||||
})
|
||||
encryptedPayload.append(msgKey)
|
||||
encryptedPayload.append(encryptedData)
|
||||
return encryptedPayload
|
||||
case let .v2(role):
|
||||
var randomBytes = Data(count: 128)
|
||||
randomBytes.withUnsafeMutableBytes { (bytes: UnsafeMutablePointer<Int8>) -> Void in
|
||||
arc4random_buf(bytes, randomBytes.count)
|
||||
}
|
||||
|
||||
var decryptedData = payloadData
|
||||
var take = 0
|
||||
while take < 12 {
|
||||
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
|
||||
take += 1
|
||||
}
|
||||
|
||||
while decryptedData.count % 16 != 0 {
|
||||
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
|
||||
take += 1
|
||||
}
|
||||
|
||||
var remainingCount = Int(arc4random_uniform(UInt32(72 + 1 - take)))
|
||||
while remainingCount % 16 != 0 {
|
||||
remainingCount -= 1
|
||||
}
|
||||
|
||||
for _ in 0 ..< remainingCount {
|
||||
decryptedData.append(randomBytes.subdata(in: take ..< (take + 1)))
|
||||
take += 1
|
||||
}
|
||||
|
||||
var xValue: Int
|
||||
switch role {
|
||||
case .creator:
|
||||
xValue = 0
|
||||
case .participant:
|
||||
xValue = 8
|
||||
}
|
||||
|
||||
var keyData = Data()
|
||||
keyData.append(parameters.key.key.memory.assumingMemoryBound(to: UInt8.self).advanced(by: 88 + xValue), count: 32)
|
||||
|
||||
keyData.append(decryptedData)
|
||||
|
||||
let keyLarge = MTSha256(keyData)!
|
||||
|
||||
let msgKey = keyLarge.subdata(in: 8 ..< (8 + 16))
|
||||
|
||||
let (aesKey, aesIv) = msgKey.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) -> (Data, Data) in
|
||||
return messageKey(key: parameters.key, msgKey: bytes, mode: parameters.mode)
|
||||
}
|
||||
|
||||
let encryptedData = MTAesEncrypt(decryptedData, aesKey, aesIv)!
|
||||
var encryptedPayload = Data()
|
||||
var keyFingerprint: Int64 = parameters.key.fingerprint
|
||||
withUnsafeBytes(of: &keyFingerprint, { bytes -> Void in
|
||||
encryptedPayload.append(bytes.baseAddress!.assumingMemoryBound(to: UInt8.self), count: 8)
|
||||
})
|
||||
@ -159,3 +322,4 @@ func encryptedMessageContents(key: SecretChatKey, data: MemoryBuffer) -> Data {
|
||||
encryptedPayload.append(encryptedData)
|
||||
return encryptedPayload
|
||||
}
|
||||
}
|
||||
|
||||
63
TelegramCore/SecretChatLayerNegotiation.swift
Normal file
63
TelegramCore/SecretChatLayerNegotiation.swift
Normal file
@ -0,0 +1,63 @@
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import Postbox
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
private let topSupportedLayer: SecretChatSequenceBasedLayer = .layer73
|
||||
|
||||
func secretChatCommonSupportedLayer(remoteLayer: Int32) -> SecretChatSequenceBasedLayer {
|
||||
switch remoteLayer {
|
||||
case 46:
|
||||
return .layer46
|
||||
case 73:
|
||||
return .layer73
|
||||
default:
|
||||
return topSupportedLayer
|
||||
}
|
||||
}
|
||||
|
||||
func secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(modifier: Modifier, peerId: PeerId, state: SecretChatState) -> SecretChatState {
|
||||
switch state.embeddedState {
|
||||
case .basicLayer:
|
||||
var updatedState = state
|
||||
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: .layer8, actionGloballyUniqueId: arc4random64(), layerSupport: topSupportedLayer.rawValue), state: updatedState)
|
||||
return updatedState
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
var updatedState = state
|
||||
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: .reportLayerSupport(layer: sequenceState.layerNegotiationState.activeLayer.secretChatLayer, actionGloballyUniqueId: arc4random64(), layerSupport: topSupportedLayer.rawValue), state: updatedState)
|
||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedLayerNegotiationState(sequenceState.layerNegotiationState.withUpdatedLocallyRequestedLayer(topSupportedLayer.rawValue))))
|
||||
return updatedState
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
||||
func secretChatCheckLayerNegotiationIfNeeded(modifier: Modifier, peerId: PeerId, state: SecretChatState) -> SecretChatState {
|
||||
switch state.embeddedState {
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
if sequenceState.layerNegotiationState.activeLayer != topSupportedLayer {
|
||||
var updatedState = state
|
||||
|
||||
if let remotelyRequestedLayer = sequenceState.layerNegotiationState.remotelyRequestedLayer {
|
||||
let updatedSequenceState = sequenceState.withUpdatedLayerNegotiationState(sequenceState.layerNegotiationState.withUpdatedActiveLayer(secretChatCommonSupportedLayer(remoteLayer: remotelyRequestedLayer)))
|
||||
updatedState = updatedState.withUpdatedEmbeddedState(.sequenceBasedLayer(updatedSequenceState))
|
||||
}
|
||||
|
||||
if (sequenceState.layerNegotiationState.locallyRequestedLayer ?? 0) < topSupportedLayer.rawValue {
|
||||
updatedState = secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(modifier: modifier, peerId: peerId, state: updatedState)
|
||||
}
|
||||
|
||||
return updatedState
|
||||
} else {
|
||||
return state
|
||||
}
|
||||
case .basicLayer:
|
||||
return state
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
||||
@ -78,6 +78,16 @@ struct SecretChatOutgoingFile: PostboxCoding {
|
||||
|
||||
enum SecretChatSequenceBasedLayer: Int32 {
|
||||
case layer46 = 46
|
||||
case layer73 = 73
|
||||
|
||||
var secretChatLayer: SecretChatLayer {
|
||||
switch self {
|
||||
case .layer46:
|
||||
return .layer46
|
||||
case .layer73:
|
||||
return .layer73
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum SecretChatOutgoingOperationValue: Int32 {
|
||||
|
||||
@ -23,7 +23,7 @@ func secretChatInitiateRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId,
|
||||
let _ = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self))
|
||||
let a = MemoryBuffer(memory: aBytes, capacity: 256, length: 256, freeWhenDone: true)
|
||||
|
||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsRequestKey(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: sessionId, a: a), mutable: true, delivered: false))
|
||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsRequestKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: sessionId, a: a), mutable: true, delivered: false))
|
||||
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(SecretChatRekeySessionState(id: sessionId, data: .requesting))))
|
||||
}
|
||||
default:
|
||||
@ -69,7 +69,7 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
|
||||
|
||||
assert(remoteKeyFingerprint == keyFingerprint)
|
||||
|
||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsCommitKey(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, keyFingerprint: keyFingerprint), mutable: true, delivered: false))
|
||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsCommitKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, keyFingerprint: keyFingerprint), mutable: true, delivered: false))
|
||||
|
||||
let keyValidityOperationIndex = modifier.operationLogGetNextEntryLocalIndex(peerId: peerId, tag: OperationLogTags.SecretOutgoing)
|
||||
let keyValidityOperationCanonicalIndex = sequenceState.canonicalOutgoingOperationIndex(keyValidityOperationIndex)
|
||||
@ -92,7 +92,7 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
|
||||
return SecretChatKey(fingerprint: keyFingerprint, key: key, validity: .sequenceBasedIndexRange(fromCanonicalIndex: keyValidityOperationCanonicalIndex), useCount: 0)
|
||||
}))
|
||||
|
||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .noop(layer: .layer46, actionGloballyUniqueId: arc4random64()), mutable: true, delivered: false))
|
||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .noop(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64()), mutable: true, delivered: false))
|
||||
|
||||
return updatedState
|
||||
} else {
|
||||
@ -107,7 +107,7 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
|
||||
switch rekeySession.data {
|
||||
case .requesting, .requested:
|
||||
if rekeySessionId < rekeySession.id {
|
||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAbortSession(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id), mutable: true, delivered: false))
|
||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAbortSession(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id), mutable: true, delivered: false))
|
||||
} else {
|
||||
acceptSession = false
|
||||
}
|
||||
@ -117,14 +117,13 @@ func secretChatAdvanceRekeySessionIfNeeded(modifier: Modifier, peerId: PeerId, s
|
||||
}
|
||||
|
||||
if acceptSession {
|
||||
let sessionId = arc4random64()
|
||||
let bBytes = malloc(256)!
|
||||
let _ = SecRandomCopyBytes(nil, 256, bBytes.assumingMemoryBound(to: UInt8.self))
|
||||
let b = MemoryBuffer(memory: bBytes, capacity: 256, length: 256, freeWhenDone: true)
|
||||
|
||||
let rekeySession = SecretChatRekeySessionState(id: rekeySessionId, data: .accepting)
|
||||
|
||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAcceptKey(layer: .layer46, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, gA: gA, b: b), mutable: true, delivered: false))
|
||||
modifier.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.SecretOutgoing, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SecretChatOutgoingOperation(contents: .pfsAcceptKey(layer: sequenceState.layerNegotiationState.activeLayer, actionGloballyUniqueId: arc4random64(), rekeySessionId: rekeySession.id, gA: gA, b: b), mutable: true, delivered: false))
|
||||
return state.withUpdatedEmbeddedState(.sequenceBasedLayer(sequenceState.withUpdatedRekeyState(rekeySession)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,7 @@ public enum SecretChatRole: Int32 {
|
||||
enum SecretChatLayer: Int32 {
|
||||
case layer8 = 8
|
||||
case layer46 = 46
|
||||
case layer73 = 73
|
||||
}
|
||||
|
||||
public struct SecretChatKeySha1Fingerprint: PostboxCoding, Equatable {
|
||||
@ -240,24 +241,24 @@ enum SecretChatHandshakeState: PostboxCoding, Equatable {
|
||||
}
|
||||
|
||||
struct SecretChatLayerNegotiationState: PostboxCoding, Equatable {
|
||||
let activeLayer: Int32
|
||||
let activeLayer: SecretChatSequenceBasedLayer
|
||||
let locallyRequestedLayer: Int32?
|
||||
let remotelyRequestedLayer: Int32?
|
||||
|
||||
init(activeLayer: Int32, locallyRequestedLayer: Int32?, remotelyRequestedLayer: Int32?) {
|
||||
init(activeLayer: SecretChatSequenceBasedLayer, locallyRequestedLayer: Int32?, remotelyRequestedLayer: Int32?) {
|
||||
self.activeLayer = activeLayer
|
||||
self.locallyRequestedLayer = locallyRequestedLayer
|
||||
self.remotelyRequestedLayer = remotelyRequestedLayer
|
||||
}
|
||||
|
||||
init(decoder: PostboxDecoder) {
|
||||
self.activeLayer = decoder.decodeInt32ForKey("a", orElse: 0)
|
||||
self.activeLayer = SecretChatSequenceBasedLayer(rawValue: decoder.decodeInt32ForKey("a", orElse: 0)) ?? .layer46
|
||||
self.locallyRequestedLayer = decoder.decodeOptionalInt32ForKey("lr")
|
||||
self.remotelyRequestedLayer = decoder.decodeOptionalInt32ForKey("rr")
|
||||
}
|
||||
|
||||
func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeInt32(self.activeLayer, forKey: "a")
|
||||
encoder.encodeInt32(self.activeLayer.rawValue, forKey: "a")
|
||||
if let locallyRequestedLayer = self.locallyRequestedLayer {
|
||||
encoder.encodeInt32(locallyRequestedLayer, forKey: "lr")
|
||||
} else {
|
||||
@ -283,6 +284,14 @@ struct SecretChatLayerNegotiationState: PostboxCoding, Equatable {
|
||||
return true
|
||||
}
|
||||
|
||||
func withUpdatedActiveLayer(_ activeLayer: SecretChatSequenceBasedLayer) -> SecretChatLayerNegotiationState {
|
||||
return SecretChatLayerNegotiationState(activeLayer: activeLayer, locallyRequestedLayer: self.locallyRequestedLayer, remotelyRequestedLayer: self.remotelyRequestedLayer)
|
||||
}
|
||||
|
||||
func withUpdatedLocallyRequestedLayer(_ locallyRequestedLayer: Int32?) -> SecretChatLayerNegotiationState {
|
||||
return SecretChatLayerNegotiationState(activeLayer: self.activeLayer, locallyRequestedLayer: locallyRequestedLayer, remotelyRequestedLayer: self.remotelyRequestedLayer)
|
||||
}
|
||||
|
||||
func withUpdatedRemotelyRequestedLayer(_ remotelyRequestedLayer: Int32?) -> SecretChatLayerNegotiationState {
|
||||
return SecretChatLayerNegotiationState(activeLayer: self.activeLayer, locallyRequestedLayer: self.locallyRequestedLayer, remotelyRequestedLayer: remotelyRequestedLayer)
|
||||
}
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
import Foundation
|
||||
#if os(macOS)
|
||||
import PostboxMac
|
||||
#else
|
||||
import Postbox
|
||||
#endif
|
||||
|
||||
public enum SecretChatBridgeRole: Int32 {
|
||||
case creator
|
||||
case participant
|
||||
}
|
||||
|
||||
public struct SecretChatStateBridge {
|
||||
public let role:SecretChatBridgeRole
|
||||
public init(role: SecretChatBridgeRole) {
|
||||
self.role = role
|
||||
}
|
||||
|
||||
public var state: PeerChatState {
|
||||
return SecretChatState(role: SecretChatRole(rawValue: role.rawValue)!, embeddedState: .terminated, keychain: SecretChatKeychain(keys: []), keyFingerprint: nil, messageAutoremoveTimeout: nil)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public func terminateLegacySecretChat(modifier: Modifier, peerId: PeerId, state: SecretChatStateBridge) -> PeerChatState {
|
||||
return addSecretChatOutgoingOperation(modifier: modifier, peerId: peerId, operation: SecretChatOutgoingOperationContents.terminate(reportSpam: false), state: state.state as! SecretChatState).withUpdatedEmbeddedState(.terminated)
|
||||
}
|
||||
@ -90,16 +90,11 @@ public class BoxedMessage: NSObject {
|
||||
#else
|
||||
redact = true
|
||||
#endif
|
||||
return recursiveDescription(redact: redact, of: self.body)
|
||||
/*if let body = self.body as? RedactingCustomStringConvertible {
|
||||
|
||||
return body.redactingDescription(false)
|
||||
#else
|
||||
return body.redactingDescription(true)
|
||||
#endif
|
||||
if let body = self.body as? RedactingCustomStringConvertible {
|
||||
return body.redactingDescription(redact)
|
||||
} else {
|
||||
return "\(self.body)"
|
||||
}*/
|
||||
return recursiveDescription(redact: redact, of: self.body)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -299,7 +299,7 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI
|
||||
return (TelegramMediaExpiredContent(data: .file), nil)
|
||||
}
|
||||
case let .messageMediaWebPage(webpage):
|
||||
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage) {
|
||||
if let mediaWebpage = telegramMediaWebpageFromApiWebpage(webpage, url: nil) {
|
||||
return (mediaWebpage, nil)
|
||||
}
|
||||
case .messageMediaUnsupported:
|
||||
@ -475,7 +475,7 @@ extension StoreMessage {
|
||||
}
|
||||
|
||||
if let viaBotId = viaBotId {
|
||||
attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId)))
|
||||
attributes.append(InlineBotMessageAttribute(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: viaBotId), title: nil))
|
||||
}
|
||||
|
||||
if let replyToMsgId = replyToMsgId {
|
||||
|
||||
@ -191,7 +191,7 @@ public func ==(lhs: TelegramMediaWebpageLoadedContent, rhs: TelegramMediaWebpage
|
||||
}
|
||||
|
||||
public enum TelegramMediaWebpageContent {
|
||||
case Pending(Int32)
|
||||
case Pending(Int32, String?)
|
||||
case Loaded(TelegramMediaWebpageLoadedContent)
|
||||
}
|
||||
|
||||
@ -213,7 +213,7 @@ public final class TelegramMediaWebpage: Media, Equatable {
|
||||
self.webpageId = MediaId(decoder.decodeBytesForKeyNoCopy("i")!)
|
||||
|
||||
if decoder.decodeInt32ForKey("ct", orElse: 0) == 0 {
|
||||
self.content = .Pending(decoder.decodeInt32ForKey("pendingDate", orElse: 0))
|
||||
self.content = .Pending(decoder.decodeInt32ForKey("pendingDate", orElse: 0), decoder.decodeOptionalStringForKey("pendingUrl"))
|
||||
} else {
|
||||
self.content = .Loaded(TelegramMediaWebpageLoadedContent(decoder: decoder))
|
||||
}
|
||||
@ -225,9 +225,14 @@ public final class TelegramMediaWebpage: Media, Equatable {
|
||||
encoder.encodeBytes(buffer, forKey: "i")
|
||||
|
||||
switch self.content {
|
||||
case let .Pending(date):
|
||||
case let .Pending(date, url):
|
||||
encoder.encodeInt32(0, forKey: "ct")
|
||||
encoder.encodeInt32(date, forKey: "pendingDate")
|
||||
if let url = url {
|
||||
encoder.encodeString(url, forKey: "pendingUrl")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "pendingUrl")
|
||||
}
|
||||
case let .Loaded(loadedContent):
|
||||
encoder.encodeInt32(1, forKey: "ct")
|
||||
loadedContent.encode(encoder)
|
||||
@ -251,10 +256,14 @@ public final class TelegramMediaWebpage: Media, Equatable {
|
||||
}
|
||||
|
||||
switch lhs.content {
|
||||
case let .Pending(lhsDate):
|
||||
case let .Pending(lhsDate, lhsUrl):
|
||||
switch rhs.content {
|
||||
case let .Pending(rhsDate) where lhsDate == rhsDate:
|
||||
case let .Pending(rhsDate, rhsUrl):
|
||||
if lhsDate == rhsDate, lhsUrl == rhsUrl {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
default:
|
||||
return false
|
||||
}
|
||||
@ -269,12 +278,12 @@ public final class TelegramMediaWebpage: Media, Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage) -> TelegramMediaWebpage? {
|
||||
func telegramMediaWebpageFromApiWebpage(_ webpage: Api.WebPage, url: String?) -> TelegramMediaWebpage? {
|
||||
switch webpage {
|
||||
case .webPageNotModified:
|
||||
return nil
|
||||
case let .webPagePending(id, date):
|
||||
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date))
|
||||
return TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.CloudWebpage, id: id), content: .Pending(date, url))
|
||||
case let .webPage(_, id, url, displayUrl, hash, type, siteName, title, description, photo, embedUrl, embedType, embedWidth, embedHeight, duration, author, document, cachedPage):
|
||||
var embedSize: CGSize?
|
||||
if let embedWidth = embedWidth, let embedHeight = embedHeight {
|
||||
|
||||
@ -45,18 +45,8 @@ func updateSecretChat(accountPeerId: PeerId, modifier: Modifier, chat: Api.Encry
|
||||
|
||||
var updatedState = currentState.withUpdatedKeychain(SecretChatKeychain(keys: [SecretChatKey(fingerprint: keyFingerprint, key: MemoryBuffer(data: key), validity: .indefinite, useCount: 0)])).withUpdatedEmbeddedState(.basicLayer).withUpdatedKeyFingerprint(SecretChatKeyFingerprint(sha1: SecretChatKeySha1Fingerprint(digest: sha1Digest(key)), sha256: SecretChatKeySha256Fingerprint(digest: sha256Digest(key))))
|
||||
|
||||
var layer: SecretChatLayer?
|
||||
switch updatedState.embeddedState {
|
||||
case .terminated, .handshake:
|
||||
break
|
||||
case .basicLayer:
|
||||
layer = .layer8
|
||||
case let .sequenceBasedLayer(sequenceState):
|
||||
layer = SecretChatLayer(rawValue: sequenceState.layerNegotiationState.activeLayer)
|
||||
}
|
||||
if let layer = layer {
|
||||
updatedState = addSecretChatOutgoingOperation(modifier: modifier, peerId: currentPeer.id, operation: .reportLayerSupport(layer: layer, actionGloballyUniqueId: arc4random64(), layerSupport: 46), state: updatedState)
|
||||
}
|
||||
updatedState = secretChatAddReportCurrentLayerSupportOperationAndUpdateRequestedLayer(modifier: modifier, peerId: currentPeer.id, state: updatedState)
|
||||
|
||||
modifier.setPeerChatState(currentPeer.id, state: updatedState)
|
||||
updatePeers(modifier: modifier, peers: [currentPeer.withUpdatedEmbeddedState(updatedState.embeddedState.peerState)], update: { _, updated in
|
||||
return updated
|
||||
|
||||
@ -21,7 +21,7 @@ public func webpagePreview(account: Account, url: String, webpageId: MediaId? =
|
||||
|> mapToSignal { result -> Signal<TelegramMediaWebpage?, NoError> in
|
||||
switch result {
|
||||
case let .messageMediaWebPage(webpage):
|
||||
if let media = telegramMediaWebpageFromApiWebpage(webpage) {
|
||||
if let media = telegramMediaWebpageFromApiWebpage(webpage, url: url) {
|
||||
if case .Loaded = media.content {
|
||||
return .single(media)
|
||||
} else {
|
||||
@ -45,9 +45,9 @@ public func actualizedWebpage(postbox: Postbox, network: Network, webpage: Teleg
|
||||
return .single(.webPageNotModified)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<TelegramMediaWebpage, NoError> in
|
||||
if let updatedWebpage = telegramMediaWebpageFromApiWebpage(result), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId {
|
||||
if let updatedWebpage = telegramMediaWebpageFromApiWebpage(result, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId == webpage.webpageId {
|
||||
return postbox.modify { modifier -> TelegramMediaWebpage in
|
||||
modifier.updateMedia(updatedWebpage.webpageId, update: updatedWebpage)
|
||||
modifier.updateMedia(webpage.webpageId, update: updatedWebpage)
|
||||
return updatedWebpage
|
||||
}
|
||||
} else {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user