diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 2d2f32e51d..8dc6ee9f96 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -458,11 +458,16 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI } let kind: TelegramMediaPollKind if (flags & (1 << 3)) != 0 { - kind = .quiz + kind = .quiz(multipleAnswers: (flags & (1 << 2)) != 0) } else { kind = .poll(multipleAnswers: (flags & (1 << 2)) != 0) } - + + let openAnswers = (flags & (1 << 6)) != 0 + let revotingDisabled = (flags & (1 << 7)) != 0 + let shuffleAnswers = (flags & (1 << 8)) != 0 + let hideResultsUntilClose = (flags & (1 << 9)) != 0 + let questionText: String let questionEntities: [MessageTextEntity] switch question { @@ -471,8 +476,12 @@ func textMediaAndExpirationTimerFromApiMedia(_ media: Api.MessageMedia?, _ peerI questionText = text questionEntities = messageTextEntitiesFromApiEntities(entities) } - - return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod), nil, nil, nil, nil, nil) + + var parsedAttachedMedia: Media? + if let apiAttachedMedia = messageMediaPollData.attachedMedia { + parsedAttachedMedia = textMediaAndExpirationTimerFromApiMedia(apiAttachedMedia, peerId).media + } + return (TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, attachedMedia: parsedAttachedMedia), nil, nil, nil, nil, nil) } case let .messageMediaToDo(messageMediaToDoData): let (todo, completions) = (messageMediaToDoData.todo, messageMediaToDoData.completions) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift index e18430feab..836ac71f74 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaPoll.swift @@ -7,7 +7,7 @@ extension TelegramMediaPollOption { init(apiOption: Api.PollAnswer) { switch apiOption { case let .pollAnswer(pollAnswerData): - let (text, option) = (pollAnswerData.text, pollAnswerData.option) + let (flags, text, option) = (pollAnswerData.flags, pollAnswerData.text, pollAnswerData.option) let answerText: String let answerEntities: [MessageTextEntity] switch text { @@ -16,8 +16,11 @@ extension TelegramMediaPollOption { answerText = text answerEntities = messageTextEntitiesFromApiEntities(entities) } - - self.init(text: answerText, entities: answerEntities, opaqueIdentifier: option.makeData()) + var parsedMedia: Media? + if (flags & (1 << 0)) != 0, let apiMedia = pollAnswerData.media { + parsedMedia = textMediaAndExpirationTimerFromApiMedia(apiMedia, PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(0))).media + } + self.init(text: answerText, entities: answerEntities, opaqueIdentifier: option.makeData(), media: parsedMedia) case let .inputPollAnswer(inputPollAnswerData): let text = inputPollAnswerData.text let answerText: String @@ -31,7 +34,7 @@ extension TelegramMediaPollOption { self.init(text: answerText, entities: answerEntities, opaqueIdentifier: Data()) } } - + var apiOption: Api.PollAnswer { return .pollAnswer(.init(flags: 0, text: .textWithEntities(.init(text: self.text, entities: apiEntitiesFromMessageTextEntities(self.entities, associatedPeers: SimpleDictionary()))), option: Buffer(data: self.opaqueIdentifier), media: nil)) } @@ -42,7 +45,10 @@ extension TelegramMediaPollOptionVoters { switch apiVoters { case let .pollAnswerVoters(pollAnswerVotersData): let (flags, option, voters) = (pollAnswerVotersData.flags, pollAnswerVotersData.option, pollAnswerVotersData.voters) - self.init(selected: (flags & (1 << 0)) != 0, opaqueIdentifier: option.makeData(), count: voters, isCorrect: (flags & (1 << 1)) != 0) + let parsedRecentVoters: [PeerId] = pollAnswerVotersData.recentVoters.flatMap { peers in + return peers.map { $0.peerId } + } ?? [] + self.init(selected: (flags & (1 << 0)) != 0, opaqueIdentifier: option.makeData(), count: voters, isCorrect: (flags & (1 << 1)) != 0, recentVoters: parsedRecentVoters) } } } @@ -54,9 +60,13 @@ extension TelegramMediaPollResults { let (results, totalVoters, recentVoters, solution, solutionEntities) = (pollResultsData.results, pollResultsData.totalVoters, pollResultsData.recentVoters, pollResultsData.solution, pollResultsData.solutionEntities) var parsedSolution: TelegramMediaPollResults.Solution? if let solution = solution, let solutionEntities = solutionEntities, !solution.isEmpty { - parsedSolution = TelegramMediaPollResults.Solution(text: solution, entities: messageTextEntitiesFromApiEntities(solutionEntities)) + var solutionMedia: Media? + if let apiSolutionMedia = pollResultsData.solutionMedia { + solutionMedia = textMediaAndExpirationTimerFromApiMedia(apiSolutionMedia, PeerId(namespace: Namespaces.Peer.Empty, id: PeerId.Id._internalFromInt64Value(0))).media + } + parsedSolution = TelegramMediaPollResults.Solution(text: solution, entities: messageTextEntitiesFromApiEntities(solutionEntities), media: solutionMedia) } - + self.init(voters: results.flatMap({ $0.map(TelegramMediaPollOptionVoters.init(apiVoters:)) }), totalVoters: totalVoters, recentVoters: recentVoters.flatMap { recentVoters in return recentVoters.map { $0.peerId } } ?? [], solution: parsedSolution) diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 84e4094eb9..6a3d7f2e8f 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -311,8 +311,11 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post if multipleAnswers { pollFlags |= 1 << 2 } - case .quiz: + case let .quiz(multipleAnswers): pollFlags |= 1 << 3 + if multipleAnswers { + pollFlags |= 1 << 2 + } } switch poll.publicity { case .anonymous: @@ -329,7 +332,11 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post if poll.deadlineTimeout != nil { pollFlags |= 1 << 4 } - + if poll.openAnswers { pollFlags |= 1 << 6 } + if poll.revotingDisabled { pollFlags |= 1 << 7 } + if poll.shuffleAnswers { pollFlags |= 1 << 8 } + if poll.hideResultsUntilClose { pollFlags |= 1 << 9 } + var mappedSolution: String? var mappedSolutionEntities: [Api.MessageEntity]? if let solution = poll.results.solution { @@ -337,8 +344,49 @@ func mediaContentToUpload(accountPeerId: PeerId, network: Network, postbox: Post mappedSolutionEntities = apiTextAttributeEntities(TextEntitiesMessageAttribute(entities: solution.entities), associatedPeers: SimpleDictionary()) pollMediaFlags |= 1 << 1 } - let inputPoll = Api.InputMedia.inputMediaPoll(.init(flags: pollMediaFlags, poll: Api.Poll.poll(.init(id: 0, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil)), correctAnswers: correctAnswers, attachedMedia: nil, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: nil)) - return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil, cacheReferenceKey: nil))) + + func cloudMediaToInputMedia(_ media: Media) -> Api.InputMedia? { + if let image = media as? TelegramMediaImage, + let reference = image.reference, + case let .cloud(id, accessHash, maybeFileReference) = reference { + let fileReference = maybeFileReference ?? Data() + return .inputMediaPhoto(.init(flags: 0, id: .inputPhoto(.init(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference))), ttlSeconds: nil, video: nil)) + } else if let file = media as? TelegramMediaFile, + let resource = file.resource as? CloudDocumentMediaResource { + return .inputMediaDocument(.init(flags: 0, id: .inputDocument(.init(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data()))), videoCover: nil, videoTimestamp: nil, ttlSeconds: nil, query: nil)) + } + return nil + } + + var apiAnswers: [Api.PollAnswer] = [] + for (_, option) in poll.options.enumerated() { + let textWithEntities = Api.TextWithEntities.textWithEntities(.init(text: option.text, entities: apiEntitiesFromMessageTextEntities(option.entities, associatedPeers: SimpleDictionary()))) + if let media = option.media, let inputMedia = cloudMediaToInputMedia(media) { + apiAnswers.append(.inputPollAnswer(.init(flags: 1 << 0, text: textWithEntities, option: Buffer(data: option.opaqueIdentifier), media: inputMedia))) + } else { + apiAnswers.append(.pollAnswer(.init(flags: 0, text: textWithEntities, option: Buffer(data: option.opaqueIdentifier), media: nil))) + } + } + + if let attached = poll.attachedMedia, let im = cloudMediaToInputMedia(attached) { + pollMediaFlags |= 1 << 3 + if let solMedia = poll.results.solution?.media, let sm = cloudMediaToInputMedia(solMedia) { + pollMediaFlags |= 1 << 2 + let inputPoll = Api.InputMedia.inputMediaPoll(.init(flags: pollMediaFlags, poll: Api.Poll.poll(.init(id: 0, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: apiAnswers, closePeriod: poll.deadlineTimeout, closeDate: nil)), correctAnswers: correctAnswers, attachedMedia: im, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: sm)) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil, cacheReferenceKey: nil))) + } else { + let inputPoll = Api.InputMedia.inputMediaPoll(.init(flags: pollMediaFlags, poll: Api.Poll.poll(.init(id: 0, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: apiAnswers, closePeriod: poll.deadlineTimeout, closeDate: nil)), correctAnswers: correctAnswers, attachedMedia: im, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: nil)) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil, cacheReferenceKey: nil))) + } + } else { + var apiSolutionMedia: Api.InputMedia? + if let solMedia = poll.results.solution?.media, let sm = cloudMediaToInputMedia(solMedia) { + apiSolutionMedia = sm + pollMediaFlags |= 1 << 2 + } + let inputPoll = Api.InputMedia.inputMediaPoll(.init(flags: pollMediaFlags, poll: Api.Poll.poll(.init(id: 0, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: apiAnswers, closePeriod: poll.deadlineTimeout, closeDate: nil)), correctAnswers: correctAnswers, attachedMedia: nil, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: apiSolutionMedia)) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil, cacheReferenceKey: nil))) + } } else if let todo = media as? TelegramMediaTodo { var flags: Int32 = 0 if todo.flags.contains(.othersCanAppend) { diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index bb12e5a493..34d903709e 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -4502,11 +4502,15 @@ func replayFinalState( } let kind: TelegramMediaPollKind if (flags & (1 << 3)) != 0 { - kind = .quiz + kind = .quiz(multipleAnswers: (flags & (1 << 2)) != 0) } else { kind = .poll(multipleAnswers: (flags & (1 << 2)) != 0) } - + let openAnswers = (flags & (1 << 6)) != 0 + let revotingDisabled = (flags & (1 << 7)) != 0 + let shuffleAnswers = (flags & (1 << 8)) != 0 + let hideResultsUntilClose = (flags & (1 << 9)) != 0 + let questionText: String let questionEntities: [MessageTextEntity] switch question { @@ -4515,8 +4519,8 @@ func replayFinalState( questionText = text questionEntities = messageTextEntitiesFromApiEntities(entities) } - - updatedPoll = TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: poll.results, isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod) + + updatedPoll = TelegramMediaPoll(pollId: MediaId(namespace: Namespaces.Media.CloudPoll, id: id), publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: poll.results, isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, attachedMedia: poll.attachedMedia) } } updatedPoll = updatedPoll.withUpdatedResults(TelegramMediaPollResults(apiResults: results), min: resultsMin) diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift index 0ae834a4d8..57e82f615b 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaPoll.swift @@ -5,23 +5,43 @@ public struct TelegramMediaPollOption: Equatable, PostboxCoding { public let text: String public let entities: [MessageTextEntity] public let opaqueIdentifier: Data - - public init(text: String, entities: [MessageTextEntity], opaqueIdentifier: Data) { + public let media: Media? + + public init(text: String, entities: [MessageTextEntity], opaqueIdentifier: Data, media: Media? = nil) { self.text = text self.entities = entities self.opaqueIdentifier = opaqueIdentifier + self.media = media } - + public init(decoder: PostboxDecoder) { self.text = decoder.decodeStringForKey("t", orElse: "") self.entities = decoder.decodeObjectArrayWithDecoderForKey("et") self.opaqueIdentifier = decoder.decodeDataForKey("i") ?? Data() + self.media = decoder.decodeObjectForKey("md") as? Media } - + public func encode(_ encoder: PostboxEncoder) { encoder.encodeString(self.text, forKey: "t") encoder.encodeObjectArray(self.entities, forKey: "et") encoder.encodeData(self.opaqueIdentifier, forKey: "i") + if let media = self.media { + encoder.encodeObject(media, forKey: "md") + } else { + encoder.encodeNil(forKey: "md") + } + } + + public static func ==(lhs: TelegramMediaPollOption, rhs: TelegramMediaPollOption) -> Bool { + if lhs.text != rhs.text { return false } + if lhs.entities != rhs.entities { return false } + if lhs.opaqueIdentifier != rhs.opaqueIdentifier { return false } + if let lhsMedia = lhs.media, let rhsMedia = rhs.media { + if !lhsMedia.isEqual(to: rhsMedia) { return false } + } else if (lhs.media == nil) != (rhs.media == nil) { + return false + } + return true } } @@ -30,21 +50,24 @@ public struct TelegramMediaPollOptionVoters: Equatable, PostboxCoding { public let opaqueIdentifier: Data public let count: Int32? public let isCorrect: Bool - - public init(selected: Bool, opaqueIdentifier: Data, count: Int32?, isCorrect: Bool) { + public let recentVoters: [PeerId] + + public init(selected: Bool, opaqueIdentifier: Data, count: Int32?, isCorrect: Bool, recentVoters: [PeerId] = []) { self.selected = selected self.opaqueIdentifier = opaqueIdentifier self.count = count self.isCorrect = isCorrect + self.recentVoters = recentVoters } - + public init(decoder: PostboxDecoder) { self.selected = decoder.decodeInt32ForKey("s", orElse: 0) != 0 self.opaqueIdentifier = decoder.decodeDataForKey("i") ?? Data() self.count = decoder.decodeOptionalInt32ForKey("c") self.isCorrect = decoder.decodeInt32ForKey("cr", orElse: 0) != 0 + self.recentVoters = decoder.decodeInt64ArrayForKey("orv").map(PeerId.init) } - + public func encode(_ encoder: PostboxEncoder) { encoder.encodeInt32(self.selected ? 1 : 0, forKey: "s") encoder.encodeData(self.opaqueIdentifier, forKey: "i") @@ -54,6 +77,7 @@ public struct TelegramMediaPollOptionVoters: Equatable, PostboxCoding { encoder.encodeNil(forKey: "c") } encoder.encodeInt32(self.isCorrect ? 1 : 0, forKey: "cr") + encoder.encodeInt64Array(self.recentVoters.map { $0.toInt64() }, forKey: "orv") } } @@ -61,37 +85,51 @@ public struct TelegramMediaPollResults: Equatable, PostboxCoding { public struct Solution: Equatable { public let text: String public let entities: [MessageTextEntity] - - public init(text: String, entities: [MessageTextEntity]) { + public let media: Media? + + public init(text: String, entities: [MessageTextEntity], media: Media? = nil) { self.text = text self.entities = entities + self.media = media + } + + public static func ==(lhs: Solution, rhs: Solution) -> Bool { + if lhs.text != rhs.text { return false } + if lhs.entities != rhs.entities { return false } + if let lhsMedia = lhs.media, let rhsMedia = rhs.media { + if !lhsMedia.isEqual(to: rhsMedia) { return false } + } else if (lhs.media == nil) != (rhs.media == nil) { + return false + } + return true } } - + public let voters: [TelegramMediaPollOptionVoters]? public let totalVoters: Int32? public let recentVoters: [PeerId] public let solution: TelegramMediaPollResults.Solution? - + public init(voters: [TelegramMediaPollOptionVoters]?, totalVoters: Int32?, recentVoters: [PeerId], solution: TelegramMediaPollResults.Solution?) { self.voters = voters self.totalVoters = totalVoters self.recentVoters = recentVoters self.solution = solution } - + public init(decoder: PostboxDecoder) { self.voters = decoder.decodeOptionalObjectArrayWithDecoderForKey("v") self.totalVoters = decoder.decodeOptionalInt32ForKey("t") self.recentVoters = decoder.decodeInt64ArrayForKey("rv").map(PeerId.init) if let text = decoder.decodeOptionalStringForKey("sol") { let entities: [MessageTextEntity] = decoder.decodeObjectArrayWithDecoderForKey("solent") - self.solution = TelegramMediaPollResults.Solution(text: text, entities: entities) + let media = decoder.decodeObjectForKey("solmd") as? Media + self.solution = TelegramMediaPollResults.Solution(text: text, entities: entities, media: media) } else { self.solution = nil } } - + public func encode(_ encoder: PostboxEncoder) { if let voters = self.voters { encoder.encodeObjectArray(voters, forKey: "v") @@ -107,6 +145,11 @@ public struct TelegramMediaPollResults: Equatable, PostboxCoding { if let solution = self.solution { encoder.encodeString(solution.text, forKey: "sol") encoder.encodeObjectArray(solution.entities, forKey: "solent") + if let media = solution.media { + encoder.encodeObject(media, forKey: "solmd") + } else { + encoder.encodeNil(forKey: "solmd") + } } else { encoder.encodeNil(forKey: "sol") } @@ -120,27 +163,28 @@ public enum TelegramMediaPollPublicity: Int32 { public enum TelegramMediaPollKind: Equatable, PostboxCoding { case poll(multipleAnswers: Bool) - case quiz - + case quiz(multipleAnswers: Bool) + public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("_v", orElse: 0) { case 0: self = .poll(multipleAnswers: decoder.decodeInt32ForKey("m", orElse: 0) != 0) case 1: - self = .quiz + self = .quiz(multipleAnswers: decoder.decodeInt32ForKey("m", orElse: 0) != 0) default: assertionFailure() self = .poll(multipleAnswers: false) } } - + public func encode(_ encoder: PostboxEncoder) { switch self { case let .poll(multipleAnswers): encoder.encodeInt32(0, forKey: "_v") encoder.encodeInt32(multipleAnswers ? 1 : 0, forKey: "m") - case .quiz: + case let .quiz(multipleAnswers): encoder.encodeInt32(1, forKey: "_v") + encoder.encodeInt32(multipleAnswers ? 1 : 0, forKey: "m") } } } @@ -151,12 +195,18 @@ public final class TelegramMediaPoll: Media, Equatable { } public let pollId: MediaId public var peerIds: [PeerId] { - return results.recentVoters + var peerIds = results.recentVoters + if let voters = results.voters { + for voter in voters { + peerIds.append(contentsOf: voter.recentVoters) + } + } + return peerIds } - + public let publicity: TelegramMediaPollPublicity public let kind: TelegramMediaPollKind - + public let text: String public let textEntities: [MessageTextEntity] public let options: [TelegramMediaPollOption] @@ -164,8 +214,14 @@ public final class TelegramMediaPoll: Media, Equatable { public let results: TelegramMediaPollResults public let isClosed: Bool public let deadlineTimeout: Int32? - - public init(pollId: MediaId, publicity: TelegramMediaPollPublicity, kind: TelegramMediaPollKind, text: String, textEntities: [MessageTextEntity], options: [TelegramMediaPollOption], correctAnswers: [Data]?, results: TelegramMediaPollResults, isClosed: Bool, deadlineTimeout: Int32?) { + + public let openAnswers: Bool + public let revotingDisabled: Bool + public let shuffleAnswers: Bool + public let hideResultsUntilClose: Bool + public let attachedMedia: Media? + + public init(pollId: MediaId, publicity: TelegramMediaPollPublicity, kind: TelegramMediaPollKind, text: String, textEntities: [MessageTextEntity], options: [TelegramMediaPollOption], correctAnswers: [Data]?, results: TelegramMediaPollResults, isClosed: Bool, deadlineTimeout: Int32?, openAnswers: Bool = false, revotingDisabled: Bool = false, shuffleAnswers: Bool = false, hideResultsUntilClose: Bool = false, attachedMedia: Media? = nil) { self.pollId = pollId self.publicity = publicity self.kind = kind @@ -176,8 +232,13 @@ public final class TelegramMediaPoll: Media, Equatable { self.results = results self.isClosed = isClosed self.deadlineTimeout = deadlineTimeout + self.openAnswers = openAnswers + self.revotingDisabled = revotingDisabled + self.shuffleAnswers = shuffleAnswers + self.hideResultsUntilClose = hideResultsUntilClose + self.attachedMedia = attachedMedia } - + public init(decoder: PostboxDecoder) { if let idBytes = decoder.decodeBytesForKeyNoCopy("i") { self.pollId = MediaId(idBytes) @@ -193,8 +254,13 @@ public final class TelegramMediaPoll: Media, Equatable { self.results = decoder.decodeObjectForKey("rs", decoder: { TelegramMediaPollResults(decoder: $0) }) as? TelegramMediaPollResults ?? TelegramMediaPollResults(voters: nil, totalVoters: nil, recentVoters: [], solution: nil) self.isClosed = decoder.decodeInt32ForKey("ic", orElse: 0) != 0 self.deadlineTimeout = decoder.decodeOptionalInt32ForKey("dt") + self.openAnswers = decoder.decodeInt32ForKey("oa", orElse: 0) != 0 + self.revotingDisabled = decoder.decodeInt32ForKey("rd", orElse: 0) != 0 + self.shuffleAnswers = decoder.decodeInt32ForKey("sa", orElse: 0) != 0 + self.hideResultsUntilClose = decoder.decodeInt32ForKey("hr", orElse: 0) != 0 + self.attachedMedia = decoder.decodeObjectForKey("am") as? Media } - + public func encode(_ encoder: PostboxEncoder) { let buffer = WriteBuffer() self.pollId.encodeToBuffer(buffer) @@ -216,19 +282,28 @@ public final class TelegramMediaPoll: Media, Equatable { } else { encoder.encodeNil(forKey: "dt") } + encoder.encodeInt32(self.openAnswers ? 1 : 0, forKey: "oa") + encoder.encodeInt32(self.revotingDisabled ? 1 : 0, forKey: "rd") + encoder.encodeInt32(self.shuffleAnswers ? 1 : 0, forKey: "sa") + encoder.encodeInt32(self.hideResultsUntilClose ? 1 : 0, forKey: "hr") + if let attachedMedia = self.attachedMedia { + encoder.encodeObject(attachedMedia, forKey: "am") + } else { + encoder.encodeNil(forKey: "am") + } } - + public func isEqual(to other: Media) -> Bool { guard let other = other as? TelegramMediaPoll else { return false } return self == other } - + public func isSemanticallyEqual(to other: Media) -> Bool { return self.isEqual(to: other) } - + public static func ==(lhs: TelegramMediaPoll, rhs: TelegramMediaPoll) -> Bool { if lhs.pollId != rhs.pollId { return false @@ -260,9 +335,26 @@ public final class TelegramMediaPoll: Media, Equatable { if lhs.deadlineTimeout != rhs.deadlineTimeout { return false } + if lhs.openAnswers != rhs.openAnswers { + return false + } + if lhs.revotingDisabled != rhs.revotingDisabled { + return false + } + if lhs.shuffleAnswers != rhs.shuffleAnswers { + return false + } + if lhs.hideResultsUntilClose != rhs.hideResultsUntilClose { + return false + } + if let lhsMedia = lhs.attachedMedia, let rhsMedia = rhs.attachedMedia { + if !lhsMedia.isEqual(to: rhsMedia) { return false } + } else if (lhs.attachedMedia == nil) != (rhs.attachedMedia == nil) { + return false + } return true } - + public func withUpdatedResults(_ results: TelegramMediaPollResults, min: Bool) -> TelegramMediaPoll { let updatedResults: TelegramMediaPollResults if min { @@ -278,7 +370,7 @@ public final class TelegramMediaPoll: Media, Equatable { } } updatedResults = TelegramMediaPollResults(voters: updatedVoters.map({ voters in - return TelegramMediaPollOptionVoters(selected: selectedOpaqueIdentifiers.contains(voters.opaqueIdentifier), opaqueIdentifier: voters.opaqueIdentifier, count: voters.count, isCorrect: correctOpaqueIdentifiers.contains(voters.opaqueIdentifier)) + return TelegramMediaPollOptionVoters(selected: selectedOpaqueIdentifiers.contains(voters.opaqueIdentifier), opaqueIdentifier: voters.opaqueIdentifier, count: voters.count, isCorrect: correctOpaqueIdentifiers.contains(voters.opaqueIdentifier), recentVoters: voters.recentVoters) }), totalVoters: results.totalVoters, recentVoters: results.recentVoters, solution: results.solution ?? self.results.solution) } else if let updatedVoters = results.voters { updatedResults = TelegramMediaPollResults(voters: updatedVoters, totalVoters: results.totalVoters, recentVoters: results.recentVoters, solution: results.solution ?? self.results.solution) @@ -288,6 +380,6 @@ public final class TelegramMediaPoll: Media, Equatable { } else { updatedResults = results } - return TelegramMediaPoll(pollId: self.pollId, publicity: self.publicity, kind: self.kind, text: self.text, textEntities: self.textEntities, options: self.options, correctAnswers: self.correctAnswers, results: updatedResults, isClosed: self.isClosed, deadlineTimeout: self.deadlineTimeout) + return TelegramMediaPoll(pollId: self.pollId, publicity: self.publicity, kind: self.kind, text: self.text, textEntities: self.textEntities, options: self.options, correctAnswers: self.correctAnswers, results: updatedResults, isClosed: self.isClosed, deadlineTimeout: self.deadlineTimeout, openAnswers: self.openAnswers, revotingDisabled: self.revotingDisabled, shuffleAnswers: self.shuffleAnswers, hideResultsUntilClose: self.hideResultsUntilClose, attachedMedia: self.attachedMedia) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift index d13a6fd4b2..0c85487841 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/Polls.swift @@ -43,10 +43,14 @@ func _internal_requestMessageSelectPollOption(account: Account, messageId: Messa } let kind: TelegramMediaPollKind if (flags & (1 << 3)) != 0 { - kind = .quiz + kind = .quiz(multipleAnswers: (flags & (1 << 2)) != 0) } else { kind = .poll(multipleAnswers: (flags & (1 << 2)) != 0) } + let openAnswers = (flags & (1 << 6)) != 0 + let revotingDisabled = (flags & (1 << 7)) != 0 + let shuffleAnswers = (flags & (1 << 8)) != 0 + let hideResultsUntilClose = (flags & (1 << 9)) != 0 let questionText: String let questionEntities: [MessageTextEntity] switch question { @@ -55,7 +59,7 @@ func _internal_requestMessageSelectPollOption(account: Account, messageId: Messa questionText = text questionEntities = messageTextEntitiesFromApiEntities(entities) } - resultPoll = TelegramMediaPoll(pollId: pollId, publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod) + resultPoll = TelegramMediaPoll(pollId: pollId, publicity: publicity, kind: kind, text: questionText, textEntities: questionEntities, options: answers.map(TelegramMediaPollOption.init(apiOption:)), correctAnswers: nil, results: TelegramMediaPollResults(apiResults: results), isClosed: (flags & (1 << 0)) != 0, deadlineTimeout: closePeriod, openAnswers: openAnswers, revotingDisabled: revotingDisabled, shuffleAnswers: shuffleAnswers, hideResultsUntilClose: hideResultsUntilClose, attachedMedia: resultPoll?.attachedMedia) } } @@ -89,6 +93,30 @@ func _internal_requestMessageSelectPollOption(account: Account, messageId: Messa } } +func _internal_addPollAnswer(account: Account, messageId: MessageId, answerText: String, answerEntities: [MessageTextEntity]) -> Signal { + return account.postbox.loadedPeerWithId(messageId.peerId) + |> take(1) + |> castError(RequestMessageSelectPollOptionError.self) + |> mapToSignal { peer in + if let inputPeer = apiInputPeer(peer) { + let apiAnswer: Api.PollAnswer = .inputPollAnswer(.init(flags: 0, text: .textWithEntities(.init(text: answerText, entities: apiEntitiesFromMessageTextEntities(answerEntities, associatedPeers: SimpleDictionary()))), option: Buffer(data: Data()), media: nil)) + return account.network.request(Api.functions.messages.addPollAnswer(peer: inputPeer, msgId: messageId.id, answer: apiAnswer)) + |> mapError { _ -> RequestMessageSelectPollOptionError in + return .generic + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> TelegramMediaPoll? in + account.stateManager.addUpdates(result) + return nil + } + |> castError(RequestMessageSelectPollOptionError.self) + } + } else { + return .single(nil) + } + } +} + func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager: AccountStateManager, messageId: MessageId) -> Signal { return postbox.transaction { transaction -> (TelegramMediaPoll, Api.InputPeer)? in guard let inputPeer = transaction.getPeer(messageId.peerId).flatMap(apiInputPeer) else { @@ -117,8 +145,11 @@ func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager if multipleAnswers { pollFlags |= 1 << 2 } - case .quiz: + case let .quiz(multipleAnswers): pollFlags |= 1 << 3 + if multipleAnswers { + pollFlags |= 1 << 2 + } } switch poll.publicity { case .anonymous: @@ -132,13 +163,17 @@ func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager pollMediaFlags |= 1 << 0 correctAnswers = correctAnswersValue.map { Buffer(data: $0) } } - + pollFlags |= 1 << 0 - + if poll.deadlineTimeout != nil { pollFlags |= 1 << 4 } - + if poll.openAnswers { pollFlags |= 1 << 6 } + if poll.revotingDisabled { pollFlags |= 1 << 7 } + if poll.shuffleAnswers { pollFlags |= 1 << 8 } + if poll.hideResultsUntilClose { pollFlags |= 1 << 9 } + var mappedSolution: String? var mappedSolutionEntities: [Api.MessageEntity]? if let solution = poll.results.solution { @@ -146,7 +181,7 @@ func _internal_requestClosePoll(postbox: Postbox, network: Network, stateManager mappedSolutionEntities = apiTextAttributeEntities(TextEntitiesMessageAttribute(entities: solution.entities), associatedPeers: SimpleDictionary()) pollMediaFlags |= 1 << 1 } - + return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: nil, media: .inputMediaPoll(.init(flags: pollMediaFlags, poll: .poll(.init(id: poll.pollId.id, flags: pollFlags, question: .textWithEntities(.init(text: poll.text, entities: apiEntitiesFromMessageTextEntities(poll.textEntities, associatedPeers: SimpleDictionary()))), answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil)), correctAnswers: correctAnswers, attachedMedia: nil, solution: mappedSolution, solutionEntities: mappedSolutionEntities, solutionMedia: nil)), replyMarkup: nil, entities: nil, scheduleDate: nil, scheduleRepeatPeriod: nil, quickReplyShortcutId: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index c5ee4182e2..38d7db955d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -256,6 +256,10 @@ public extension TelegramEngine { return _internal_requestClosePoll(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, messageId: messageId) } + public func addPollAnswer(messageId: MessageId, answerText: String, answerEntities: [MessageTextEntity] = []) -> Signal { + return _internal_addPollAnswer(account: self.account, messageId: messageId, answerText: answerText, answerEntities: answerEntities) + } + public func pollResults(messageId: MessageId, poll: TelegramMediaPoll) -> PollResultsContext { return PollResultsContext(account: self.account, messageId: messageId, poll: poll) }