From e89f708e6f164a12b779969a63ac3f5c82a3d304 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 22 Nov 2016 21:33:06 +0300 Subject: [PATCH] no message --- TelegramCore.xcodeproj/project.pbxproj | 30 ++ TelegramCore/Account.swift | 1 + TelegramCore/ChatContextResult.swift | 411 ++++++++++++++++++ TelegramCore/CloudFileMediaResource.swift | 76 +++- TelegramCore/EnqueueMessage.swift | 29 +- TelegramCore/Fetch.swift | 2 + TelegramCore/FetchHttpResource.swift | 29 ++ ...ingChatContextResultMessageAttribute.swift | 26 ++ ...OutgoingMessageWithChatContextResult.swift | 152 +++++++ TelegramCore/PeerParticipants.swift | 115 ++--- TelegramCore/PendingMessageManager.swift | 12 +- .../PendingMessageUploadedContent.swift | 10 + .../ReplyMarkupMessageAttribute.swift | 92 ++-- TelegramCore/RequestChatContextResults.swift | 34 ++ .../RequestMessageActionCallback.swift | 25 +- TelegramCore/ResolvePeerByName.swift | 7 +- TelegramCore/StateManagement.swift | 10 + TelegramCore/StoreMessage_Telegram.swift | 12 +- TelegramCore/TelegramMediaFile.swift | 16 +- TelegramCore/TelegramMediaMap.swift | 4 +- TelegramCore/TelegramUser.swift | 105 ++++- .../TextEntitiesMessageAttribute.swift | 91 +++- TelegramCore/UpdateCachedPeerData.swift | 6 +- TelegramCore/UpdatesApiUtils.swift | 6 + 24 files changed, 1150 insertions(+), 151 deletions(-) create mode 100644 TelegramCore/ChatContextResult.swift create mode 100644 TelegramCore/FetchHttpResource.swift create mode 100644 TelegramCore/OutgoingChatContextResultMessageAttribute.swift create mode 100644 TelegramCore/OutgoingMessageWithChatContextResult.swift create mode 100644 TelegramCore/RequestChatContextResults.swift diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index d56fa5be45..9ba429aff9 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -210,10 +210,20 @@ D0B8444F1DAB9206005F29E1 /* ChangePeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B843961DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift */; }; D0B844531DAC0773005F29E1 /* TelegramUserPresence.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */; }; D0CAF2EA1D75EC600011F558 /* MtProtoKitDynamic.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0CAF2E91D75EC600011F558 /* MtProtoKitDynamic.framework */; }; + D0DC354E1DE368F7000195EB /* RequestChatContextResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */; }; + D0DC35501DE36900000195EB /* ChatContextResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC354F1DE36900000195EB /* ChatContextResult.swift */; }; + D0DC35511DE36908000195EB /* RequestChatContextResults.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */; }; + D0DC35521DE36908000195EB /* ChatContextResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC354F1DE36900000195EB /* ChatContextResult.swift */; }; D0DF0C8A1D819C7E008AEB01 /* JoinChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C891D819C7E008AEB01 /* JoinChannel.swift */; }; D0DF0C911D81A857008AEB01 /* ImageRepresentationsUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C901D81A857008AEB01 /* ImageRepresentationsUtils.swift */; }; D0DF0C931D81AD09008AEB01 /* MessageUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0C921D81AD09008AEB01 /* MessageUtils.swift */; }; D0DF0CA81D82BF32008AEB01 /* PeerParticipants.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DF0CA71D82BF32008AEB01 /* PeerParticipants.swift */; }; + D0E35A0E1DE4953E00BC6096 /* FetchHttpResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A0D1DE4953E00BC6096 /* FetchHttpResource.swift */; }; + D0E35A101DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A0F1DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift */; }; + D0E35A121DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */; }; + D0E35A131DE4C69100BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */; }; + D0E35A141DE4C69C00BC6096 /* FetchHttpResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A0D1DE4953E00BC6096 /* FetchHttpResource.swift */; }; + D0E35A151DE4C6A200BC6096 /* OutgoingMessageWithChatContextResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A0F1DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift */; }; D0F3CC791DDE2859008148FA /* SearchMessages.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0D711D631ABA00955575 /* SearchMessages.swift */; }; D0F3CC7A1DDE2859008148FA /* RequestMessageActionCallback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */; }; D0F3CC7B1DDE2859008148FA /* RequestEditMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */; }; @@ -368,10 +378,15 @@ D0B843B11DA7FF30005F29E1 /* NBPhoneNumberUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NBPhoneNumberUtil.m; sourceTree = ""; }; D0B844521DAC0773005F29E1 /* TelegramUserPresence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramUserPresence.swift; sourceTree = ""; }; D0CAF2E91D75EC600011F558 /* MtProtoKitDynamic.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MtProtoKitDynamic.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphonesimulator/MtProtoKitDynamic.framework"; sourceTree = ""; }; + D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestChatContextResults.swift; sourceTree = ""; }; + D0DC354F1DE36900000195EB /* ChatContextResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChatContextResult.swift; sourceTree = ""; }; D0DF0C891D819C7E008AEB01 /* JoinChannel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JoinChannel.swift; sourceTree = ""; }; D0DF0C901D81A857008AEB01 /* ImageRepresentationsUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageRepresentationsUtils.swift; sourceTree = ""; }; D0DF0C921D81AD09008AEB01 /* MessageUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageUtils.swift; sourceTree = ""; }; D0DF0CA71D82BF32008AEB01 /* PeerParticipants.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerParticipants.swift; sourceTree = ""; }; + D0E35A0D1DE4953E00BC6096 /* FetchHttpResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchHttpResource.swift; sourceTree = ""; }; + D0E35A0F1DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingMessageWithChatContextResult.swift; sourceTree = ""; }; + D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OutgoingChatContextResultMessageAttribute.swift; sourceTree = ""; }; D0F3CC7C1DDE289E008148FA /* ResolvePeerByName.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ResolvePeerByName.swift; sourceTree = ""; }; D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditedMessageAttribute.swift; sourceTree = ""; }; D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = ""; }; @@ -536,6 +551,7 @@ D073CE5F1DCB9D14007511FD /* OutgoingMessageInfoAttribute.swift */, D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */, D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */, + D0E35A111DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift */, ); name = Attributes; sourceTree = ""; @@ -586,6 +602,7 @@ children = ( D03B0D431D6319F900955575 /* CloudFileMediaResource.swift */, D03B0D391D6319E200955575 /* Fetch.swift */, + D0E35A0D1DE4953E00BC6096 /* FetchHttpResource.swift */, ); name = Resources; sourceTree = ""; @@ -635,6 +652,9 @@ D03B0D711D631ABA00955575 /* SearchMessages.swift */, D01AC91C1DD5DA5E00E8160F /* RequestMessageActionCallback.swift */, D01AC9201DD5E7E500E8160F /* RequestEditMessage.swift */, + D0DC354D1DE368F7000195EB /* RequestChatContextResults.swift */, + D0DC354F1DE36900000195EB /* ChatContextResult.swift */, + D0E35A0F1DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift */, ); name = Messages; sourceTree = ""; @@ -936,6 +956,7 @@ D03B0D5F1D631A6900955575 /* Serialization.swift in Sources */, D03B0D441D6319F900955575 /* CloudFileMediaResource.swift in Sources */, D01AC9211DD5E7E500E8160F /* RequestEditMessage.swift in Sources */, + D0E35A0E1DE4953E00BC6096 /* FetchHttpResource.swift in Sources */, D01AC91D1DD5DA5E00E8160F /* RequestMessageActionCallback.swift in Sources */, D09BB6B61DB0428000A905C0 /* PendingMessageUploadedContent.swift in Sources */, D0F3CC7D1DDE289E008148FA /* ResolvePeerByName.swift in Sources */, @@ -958,6 +979,7 @@ D03B0CF51D62250800955575 /* TelegramMediaContact.swift in Sources */, D03B0CFB1D62250800955575 /* TelegramMediaWebpage.swift in Sources */, D09A2FEB1D7CDC320018FB72 /* PeerAccessRestrictionInfo.swift in Sources */, + D0E35A101DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift in Sources */, D003702B1DA42586004308D3 /* PhoneNumber.swift in Sources */, D03B0CF91D62250800955575 /* TelegramMediaMap.swift in Sources */, D03B0D671D631A8B00955575 /* AccountViewTracker.swift in Sources */, @@ -974,8 +996,10 @@ D0F7AB2F1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */, D0B843971DA7FBBC005F29E1 /* ChangePeerNotificationSettings.swift in Sources */, D09BB6B41DB02C2B00A905C0 /* PendingMessageManager.swift in Sources */, + D0DC354E1DE368F7000195EB /* RequestChatContextResults.swift in Sources */, D0B843851DA6EDC4005F29E1 /* CachedChannelData.swift in Sources */, D0B843831DA6EDB8005F29E1 /* CachedGroupData.swift in Sources */, + D0E35A121DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */, D0B844531DAC0773005F29E1 /* TelegramUserPresence.swift in Sources */, D0B843871DA6F705005F29E1 /* UpdateCachedPeerData.swift in Sources */, D03B0D6D1D631AA300955575 /* ContactManagement.swift in Sources */, @@ -985,6 +1009,7 @@ D03B0D0C1D62255C00955575 /* StateManagement.swift in Sources */, D073CE5D1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift in Sources */, D03B0D721D631ABA00955575 /* SearchMessages.swift in Sources */, + D0DC35501DE36900000195EB /* ChatContextResult.swift in Sources */, D03B0D5C1D631A6900955575 /* Download.swift in Sources */, D0B843C71DA7FF30005F29E1 /* NBPhoneNumberDefines.m in Sources */, D03B0D5D1D631A6900955575 /* MultipartFetch.swift in Sources */, @@ -1046,6 +1071,7 @@ D0B8443B1DAB91EF005F29E1 /* Holes.swift in Sources */, D0F3CC7A1DDE2859008148FA /* RequestMessageActionCallback.swift in Sources */, D073CEA11DCBF3D3007511FD /* StickerPack.swift in Sources */, + D0E35A141DE4C69C00BC6096 /* FetchHttpResource.swift in Sources */, D0B8443E1DAB91EF005F29E1 /* SynchronizePeerReadState.swift in Sources */, D0B8440D1DAB91CD005F29E1 /* ImageRepresentationsUtils.swift in Sources */, D03C536A1DAD5CA9004C17B3 /* TelegramUser.swift in Sources */, @@ -1057,6 +1083,7 @@ D0B8442D1DAB91E0005F29E1 /* NBMetadataCoreTest.m in Sources */, D0B844131DAB91CD005F29E1 /* StringFormat.swift in Sources */, D0B844401DAB91EF005F29E1 /* UpdateMessageService.swift in Sources */, + D0E35A131DE4C69100BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */, D0B418961D7E0580004562A4 /* TelegramMediaFile.swift in Sources */, D0B4189B1D7E0580004562A4 /* TelegramMediaWebpage.swift in Sources */, D0B844341DAB91E0005F29E1 /* NBPhoneNumberDefines.m in Sources */, @@ -1067,6 +1094,7 @@ D0B418A61D7E0592004562A4 /* CloudFileMediaResource.swift in Sources */, D073CEA51DCBF3F5007511FD /* StickerManagement.swift in Sources */, D03C536D1DAD5CA9004C17B3 /* ApiGroupOrChannel.swift in Sources */, + D0E35A151DE4C6A200BC6096 /* OutgoingMessageWithChatContextResult.swift in Sources */, D0B418A91D7E0597004562A4 /* Buffer.swift in Sources */, D0B8442E1DAB91E0005F29E1 /* NBMetadataCoreTestMapper.m in Sources */, D0B8443F1DAB91EF005F29E1 /* UpdateGroup.swift in Sources */, @@ -1080,9 +1108,11 @@ D0B8444B1DAB91FD005F29E1 /* ManagedSynchronizePeerReadStates.swift in Sources */, D073CE6C1DCBCF17007511FD /* TextEntitiesMessageAttribute.swift in Sources */, D03C53751DAD5CA9004C17B3 /* TelegramUserPresence.swift in Sources */, + D0DC35521DE36908000195EB /* ChatContextResult.swift in Sources */, D0F7AB301DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift in Sources */, D073CE6D1DCBCF17007511FD /* InlineBotMessageAttribute.swift in Sources */, D0B8440F1DAB91CD005F29E1 /* Either.swift in Sources */, + D0DC35511DE36908000195EB /* RequestChatContextResults.swift in Sources */, D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */, D0B844111DAB91CD005F29E1 /* Regex.swift in Sources */, D0B8443C1DAB91EF005F29E1 /* SendUnsentMessage.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index f5ce867923..8973a8e491 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -243,6 +243,7 @@ private var declaredEncodables: Void = { declareEncodable(EditedMessageAttribute.self, f: { EditedMessageAttribute(decoder: $0) }) declareEncodable(ReplyMarkupMessageAttribute.self, f: { ReplyMarkupMessageAttribute(decoder: $0) }) declareEncodable(CachedResolvedByNamePeer.self, f: { CachedResolvedByNamePeer(decoder: $0) }) + declareEncodable(OutgoingChatContextResultMessageAttribute.self, f: { OutgoingChatContextResultMessageAttribute(decoder: $0) }) return }() diff --git a/TelegramCore/ChatContextResult.swift b/TelegramCore/ChatContextResult.swift new file mode 100644 index 0000000000..830b9da467 --- /dev/null +++ b/TelegramCore/ChatContextResult.swift @@ -0,0 +1,411 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +public enum ChatContextResultMessage: Coding, Equatable { + case auto(caption: String, replyMarkup: ReplyMarkupMessageAttribute?) + case text(text: String, entities: TextEntitiesMessageAttribute?, disableUrlPreview: Bool, replyMarkup: ReplyMarkupMessageAttribute?) + case mapLocation(media: TelegramMediaMap, replyMarkup: ReplyMarkupMessageAttribute?) + case contact(media: TelegramMediaContact, replyMarkup: ReplyMarkupMessageAttribute?) + + public init(decoder: Decoder) { + switch decoder.decodeInt32ForKey("_v") as Int32 { + case 0: + self = .auto(caption: decoder.decodeStringForKey("c"), replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute) + case 1: + self = .text(text: decoder.decodeStringForKey("t"), entities: decoder.decodeObjectForKey("e") as? TextEntitiesMessageAttribute, disableUrlPreview: decoder.decodeInt32ForKey("du") != 0, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute) + case 2: + self = .mapLocation(media: decoder.decodeObjectForKey("l") as! TelegramMediaMap, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute) + case 3: + self = .contact(media: decoder.decodeObjectForKey("c") as! TelegramMediaContact, replyMarkup: decoder.decodeObjectForKey("m") as? ReplyMarkupMessageAttribute) + default: + self = .auto(caption: "", replyMarkup: nil) + } + } + + public func encode(_ encoder: Encoder) { + switch self { + case let .auto(caption, replyMarkup): + encoder.encodeInt32(0, forKey: "_v") + encoder.encodeString(caption, forKey: "c") + if let replyMarkup = replyMarkup { + encoder.encodeObject(replyMarkup, forKey: "m") + } else { + encoder.encodeNil(forKey: "m") + } + case let .text(text, entities, disableUrlPreview, replyMarkup): + encoder.encodeInt32(1, forKey: "_v") + encoder.encodeString(text, forKey: "t") + if let entities = entities { + encoder.encodeObject(entities, forKey: "e") + } else { + encoder.encodeNil(forKey: "e") + } + encoder.encodeInt32(disableUrlPreview ? 1 : 0, forKey: "du") + if let replyMarkup = replyMarkup { + encoder.encodeObject(replyMarkup, forKey: "m") + } else { + encoder.encodeNil(forKey: "m") + } + case let .mapLocation(media, replyMarkup): + encoder.encodeInt32(2, forKey: "_v") + encoder.encodeObject(media, forKey: "l") + if let replyMarkup = replyMarkup { + encoder.encodeObject(replyMarkup, forKey: "m") + } else { + encoder.encodeNil(forKey: "m") + } + case let .contact(media, replyMarkup): + encoder.encodeInt32(3, forKey: "_v") + encoder.encodeObject(media, forKey: "c") + if let replyMarkup = replyMarkup { + encoder.encodeObject(replyMarkup, forKey: "m") + } else { + encoder.encodeNil(forKey: "m") + } + } + } + + public static func ==(lhs: ChatContextResultMessage, rhs: ChatContextResultMessage) -> Bool { + switch lhs { + case let .auto(lhsCaption, lhsReplyMarkup): + if case let .auto(rhsCaption, rhsReplyMarkup) = rhs { + if lhsCaption != rhsCaption { + return false + } + if lhsReplyMarkup != rhsReplyMarkup { + return false + } + return true + } else { + return false + } + case let .text(lhsText, lhsEntities, lhsDisableUrlPreview, lhsReplyMarkup): + if case let .text(rhsText, rhsEntities, rhsDisableUrlPreview, rhsReplyMarkup) = rhs { + if lhsText != rhsText { + return false + } + if lhsEntities != rhsEntities { + return false + } + if lhsDisableUrlPreview != rhsDisableUrlPreview { + return false + } + if lhsReplyMarkup != rhsReplyMarkup { + return false + } + return true + } else { + return false + } + case let .mapLocation(lhsMedia, lhsReplyMarkup): + if case let .mapLocation(rhsMedia, rhsReplyMarkup) = rhs { + if !lhsMedia.isEqual(rhsMedia) { + return false + } + if lhsReplyMarkup != rhsReplyMarkup { + return false + } + return true + } else { + return false + } + case let .contact(lhsMedia, lhsReplyMarkup): + if case let .contact(rhsMedia, rhsReplyMarkup) = rhs { + if !lhsMedia.isEqual(rhsMedia) { + return false + } + if lhsReplyMarkup != rhsReplyMarkup { + return false + } + return true + } else { + return false + } + } + } +} + +public enum ChatContextResult: Equatable { + case externalReference(id: String, type: String, title: String?, description: String?, url: String?, thumbnailUrl: String?, contentUrl: String?, contentType: String?, dimensions: CGSize?, duration: Int32?, message: ChatContextResultMessage) + case internalReference(id: String, type: String, title: String?, description: String?, image: TelegramMediaImage?, file: TelegramMediaFile?, message: ChatContextResultMessage) + + public var id: String { + switch self { + case let .externalReference(id, _, _, _, _, _, _, _, _, _, _): + return id + case let .internalReference(id, _, _, _, _, _, _): + return id + } + } + + public var type: String { + switch self { + case let .externalReference(_, type, _, _, _, _, _, _, _, _, _): + return type + case let .internalReference(_, type, _, _, _, _, _): + return type + } + } + + public var title: String? { + switch self { + case let .externalReference(_, _, title, _, _, _, _, _, _, _, _): + return title + case let .internalReference(_, _, title, _, _, _, _): + return title + } + } + + public var description: String? { + switch self { + case let .externalReference(_, _, _, description, _, _, _, _, _, _, _): + return description + case let .internalReference(_, _, _, description, _, _, _): + return description + } + } + + public var message: ChatContextResultMessage { + switch self { + case let .externalReference(_, _, _, _, _, _, _, _, _, _, message): + return message + case let .internalReference(_, _, _, _, _, _, message): + return message + } + } + + public static func ==(lhs: ChatContextResult, rhs: ChatContextResult) -> Bool { + switch lhs { + case let .externalReference(lhsId, lhsType, lhsTitle, lhsDescription, lhsUrl, lhsThumbnailUrl, lhsContentUrl, lhsContentType, lhsDimensions, lhsDuration, lhsMessage): + if case let .externalReference(rhsId, rhsType, rhsTitle, rhsDescription, rhsUrl, rhsThumbnailUrl, rhsContentUrl, rhsContentType, rhsDimensions, rhsDuration, rhsMessage) = rhs { + if lhsId != rhsId { + return false + } + if lhsType != rhsType { + return false + } + if lhsTitle != rhsTitle { + return false + } + if lhsDescription != rhsDescription { + return false + } + if lhsUrl != rhsUrl { + return false + } + if lhsThumbnailUrl != rhsThumbnailUrl { + return false + } + if lhsContentUrl != rhsContentUrl { + return false + } + if lhsContentType != rhsContentType { + return false + } + if lhsDimensions != rhsDimensions { + return false + } + if lhsDuration != rhsDuration { + return false + } + if lhsMessage != rhsMessage { + return false + } + return true + } else { + return false + } + case let .internalReference(lhsId, lhsType, lhsTitle, lhsDescription, lhsImage, lhsFile, lhsMessage): + if case let .internalReference(rhsId, rhsType, rhsTitle, rhsDescription, rhsImage, rhsFile, rhsMessage) = rhs { + if lhsId != rhsId { + return false + } + if lhsType != rhsType { + return false + } + if lhsTitle != rhsTitle { + return false + } + if lhsDescription != rhsDescription { + return false + } + if let lhsImage = lhsImage, let rhsImage = rhsImage { + if !lhsImage.isEqual(rhsImage) { + return false + } + } else if (lhsImage != nil) != (rhsImage != nil) { + return false + } + if let lhsFile = lhsFile, let rhsFile = rhsFile { + if !lhsFile.isEqual(rhsFile) { + return false + } + } else if (lhsFile != nil) != (rhsFile != nil) { + return false + } + if lhsMessage != rhsMessage { + return false + } + return true + } else { + return false + } + } + } +} + +public enum ChatContextResultCollectionPresentation { + case media + case list +} + +public struct ChatContextResultSwitchPeer: Equatable { + public let text: String + public let startParam: String + + public static func ==(lhs: ChatContextResultSwitchPeer, rhs: ChatContextResultSwitchPeer) -> Bool { + return lhs.text == rhs.text && lhs.startParam == rhs.startParam + } +} + +public final class ChatContextResultCollection: Equatable { + public let botId: PeerId + public let queryId: Int64 + public let nextOffset: String? + public let presentation: ChatContextResultCollectionPresentation + public let switchPeer: ChatContextResultSwitchPeer? + public let results: [ChatContextResult] + public let cacheTimeout: Int32 + + init(botId: PeerId, queryId: Int64, nextOffset: String?, presentation: ChatContextResultCollectionPresentation, switchPeer: ChatContextResultSwitchPeer?, results: [ChatContextResult], cacheTimeout: Int32) { + self.botId = botId + self.queryId = queryId + self.nextOffset = nextOffset + self.presentation = presentation + self.switchPeer = switchPeer + self.results = results + self.cacheTimeout = cacheTimeout + } + + public static func ==(lhs: ChatContextResultCollection, rhs: ChatContextResultCollection) -> Bool { + if lhs.botId != rhs.botId { + return false + } + if lhs.queryId != rhs.queryId { + return false + } + if lhs.nextOffset != rhs.nextOffset { + return false + } + if lhs.presentation != rhs.presentation { + return false + } + if lhs.switchPeer != rhs.switchPeer { + return false + } + if lhs.results != rhs.results { + return false + } + if lhs.cacheTimeout != rhs.cacheTimeout { + return false + } + return true + } +} + +extension ChatContextResultMessage { + init(apiMessage: Api.BotInlineMessage) { + switch apiMessage { + case let .botInlineMessageMediaAuto(_, caption, replyMarkup): + var parsedReplyMarkup: ReplyMarkupMessageAttribute? + if let replyMarkup = replyMarkup { + parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup) + } + self = .auto(caption: caption, replyMarkup: parsedReplyMarkup) + case let .botInlineMessageText(flags, message, entities, replyMarkup): + var parsedEntities: TextEntitiesMessageAttribute? + if let entities = entities, !entities.isEmpty { + parsedEntities = TextEntitiesMessageAttribute(entities: messageTextEntitiesFromApiEntities(entities)) + } + var parsedReplyMarkup: ReplyMarkupMessageAttribute? + if let replyMarkup = replyMarkup { + parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup) + } + self = .text(text: message, entities: parsedEntities, disableUrlPreview: (flags & (1 << 0)) != 0, replyMarkup: parsedReplyMarkup) + case let .botInlineMessageMediaGeo(_, geo, replyMarkup): + let media = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil) + var parsedReplyMarkup: ReplyMarkupMessageAttribute? + if let replyMarkup = replyMarkup { + parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup) + } + self = .mapLocation(media: media, replyMarkup: parsedReplyMarkup) + case let .botInlineMessageMediaVenue(_, geo, title, address, provider, venueId, replyMarkup): + let media = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId) + var parsedReplyMarkup: ReplyMarkupMessageAttribute? + if let replyMarkup = replyMarkup { + parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup) + } + self = .mapLocation(media: media, replyMarkup: parsedReplyMarkup) + case let .botInlineMessageMediaContact(_, phoneNumber, firstName, lastName, replyMarkup): + let media = TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: nil) + var parsedReplyMarkup: ReplyMarkupMessageAttribute? + if let replyMarkup = replyMarkup { + parsedReplyMarkup = ReplyMarkupMessageAttribute(apiMarkup: replyMarkup) + } + self = .contact(media: media, replyMarkup: parsedReplyMarkup) + } + } +} + +extension ChatContextResult { + init(apiResult: Api.BotInlineResult) { + switch apiResult { + case let .botInlineResult(_, id, type, title, description, url, thumbUrl, contentUrl, contentType, w, h, duration, sendMessage): + var dimensions: CGSize? + if let w = w, let h = h { + dimensions = CGSize(width: CGFloat(w), height: CGFloat(h)) + } + self = .externalReference(id: id, type: type, title: title, description: description, url: url, thumbnailUrl: thumbUrl, contentUrl: contentUrl, contentType: contentType, dimensions: dimensions, duration: duration, message: ChatContextResultMessage(apiMessage: sendMessage)) + case let .botInlineMediaResult(flags, id, type, photo, document, title, description, sendMessage): + var image: TelegramMediaImage? + var file: TelegramMediaFile? + if let photo = photo, let parsedImage = telegramMediaImageFromApiPhoto(photo) { + image = parsedImage + } + if let document = document, let parsedFile = telegramMediaFileFromApiDocument(document) { + file = parsedFile + } + self = .internalReference(id: id, type: type, title: title, description: description, image: image, file: file, message: ChatContextResultMessage(apiMessage: sendMessage)) + } + } +} + +extension ChatContextResultSwitchPeer { + init(apiSwitchPeer: Api.InlineBotSwitchPM) { + switch apiSwitchPeer { + case let .inlineBotSwitchPM(text, startParam): + self.init(text: text, startParam: startParam) + } + } +} + +extension ChatContextResultCollection { + convenience init(apiResults: Api.messages.BotResults, botId: PeerId) { + switch apiResults { + case let .botResults(flags, queryId, nextOffset, switchPm, results, cacheTime): + var switchPeer: ChatContextResultSwitchPeer? + if let switchPm = switchPm { + switchPeer = ChatContextResultSwitchPeer(apiSwitchPeer: switchPm) + } + self.init(botId: botId, queryId: queryId, nextOffset: nextOffset, presentation: (flags & (1 << 0) != 0) ? .media : .list, switchPeer: switchPeer, results: results.map { ChatContextResult(apiResult: $0) }, cacheTimeout: cacheTime) + } + } +} diff --git a/TelegramCore/CloudFileMediaResource.swift b/TelegramCore/CloudFileMediaResource.swift index 51e4cdd4ed..9b99ba1e0e 100644 --- a/TelegramCore/CloudFileMediaResource.swift +++ b/TelegramCore/CloudFileMediaResource.swift @@ -132,7 +132,7 @@ public class CloudDocumentMediaResource: TelegramCloudMediaResource { public let datacenterId: Int let fileId: Int64 let accessHash: Int64 - public let size: Int + public let size: Int? public var id: MediaResourceId { return CloudDocumentMediaResourceId(datacenterId: self.datacenterId, fileId: self.fileId, accessHash: self.accessHash) @@ -142,7 +142,7 @@ public class CloudDocumentMediaResource: TelegramCloudMediaResource { return Api.InputFileLocation.inputDocumentFileLocation(id: self.fileId, accessHash: self.accessHash, version: 0) } - public init(datacenterId: Int, fileId: Int64, accessHash: Int64, size: Int) { + public init(datacenterId: Int, fileId: Int64, accessHash: Int64, size: Int?) { self.datacenterId = datacenterId self.fileId = fileId self.accessHash = accessHash @@ -153,14 +153,22 @@ public class CloudDocumentMediaResource: TelegramCloudMediaResource { self.datacenterId = Int(decoder.decodeInt32ForKey("d") as Int32) self.fileId = decoder.decodeInt64ForKey("f") self.accessHash = decoder.decodeInt64ForKey("a") - self.size = Int(decoder.decodeInt32ForKey("n") as Int32) + if let size = (decoder.decodeInt32ForKey("n") as Int32?) { + self.size = Int(size) + } else { + self.size = nil + } } public func encode(_ encoder: Encoder) { encoder.encodeInt32(Int32(self.datacenterId), forKey: "d") encoder.encodeInt64(self.fileId, forKey: "f") encoder.encodeInt64(self.accessHash, forKey: "a") - encoder.encodeInt32(Int32(size), forKey: "n") + if let size = self.size { + encoder.encodeInt32(Int32(size), forKey: "n") + } else { + encoder.encodeNil(forKey: "n") + } } public func isEqual(to: TelegramMediaResource) -> Bool { @@ -320,6 +328,66 @@ public class LocalFileReferenceMediaResource: TelegramMediaResource { } } +public struct HttpReferenceMediaResourceId: MediaResourceId { + public let url: String + + public func isEqual(to: MediaResourceId) -> Bool { + if let to = to as? HttpReferenceMediaResourceId { + return self.url == to.url + } else { + return false + } + } + + public var hashValue: Int { + return self.url.hashValue + } + + public var uniqueId: String { + return "http-\(persistentHash32(self.url))" + } +} + +public final class HttpReferenceMediaResource: TelegramMediaResource { + public let url: String + public let size: Int? + + public init(url: String, size: Int?) { + self.url = url + self.size = size + } + + public required init(decoder: Decoder) { + self.url = decoder.decodeStringForKey("u") + if let size = (decoder.decodeInt32ForKey("s") as Int32?) { + self.size = Int(size) + } else { + self.size = nil + } + } + + public func encode(_ encoder: Encoder) { + encoder.encodeString(self.url, forKey: "u") + if let size = self.size { + encoder.encodeInt32(Int32(size), forKey: "s") + } else { + encoder.encodeNil(forKey: "s") + } + } + + public var id: MediaResourceId { + return HttpReferenceMediaResourceId(url: self.url) + } + + public func isEqual(to: TelegramMediaResource) -> Bool { + if let to = to as? HttpReferenceMediaResource { + return to.url == self.url + } else { + return false + } + } +} + func mediaResourceFromApiFileLocation(_ fileLocation: Api.FileLocation, size: Int?) -> TelegramMediaResource? { switch fileLocation { case let .fileLocation(dcId, volumeId, localId, secret): diff --git a/TelegramCore/EnqueueMessage.swift b/TelegramCore/EnqueueMessage.swift index a42c5ccdd5..2d9ce33cba 100644 --- a/TelegramCore/EnqueueMessage.swift +++ b/TelegramCore/EnqueueMessage.swift @@ -8,19 +8,38 @@ import Foundation #endif public enum EnqueueMessage { - case message(text: String, media: Media?, replyToMessageId: MessageId?) + case message(text: String, attributes: [MessageAttribute], media: Media?, replyToMessageId: MessageId?) case forward(source: MessageId) public func withUpdatedReplyToMessageId(_ replyToMessageId: MessageId?) -> EnqueueMessage { switch self { - case let .message(text, media, _): - return .message(text: text, media: media, replyToMessageId: replyToMessageId) + case let .message(text, attributes, media, _): + return .message(text: text, attributes: attributes, media: media, replyToMessageId: replyToMessageId) case .forward: return self } } } +private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAttribute]) -> [MessageAttribute] { + return attributes.filter { attribute in + switch attribute { + case let _ as TextEntitiesMessageAttribute: + return true + case let _ as InlineBotMessageAttribute: + return true + case let _ as OutgoingMessageInfoAttribute: + return true + case let _ as ReplyMarkupMessageAttribute: + return true + case let _ as OutgoingChatContextResultMessageAttribute: + return true + default: + return false + } + } +} + private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAttribute]) -> [MessageAttribute] { return attributes.filter { attribute in switch attribute { @@ -49,7 +68,9 @@ public func enqueueMessages(account: Account, peerId: PeerId, messages: [Enqueue attributes.append(OutgoingMessageInfoAttribute(uniqueId: randomId)) switch message { - case let .message(text, media, replyToMessageId): + case let .message(text, requestedAttributes, media, replyToMessageId): + attributes.append(contentsOf: filterMessageAttributesForOutgoingMessage(requestedAttributes)) + if let replyToMessageId = replyToMessageId { attributes.append(ReplyMessageAttribute(messageId: replyToMessageId)) } diff --git a/TelegramCore/Fetch.swift b/TelegramCore/Fetch.swift index f911efe908..4bf3833e6b 100644 --- a/TelegramCore/Fetch.swift +++ b/TelegramCore/Fetch.swift @@ -79,6 +79,8 @@ func fetchResource(account: Account, resource: MediaResource, range: Range) #endif } else if let localFileResource = resource as? LocalFileReferenceMediaResource { return fetchLocalFileResource(path: localFileResource.localFilePath) + } else if let httpReference = resource as? HttpReferenceMediaResource { + return fetchHttpResource(url: httpReference.url) } return .never() } diff --git a/TelegramCore/FetchHttpResource.swift b/TelegramCore/FetchHttpResource.swift new file mode 100644 index 0000000000..8f62a62246 --- /dev/null +++ b/TelegramCore/FetchHttpResource.swift @@ -0,0 +1,29 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +func fetchHttpResource(url: String) -> Signal { + if let url = URL(string: url) { + let signal = MTHttpRequestOperation.data(forHttpUrl: url)! + return Signal { subscriber in + let disposable = signal.start(next: { next in + let fetchResult = MediaResourceDataFetchResult(data: next as! Data, complete: true) + subscriber.putNext(fetchResult) + subscriber.putCompletion() + }) + + return ActionDisposable { + disposable?.dispose() + } + } + } else { + return .never() + } +} diff --git a/TelegramCore/OutgoingChatContextResultMessageAttribute.swift b/TelegramCore/OutgoingChatContextResultMessageAttribute.swift new file mode 100644 index 0000000000..492de2557d --- /dev/null +++ b/TelegramCore/OutgoingChatContextResultMessageAttribute.swift @@ -0,0 +1,26 @@ +import Foundation +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +public class OutgoingChatContextResultMessageAttribute: MessageAttribute { + public let queryId: Int64 + public let id: String + + init(queryId: Int64, id: String) { + self.queryId = queryId + self.id = id + } + + required public init(decoder: Decoder) { + self.queryId = decoder.decodeInt64ForKey("q") + self.id = decoder.decodeStringForKey("i") + } + + public func encode(_ encoder: Encoder) { + encoder.encodeInt64(self.queryId, forKey: "q") + encoder.encodeString(self.id, forKey: "i") + } +} diff --git a/TelegramCore/OutgoingMessageWithChatContextResult.swift b/TelegramCore/OutgoingMessageWithChatContextResult.swift new file mode 100644 index 0000000000..c33a302de3 --- /dev/null +++ b/TelegramCore/OutgoingMessageWithChatContextResult.swift @@ -0,0 +1,152 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac +#else + import Postbox + import SwiftSignalKit +#endif + +/* +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)) + + switch result.message { + case let .auto(caption, replyMarkup): + switch result { + case let .internalReference(id, type, title, description, image, file, message): + if let image = image { + return .message(text: "", attributes: attributes, media: image, replyToMessageId: nil) + } else if let file = file { + return .message(text: "", attributes: attributes, media: file, replyToMessageId: nil) + } else { + return nil + } + case let .externalReference(id, type, title, description, url, thumbnailUrl, contentUrl, contentType, dimensions, duration, message): + return nil + } + case let .text(text, entities, disableUrlPreview, replyMarkup): + if let entities = entities { + attributes.append(entities) + } + if let replyMarkup = replyMarkup { + attributes.append(replyMarkup) + } + return .message(text: text, attributes: attributes, media: nil, replyToMessageId: nil) + case let .mapLocation(media, replyMarkup): + if let replyMarkup = replyMarkup { + attributes.append(replyMarkup) + } + return .message(text: "", attributes: attributes, media: media, replyToMessageId: nil) + case let .contact(media, replyMarkup): + if let replyMarkup = replyMarkup { + attributes.append(replyMarkup) + } + return .message(text: "", attributes: attributes, media: media, replyToMessageId: nil) + } +} diff --git a/TelegramCore/PeerParticipants.swift b/TelegramCore/PeerParticipants.swift index 04a73c096a..2289455c1f 100644 --- a/TelegramCore/PeerParticipants.swift +++ b/TelegramCore/PeerParticipants.swift @@ -7,90 +7,37 @@ import Foundation import SwiftSignalKit #endif -public func peerParticipants(account: Account, id: PeerId) -> Signal<[Peer], NoError> { - if id.namespace == Namespaces.Peer.CloudGroup || id.namespace == Namespaces.Peer.CloudChannel { - return account.postbox.loadedPeerWithId(id) - |> take(1) - |> mapToSignal { peer -> Signal<[Peer], NoError> in - if let group = peer as? TelegramGroup { - return account.network.request(Api.functions.messages.getFullChat(chatId: group.id.id)) - |> retryRequest - |> map { result -> [Peer] in - switch result { - case let .chatFull(fullChat, chats, users): - var peerIds = Set() - switch fullChat { - case let .chatFull(_, participants, _, _, _, _): - switch participants { - case let .chatParticipants(_, participants, _): - for participant in participants { - let peerId: PeerId - switch participant { - case let .chatParticipant(userId, _, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .chatParticipantAdmin(userId, _, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .chatParticipantCreator(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - } - peerIds.insert(peerId) - } - case .chatParticipantsForbidden: - break - } - case .channelFull: - break - } - let peers: [Peer] = users.filter({ user in - return peerIds.contains(user.peerId) - }).map({ user in - return TelegramUser(user: user) - }) - return peers - } - return [] - } - } else if let channel = peer as? TelegramChannel { - if let inputChannel = apiInputChannel(channel) { - return account.network.request(Api.functions.channels.getParticipants(channel: inputChannel, filter: .channelParticipantsRecent, offset: 0, limit: 32)) - |> retryRequest - |> map { result -> [Peer] in - switch result { - case let .channelParticipants(_, participants, users): - var peerIds = Set() - for participant in participants { - let peerId: PeerId - switch participant { - case let .channelParticipant(userId, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .channelParticipantCreator(userId): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .channelParticipantEditor(userId, _, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .channelParticipantKicked(userId, _, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .channelParticipantModerator(userId, _, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - case let .channelParticipantSelf(userId, _, _): - peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) - } - peerIds.insert(peerId) - } - let peers: [Peer] = users.filter({ user in - return peerIds.contains(user.peerId) - }).map({ user in - return TelegramUser(user: user) - }) - return peers - } - } - } else { - return .single([]) - } - } - return .single([]) +private struct PeerParticipants: Equatable { + let peers: [Peer] + + static func ==(lhs: PeerParticipants, rhs: PeerParticipants) -> Bool { + if lhs.peers.count != rhs.peers.count { + return false } - } else { - return .single([]) + for i in 0 ..< lhs.peers.count { + if !lhs.peers[i].isEqual(rhs.peers[i]) { + return false + } + } + return true + } +} + +public func peerParticipants(account: Account, id: PeerId) -> Signal<[Peer], NoError> { + return account.viewTracker.peerView(id) |> map { view -> PeerParticipants in + if let cachedGroupData = view.cachedData as? CachedGroupData, let participants = cachedGroupData.participants { + var peers: [Peer] = [] + for participant in participants.participants { + if let peer = view.peers[participant.peerId] { + peers.append(peer) + } + } + return PeerParticipants(peers: peers) + } else { + return PeerParticipants(peers: []) + } + } + |> distinctUntilChanged |> map { participants in + return participants.peers } } diff --git a/TelegramCore/PendingMessageManager.swift b/TelegramCore/PendingMessageManager.swift index bd7515f47d..385b5a9cc3 100644 --- a/TelegramCore/PendingMessageManager.swift +++ b/TelegramCore/PendingMessageManager.swift @@ -224,6 +224,7 @@ public final class PendingMessageManager { if let peer = modifier.getPeer(message.id.peerId), let inputPeer = apiInputPeer(peer) { var uniqueId: Int64 = 0 var forwardSourceInfoAttribute: ForwardSourceInfoAttribute? + var outgoingChatContextResultAttribute: OutgoingChatContextResultMessageAttribute? var replyMessageId: Int32? for attribute in message.attributes { @@ -231,8 +232,10 @@ public final class PendingMessageManager { replyMessageId = replyAttribute.messageId.id } else if let outgoingInfo = attribute as? OutgoingMessageInfoAttribute { uniqueId = outgoingInfo.uniqueId - } else if let forwardSourceInfo = attribute as? ForwardSourceInfoAttribute { - forwardSourceInfoAttribute = forwardSourceInfo + } else if let attribute = attribute as? ForwardSourceInfoAttribute { + forwardSourceInfoAttribute = attribute + } else if let attribute = attribute as? OutgoingChatContextResultMessageAttribute { + outgoingChatContextResultAttribute = attribute } } @@ -264,6 +267,11 @@ public final class PendingMessageManager { } else { sendMessageRequest = .fail(NoError()) } + case let .chatContextResult(chatContextResult): + sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id)) + |> mapError { _ -> NoError in + return NoError() + } } return sendMessageRequest diff --git a/TelegramCore/PendingMessageUploadedContent.swift b/TelegramCore/PendingMessageUploadedContent.swift index fef19e0769..31d865f2b5 100644 --- a/TelegramCore/PendingMessageUploadedContent.swift +++ b/TelegramCore/PendingMessageUploadedContent.swift @@ -11,6 +11,7 @@ enum PendingMessageUploadedContent { case text(String) case media(Api.InputMedia) case forward(ForwardSourceInfoAttribute) + case chatContextResult(OutgoingChatContextResultMessageAttribute) } enum PendingMessageUploadedContentResult { @@ -19,6 +20,13 @@ enum PendingMessageUploadedContentResult { } func uploadedMessageContent(network: Network, postbox: Postbox, message: Message) -> Signal { + var outgoingChatContextResultAttribute: OutgoingChatContextResultMessageAttribute? + for attribute in message.attributes { + if let attribute = attribute as? OutgoingChatContextResultMessageAttribute { + outgoingChatContextResultAttribute = attribute + } + } + if let forwardInfo = message.forwardInfo { var forwardSourceInfo: ForwardSourceInfoAttribute? for attribute in message.attributes { @@ -31,6 +39,8 @@ func uploadedMessageContent(network: Network, postbox: Postbox, message: Message } else { return .never() } + } else if let outgoingChatContextResultAttribute = outgoingChatContextResultAttribute { + return .single(.content(message, .chatContextResult(outgoingChatContextResultAttribute))) } else if let media = message.media.first { if let image = media as? TelegramMediaImage, let largestRepresentation = largestImageRepresentation(image.representations) { return uploadedMediaImageContent(network: network, postbox: postbox, image: image, message: message) diff --git a/TelegramCore/ReplyMarkupMessageAttribute.swift b/TelegramCore/ReplyMarkupMessageAttribute.swift index ba5fea7e1a..f7e596c39d 100644 --- a/TelegramCore/ReplyMarkupMessageAttribute.swift +++ b/TelegramCore/ReplyMarkupMessageAttribute.swift @@ -5,30 +5,7 @@ import Foundation import Postbox #endif -/* - - keyboardButton#a2fa4880 text:string = KeyboardButton; - keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; - keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton; - keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; - keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton; - keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton; - keyboardButtonGame#50f41ccf text:string = KeyboardButton; - - keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; - - */ - -/* - - replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup; - replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags.2?true = ReplyMarkup; - replyKeyboardMarkup#3502758c flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector = ReplyMarkup; - replyInlineMarkup#48a30254 rows:Vector = ReplyMarkup; - - */ - -public enum ReplyMarkupButtonAction: Coding { +public enum ReplyMarkupButtonAction: Coding, Equatable { case text case url(String) case callback(MemoryBuffer) @@ -75,14 +52,61 @@ public enum ReplyMarkupButtonAction: Coding { case let .switchInline(samePeer, query): encoder.encodeInt32(5, forKey: "v") encoder.encodeInt32(samePeer ? 1 : 0, forKey: "s") - encoder.encodeString(query, forKey: "1") + encoder.encodeString(query, forKey: "q") case .openWebApp: encoder.encodeInt32(6, forKey: "v") } } + + public static func ==(lhs: ReplyMarkupButtonAction, rhs: ReplyMarkupButtonAction) -> Bool { + switch lhs { + case .text: + if case .text = rhs { + return true + } else { + return false + } + case let .url(url): + if case .url(url) = rhs { + return true + } else { + return false + } + case let .callback(data): + if case .callback(data) = rhs { + return true + } else { + return false + } + case .requestPhone: + if case .requestPhone = rhs { + return true + } else { + return false + } + case .requestMap: + if case .requestMap = rhs { + return true + } else { + return false + } + case let .switchInline(samePeer, query): + if case .switchInline(samePeer, query) = rhs { + return true + } else { + return false + } + case .openWebApp: + if case .openWebApp = rhs { + return true + } else { + return false + } + } + } } -public struct ReplyMarkupButton: Coding { +public struct ReplyMarkupButton: Coding, Equatable { public let title: String public let action: ReplyMarkupButtonAction @@ -100,9 +124,13 @@ public struct ReplyMarkupButton: Coding { encoder.encodeString(self.title, forKey: ".t") self.action.encode(encoder) } + + public static func ==(lhs: ReplyMarkupButton, rhs: ReplyMarkupButton) -> Bool { + return lhs.title == rhs.title && lhs.action == rhs.action + } } -public struct ReplyMarkupRow: Coding { +public struct ReplyMarkupRow: Coding, Equatable { public let buttons: [ReplyMarkupButton] init(buttons: [ReplyMarkupButton]) { @@ -116,6 +144,10 @@ public struct ReplyMarkupRow: Coding { public func encode(_ encoder: Encoder) { encoder.encodeObjectArray(self.buttons, forKey: "b") } + + public static func ==(lhs: ReplyMarkupRow, rhs: ReplyMarkupRow) -> Bool { + return lhs.buttons == rhs.buttons + } } public struct ReplyMarkupMessageFlags: OptionSet { @@ -135,7 +167,7 @@ public struct ReplyMarkupMessageFlags: OptionSet { public static let inline = ReplyMarkupMessageFlags(rawValue: 1 << 3) } -public class ReplyMarkupMessageAttribute: MessageAttribute { +public class ReplyMarkupMessageAttribute: MessageAttribute, Equatable { public let rows: [ReplyMarkupRow] public let flags: ReplyMarkupMessageFlags @@ -153,6 +185,10 @@ public class ReplyMarkupMessageAttribute: MessageAttribute { encoder.encodeObjectArray(self.rows, forKey: "r") encoder.encodeInt32(self.flags.rawValue, forKey: "f") } + + public static func ==(lhs: ReplyMarkupMessageAttribute, rhs: ReplyMarkupMessageAttribute) -> Bool { + return lhs.flags == rhs.flags && lhs.rows == rhs.rows + } } extension ReplyMarkupButton { diff --git a/TelegramCore/RequestChatContextResults.swift b/TelegramCore/RequestChatContextResults.swift new file mode 100644 index 0000000000..49f852a4fe --- /dev/null +++ b/TelegramCore/RequestChatContextResults.swift @@ -0,0 +1,34 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +public func requestChatContextResults(account: Account, botId: PeerId, peerId: PeerId, query: String, offset: String) -> Signal { + return account.postbox.modify { modifier -> (bot: Peer, peer: Peer)? in + if let bot = modifier.getPeer(botId), let peer = modifier.getPeer(peerId) { + return (bot, peer) + } else { + return nil + } + } + |> mapToSignal { botAndPeer -> Signal in + if let (bot, peer) = botAndPeer, let inputBot = apiInputUser(bot), let inputPeer = apiInputPeer(peer) { + var flags: Int32 = 0 + return account.network.request(Api.functions.messages.getInlineBotResults(flags: flags, bot: inputBot, peer: inputPeer, geoPoint: nil, query: query, offset: offset)) + |> map { result -> ChatContextResultCollection? in + return ChatContextResultCollection(apiResults: result, botId: bot.id) + } + |> `catch` { _ -> Signal in + return .single(nil) + } + } else { + return .single(nil) + } + } +} diff --git a/TelegramCore/RequestMessageActionCallback.swift b/TelegramCore/RequestMessageActionCallback.swift index d238b48762..1c04f05c94 100644 --- a/TelegramCore/RequestMessageActionCallback.swift +++ b/TelegramCore/RequestMessageActionCallback.swift @@ -9,7 +9,13 @@ import Foundation import MtProtoKitDynamic #endif -public func requestMessageActionCallback(account: Account, messageId: MessageId, data: MemoryBuffer?) -> Signal { +public enum MessageActionCallbackResult { + case none + case alert(String) + case url(String) +} + +public func requestMessageActionCallback(account: Account, messageId: MessageId, data: MemoryBuffer?) -> Signal { return account.postbox.loadedPeerWithId(messageId.peerId) |> take(1) |> mapToSignal { peer in @@ -22,11 +28,22 @@ public func requestMessageActionCallback(account: Account, messageId: MessageId, } return account.network.request(Api.functions.messages.getBotCallbackAnswer(flags: flags, peer: inputPeer, msgId: messageId.id, data: dataBuffer)) |> retryRequest - |> map { result in - return Void() + |> map { result -> MessageActionCallbackResult in + //messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer; + + switch result { + case let .botCallbackAnswer(_, message, url, cacheTime): + if let message = message { + return .alert(message) + } else if let url = url { + return .url(url) + } else { + return .none + } + } } } else { - return .complete() + return .single(.none) } } } diff --git a/TelegramCore/ResolvePeerByName.swift b/TelegramCore/ResolvePeerByName.swift index efcf408677..4362b19af6 100644 --- a/TelegramCore/ResolvePeerByName.swift +++ b/TelegramCore/ResolvePeerByName.swift @@ -46,7 +46,12 @@ final class CachedResolvedByNamePeer: Coding { private let resolvedByNamePeersCollectionSpec = ItemCacheCollectionSpec(lowWaterItemCount: 150, highWaterItemCount: 200) -public func resolvePeerByName(account: Account, name: String, ageLimit: Int32 = 60 * 5) -> Signal { +public enum ResolvePeerByNameCachedPolicy { + case remoteIfEarlierThan(timestamp: Int32) + case remote +} + +public func resolvePeerByName(account: Account, name: String, ageLimit: Int32 = 60 * 60) -> Signal { var normalizedName = name if normalizedName.hasPrefix("@") { normalizedName = normalizedName.substring(from: name.index(after: name.startIndex)) diff --git a/TelegramCore/StateManagement.swift b/TelegramCore/StateManagement.swift index d42555a7fb..2aae4245bf 100644 --- a/TelegramCore/StateManagement.swift +++ b/TelegramCore/StateManagement.swift @@ -880,6 +880,16 @@ private func finalStateWithUpdates(account: Account, state: MutableState, update if let date = date { let peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000) + if updatedState.peers[peerId] == nil { + updatedState.updatePeer(peerId, { peer in + if peer == nil { + return TelegramUser(id: peerId, accessHash: nil, firstName: "Telegram Notifications", lastName: nil, username: nil, phone: nil, photo: [], botInfo: BotUserInfo(flags: [], inlinePlaceholder: nil)) + } else { + return peer + } + }) + } + var alreadyStored = false if let storedMessages = updatedState.storedMessagesByPeerIdAndTimestamp[peerId] { for index in storedMessages { diff --git a/TelegramCore/StoreMessage_Telegram.swift b/TelegramCore/StoreMessage_Telegram.swift index afe7ec230c..135e0c3bc8 100644 --- a/TelegramCore/StoreMessage_Telegram.swift +++ b/TelegramCore/StoreMessage_Telegram.swift @@ -203,13 +203,11 @@ func textAndMediaFromApiMedia(_ media: Api.MessageMedia?) -> (String?, Media?) { let mediaContact = TelegramMediaContact(firstName: firstName, lastName: lastName, phoneNumber: phoneNumber, peerId: contactPeerId) return (nil, mediaContact) case let .messageMediaGeo(geo): - if let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil) { - return (nil, mediaMap) - } + let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: nil, address: nil, provider: nil, venueId: nil) + return (nil, mediaMap) case let .messageMediaVenue(geo, title, address, provider, venueId): - if let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId) { - return (nil, mediaMap) - } + let mediaMap = telegramMediaMapFromApiGeoPoint(geo, title: title, address: address, provider: provider, venueId: venueId) + return (nil, mediaMap) case let .messageMediaDocument(document, caption): if let mediaFile = telegramMediaFileFromApiDocument(document) { return (caption, mediaFile) @@ -263,8 +261,6 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes return result } -//message#c09be45f flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int edit_date:flags.15?int = Message; - extension StoreMessage { convenience init?(apiMessage: Api.Message) { switch apiMessage { diff --git a/TelegramCore/TelegramMediaFile.swift b/TelegramCore/TelegramMediaFile.swift index 1235df677e..e498b4a2bb 100644 --- a/TelegramCore/TelegramMediaFile.swift +++ b/TelegramCore/TelegramMediaFile.swift @@ -92,7 +92,7 @@ public final class TelegramMediaFile: Media, Equatable { public let resource: TelegramMediaResource public let previewRepresentations: [TelegramMediaImageRepresentation] public let mimeType: String - public let size: Int + public let size: Int? public let attributes: [TelegramMediaFileAttribute] public let peerIds: [PeerId] = [] @@ -100,7 +100,7 @@ public final class TelegramMediaFile: Media, Equatable { return self.fileId } - public init(fileId: MediaId, resource: TelegramMediaResource, previewRepresentations: [TelegramMediaImageRepresentation], mimeType: String, size: Int, attributes: [TelegramMediaFileAttribute]) { + public init(fileId: MediaId, resource: TelegramMediaResource, previewRepresentations: [TelegramMediaImageRepresentation], mimeType: String, size: Int?, attributes: [TelegramMediaFileAttribute]) { self.fileId = fileId self.resource = resource self.previewRepresentations = previewRepresentations @@ -114,7 +114,11 @@ public final class TelegramMediaFile: Media, Equatable { self.resource = decoder.decodeObjectForKey("r") as! TelegramMediaResource self.previewRepresentations = decoder.decodeObjectArrayForKey("pr") self.mimeType = decoder.decodeStringForKey("mt") - self.size = Int(decoder.decodeInt32ForKey("s")) + if let size = (decoder.decodeInt32ForKey("s") as Int32?) { + self.size = Int(size) + } else { + self.size = nil + } self.attributes = decoder.decodeObjectArrayForKey("at") } @@ -125,7 +129,11 @@ public final class TelegramMediaFile: Media, Equatable { encoder.encodeObject(self.resource, forKey: "r") encoder.encodeObjectArray(self.previewRepresentations, forKey: "pr") encoder.encodeString(self.mimeType, forKey: "mt") - encoder.encodeInt32(Int32(self.size), forKey: "s") + if let size = self.size { + encoder.encodeInt32(Int32(size), forKey: "s") + } else { + encoder.encodeNil(forKey: "s") + } encoder.encodeObjectArray(self.attributes, forKey: "at") } diff --git a/TelegramCore/TelegramMediaMap.swift b/TelegramCore/TelegramMediaMap.swift index 08b0b7aec6..1ad009954c 100644 --- a/TelegramCore/TelegramMediaMap.swift +++ b/TelegramCore/TelegramMediaMap.swift @@ -130,7 +130,7 @@ public final class TelegramMediaMap: Media { } } -public func telegramMediaMapFromApiGeoPoint(_ geo: Api.GeoPoint, title: String?, address: String?, provider: String?, venueId: String?) -> TelegramMediaMap? { +public func telegramMediaMapFromApiGeoPoint(_ geo: Api.GeoPoint, title: String?, address: String?, provider: String?, venueId: String?) -> TelegramMediaMap { var venue: MapVenue? if let title = title { venue = MapVenue(title: title, address: address, provider: provider, id: venueId) @@ -146,6 +146,6 @@ public func telegramMediaMapFromApiGeoPoint(_ geo: Api.GeoPoint, title: String?, } return TelegramMediaMap(latitude: lat, longitude: long, geoPlace: geoPlace, venue: venue) case .geoPointEmpty: - return nil + return TelegramMediaMap(latitude: 0.0, longitude: 0.0, geoPlace: nil, venue: venue) } } diff --git a/TelegramCore/TelegramUser.swift b/TelegramCore/TelegramUser.swift index caa8e05b7b..7ffef52ca0 100644 --- a/TelegramCore/TelegramUser.swift +++ b/TelegramCore/TelegramUser.swift @@ -5,6 +5,50 @@ import Foundation import Postbox #endif +public struct BotUserInfoFlags: OptionSet { + public var rawValue: Int32 + + public init() { + self.rawValue = 0 + } + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public static let hasAccessToChatHistory = BotUserInfoFlags(rawValue: (1 << 0)) + public static let worksWithGroups = BotUserInfoFlags(rawValue: (1 << 1)) + public static let requiresGeolocationForInlineRequests = BotUserInfoFlags(rawValue: (1 << 2)) +} + +public struct BotUserInfo: Coding, Equatable { + public let flags: BotUserInfoFlags + public let inlinePlaceholder: String? + + init(flags: BotUserInfoFlags, inlinePlaceholder: String?) { + self.flags = flags + self.inlinePlaceholder = inlinePlaceholder + } + + public init(decoder: Decoder) { + self.flags = BotUserInfoFlags(rawValue: decoder.decodeInt32ForKey("f")) + self.inlinePlaceholder = decoder.decodeStringForKey("ip") + } + + public func encode(_ encoder: Encoder) { + encoder.encodeInt32(self.flags.rawValue, forKey: "f") + if let inlinePlaceholder = self.inlinePlaceholder { + encoder.encodeString(inlinePlaceholder, forKey: "ip") + } else { + encoder.encodeNil(forKey: "ip") + } + } + + public static func ==(lhs: BotUserInfo, rhs: BotUserInfo) -> Bool { + return lhs.flags == rhs.flags && lhs.inlinePlaceholder == rhs.inlinePlaceholder + } +} + public final class TelegramUser: Peer { public let id: PeerId public let accessHash: Int64? @@ -13,6 +57,7 @@ public final class TelegramUser: Peer { public let username: String? public let phone: String? public let photo: [TelegramMediaImageRepresentation] + public let botInfo: BotUserInfo? public var name: String { if let firstName = self.firstName { @@ -32,7 +77,7 @@ public final class TelegramUser: Peer { return .personName(first: self.firstName ?? "", last: self.lastName ?? "") } - public init(id: PeerId, accessHash: Int64?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: [TelegramMediaImageRepresentation]) { + public init(id: PeerId, accessHash: Int64?, firstName: String?, lastName: String?, username: String?, phone: String?, photo: [TelegramMediaImageRepresentation], botInfo: BotUserInfo?) { self.id = id self.accessHash = accessHash self.firstName = firstName @@ -40,6 +85,7 @@ public final class TelegramUser: Peer { self.username = username self.phone = phone self.photo = photo + self.botInfo = botInfo } public init(decoder: Decoder) { @@ -59,6 +105,12 @@ public final class TelegramUser: Peer { self.phone = decoder.decodeStringForKey("p") self.photo = decoder.decodeObjectArrayForKey("ph") + + if let botInfo = decoder.decodeObjectForKey("bi", decoder: { return BotUserInfo(decoder: $0) }) as? BotUserInfo { + self.botInfo = botInfo + } else { + self.botInfo = nil + } } public func encode(_ encoder: Encoder) { @@ -83,6 +135,12 @@ public final class TelegramUser: Peer { } encoder.encodeObjectArray(self.photo, forKey: "ph") + + if let botInfo = self.botInfo { + encoder.encodeObject(botInfo, forKey: "bi") + } else { + encoder.encodeNil(forKey: "bi") + } } public func isEqual(_ other: Peer) -> Bool { @@ -110,6 +168,9 @@ public final class TelegramUser: Peer { return false } } + if self.botInfo != other.botInfo { + return false + } return true } else { @@ -118,10 +179,13 @@ public final class TelegramUser: Peer { } } +//user#d10d979a flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?string bot_inline_placeholder:flags.19?string = User; + + public extension TelegramUser { public convenience init(user: Api.User) { switch user { - case let .user(_, id, accessHash, firstName, lastName, username, phone, photo, _, _, _, _): + case let .user(flags, id, accessHash, firstName, lastName, username, phone, photo, _, _, _, botInlinePlaceholder): var telegramPhoto: [TelegramMediaImageRepresentation] = [] if let photo = photo { switch photo { @@ -134,15 +198,29 @@ public extension TelegramUser { break } } - self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: id), accessHash: accessHash, firstName: firstName, lastName: lastName, username: username, phone: phone, photo: telegramPhoto) + var botInfo: BotUserInfo? + if (flags & (1 << 14)) != 0 { + var botFlags = BotUserInfoFlags() + if (flags & (1 << 15)) != 0 { + botFlags.insert(.hasAccessToChatHistory) + } + if (flags & (1 << 16)) == 0 { + botFlags.insert(.worksWithGroups) + } + if (flags & (1 << 21)) == 0 { + botFlags.insert(.requiresGeolocationForInlineRequests) + } + botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder) + } + self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: id), accessHash: accessHash, firstName: firstName, lastName: lastName, username: username, phone: phone, photo: telegramPhoto, botInfo: botInfo) case let .userEmpty(id): - self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: id), accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: []) + self.init(id: PeerId(namespace: Namespaces.Peer.CloudUser, id: id), accessHash: nil, firstName: nil, lastName: nil, username: nil, phone: nil, photo: [], botInfo: nil) } } public static func merge(_ lhs: TelegramUser?, rhs: Api.User) -> TelegramUser? { switch rhs { - case let .user(_, _, accessHash, _, _, username, _, photo, _, _, _, _): + case let .user(flags, _, accessHash, _, _, username, _, photo, _, _, _, botInlinePlaceholder): if let _ = accessHash { return TelegramUser(user: rhs) } else { @@ -159,7 +237,22 @@ public extension TelegramUser { } } if let lhs = lhs { - return TelegramUser(id: lhs.id, accessHash: lhs.accessHash, firstName: lhs.firstName, lastName: lhs.lastName, username: username, phone: lhs.phone, photo: telegramPhoto) + var botInfo: BotUserInfo? + if (flags & (1 << 14)) != 0 { + var botFlags = BotUserInfoFlags() + if (flags & (1 << 15)) != 0 { + botFlags.insert(.hasAccessToChatHistory) + } + if (flags & (1 << 16)) == 0 { + botFlags.insert(.worksWithGroups) + } + if (flags & (1 << 21)) == 0 { + botFlags.insert(.requiresGeolocationForInlineRequests) + } + botInfo = BotUserInfo(flags: botFlags, inlinePlaceholder: botInlinePlaceholder) + } + + return TelegramUser(id: lhs.id, accessHash: lhs.accessHash, firstName: lhs.firstName, lastName: lhs.lastName, username: username, phone: lhs.phone, photo: telegramPhoto, botInfo: botInfo) } else { return TelegramUser(user: rhs) } diff --git a/TelegramCore/TextEntitiesMessageAttribute.swift b/TelegramCore/TextEntitiesMessageAttribute.swift index cdbbb1bbd0..d9414c9b20 100644 --- a/TelegramCore/TextEntitiesMessageAttribute.swift +++ b/TelegramCore/TextEntitiesMessageAttribute.swift @@ -5,7 +5,7 @@ import Foundation import Postbox #endif -public enum MessageTextEntityType { +public enum MessageTextEntityType: Equatable { case Unknown case Mention case Hashtag @@ -18,9 +18,86 @@ public enum MessageTextEntityType { case Pre case TextUrl(url: String) case TextMention(peerId: PeerId) + + public static func ==(lhs: MessageTextEntityType, rhs: MessageTextEntityType) -> Bool { + switch lhs { + case .Unknown: + if case .Unknown = rhs { + return true + } else { + return false + } + case .Mention: + if case .Mention = rhs { + return true + } else { + return false + } + case .Hashtag: + if case .Hashtag = rhs { + return true + } else { + return false + } + case .BotCommand: + if case .BotCommand = rhs { + return true + } else { + return false + } + case .Url: + if case .Url = rhs { + return true + } else { + return false + } + case .Email: + if case .Email = rhs { + return true + } else { + return false + } + case .Bold: + if case .Bold = rhs { + return true + } else { + return false + } + case .Italic: + if case .Italic = rhs { + return true + } else { + return false + } + case .Code: + if case .Code = rhs { + return true + } else { + return false + } + case .Pre: + if case .Pre = rhs { + return true + } else { + return false + } + case let .TextUrl(url): + if case let .TextUrl(url) = rhs { + return true + } else { + return false + } + case let .TextMention(peerId): + if case .TextMention(peerId) = rhs { + return true + } else { + return false + } + } + } } -public struct MessageTextEntity: Coding { +public struct MessageTextEntity: Coding, Equatable { public let range: Range public let type: MessageTextEntityType @@ -92,9 +169,13 @@ public struct MessageTextEntity: Coding { encoder.encodeInt64(peerId.toInt64(), forKey: "peerId") } } + + public static func ==(lhs: MessageTextEntity, rhs: MessageTextEntity) -> Bool { + return lhs.range == rhs.range && lhs.type == rhs.type + } } -public class TextEntitiesMessageAttribute: MessageAttribute { +public class TextEntitiesMessageAttribute: MessageAttribute, Equatable { public let entities: [MessageTextEntity] public var associatedPeerIds: [PeerId] { @@ -121,4 +202,8 @@ public class TextEntitiesMessageAttribute: MessageAttribute { public func encode(_ encoder: Encoder) { encoder.encodeObjectArray(self.entities, forKey: "entities") } + + public static func ==(lhs: TextEntitiesMessageAttribute, rhs: TextEntitiesMessageAttribute) -> Bool { + return lhs.entities == rhs.entities + } } diff --git a/TelegramCore/UpdateCachedPeerData.swift b/TelegramCore/UpdateCachedPeerData.swift index c72f45dc58..3ff0fddc70 100644 --- a/TelegramCore/UpdateCachedPeerData.swift +++ b/TelegramCore/UpdateCachedPeerData.swift @@ -17,7 +17,11 @@ func fetchAndUpdateCachedPeerData(peerId: PeerId, network: Network, postbox: Pos return postbox.modify { modifier -> Void in switch result { - case let .userFull(_, _, _, _, _, notifySettings, _, commonChatCount): + case let .userFull(_, user, _, _, _, notifySettings, _, commonChatCount): + let telegramUser = TelegramUser(user: user) + modifier.updatePeers([telegramUser], update: { _, updated -> Peer in + return updated + }) modifier.updatePeerNotificationSettings([peerId: TelegramPeerNotificationSettings(apiSettings: notifySettings)]) } modifier.updatePeerCachedData(peerIds: [peerId], update: { peerId, _ in diff --git a/TelegramCore/UpdatesApiUtils.swift b/TelegramCore/UpdatesApiUtils.swift index 602a69d4a0..3511ce10c3 100644 --- a/TelegramCore/UpdatesApiUtils.swift +++ b/TelegramCore/UpdatesApiUtils.swift @@ -232,6 +232,12 @@ extension Api.Update { return [PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)] case let .updateUserPhoto(userId, _, _, _): return [PeerId(namespace: Namespaces.Peer.CloudUser, id: userId)] + case let .updateServiceNotification(_, inboxDate, _, _, _, _): + if let _ = inboxDate { + return [PeerId(namespace: Namespaces.Peer.CloudUser, id: 777000)] + } else { + return [] + } default: return [] }