diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 393a1b7eb5..ffc39f986f 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -283,6 +283,9 @@ "LOCAL_CHANNEL_MESSAGE_FWDS" = "%1$@ posted %2$d forwarded messages"; "LOCAL_CHAT_MESSAGE_FWDS" = "%1$@ forwarded %2$d messages"; +"PUSH_STORY_HIDDEN_AUTHOR" = "New story was posted"; +"PUSH_MESSAGE_STARGIFT" = "%1$@|sent you a Gift worth of %2$@ stars"; + // Common "Common.OK" = "OK"; "Common.Cancel" = "Cancel"; diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 04a0a8234d..f83749f842 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -535,7 +535,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1098720356] = { return Api.MediaArea.parse_mediaAreaVenue($0) } dict[1235637404] = { return Api.MediaArea.parse_mediaAreaWeather($0) } dict[-808853502] = { return Api.MediaAreaCoordinates.parse_mediaAreaCoordinates($0) } - dict[-1808510398] = { return Api.Message.parse_message($0) } + dict[-1761756183] = { return Api.Message.parse_message($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } dict[-741178048] = { return Api.Message.parse_messageService($0) } dict[-872240531] = { return Api.MessageAction.parse_messageActionBoostApply($0) } @@ -723,11 +723,12 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1395233698] = { return Api.PeerSettings.parse_peerSettings($0) } dict[-1707742823] = { return Api.PeerStories.parse_peerStories($0) } dict[1000707084] = { return Api.PhoneCall.parse_phoneCall($0) } - dict[912311057] = { return Api.PhoneCall.parse_phoneCallAccepted($0) } - dict[1355435489] = { return Api.PhoneCall.parse_phoneCallDiscarded($0) } + dict[587035009] = { return Api.PhoneCall.parse_phoneCallAccepted($0) } + dict[-103656189] = { return Api.PhoneCall.parse_phoneCallDiscarded($0) } dict[1399245077] = { return Api.PhoneCall.parse_phoneCallEmpty($0) } - dict[347139340] = { return Api.PhoneCall.parse_phoneCallRequested($0) } - dict[-987599081] = { return Api.PhoneCall.parse_phoneCallWaiting($0) } + dict[1161174115] = { return Api.PhoneCall.parse_phoneCallRequested($0) } + dict[-288085928] = { return Api.PhoneCall.parse_phoneCallWaiting($0) } + dict[-1344096199] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonAllowGroupCall($0) } dict[-84416311] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonBusy($0) } dict[-527056480] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonDisconnect($0) } dict[1471006352] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonHangup($0) } diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index fb45fde458..237a8c7d33 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -60,15 +60,15 @@ public extension Api { } public extension Api { indirect enum Message: TypeConstructorDescription { - case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?) + case message(flags: Int32, flags2: Int32, id: Int32, fromId: Api.Peer?, fromBoostsApplied: Int32?, peerId: Api.Peer, savedPeerId: Api.Peer?, fwdFrom: Api.MessageFwdHeader?, viaBotId: Int64?, viaBusinessBotId: Int64?, replyTo: Api.MessageReplyHeader?, date: Int32, message: String, media: Api.MessageMedia?, replyMarkup: Api.ReplyMarkup?, entities: [Api.MessageEntity]?, views: Int32?, forwards: Int32?, replies: Api.MessageReplies?, editDate: Int32?, postAuthor: String?, groupedId: Int64?, reactions: Api.MessageReactions?, restrictionReason: [Api.RestrictionReason]?, ttlPeriod: Int32?, quickReplyShortcutId: Int32?, effect: Int64?, factcheck: Api.FactCheck?, reportDeliveryUntilDate: Int32?) case messageEmpty(flags: Int32, id: Int32, peerId: Api.Peer?) case messageService(flags: Int32, id: Int32, fromId: Api.Peer?, peerId: Api.Peer, replyTo: Api.MessageReplyHeader?, date: Int32, action: Api.MessageAction, reactions: Api.MessageReactions?, ttlPeriod: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck): + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate): if boxed { - buffer.appendInt32(-1808510398) + buffer.appendInt32(-1761756183) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags2, buffer: buffer, boxed: false) @@ -106,6 +106,7 @@ public extension Api { if Int(flags) & Int(1 << 30) != 0 {serializeInt32(quickReplyShortcutId!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 2) != 0 {serializeInt64(effect!, buffer: buffer, boxed: false)} if Int(flags2) & Int(1 << 3) != 0 {factcheck!.serialize(buffer, true)} + if Int(flags2) & Int(1 << 5) != 0 {serializeInt32(reportDeliveryUntilDate!, buffer: buffer, boxed: false)} break case .messageEmpty(let flags, let id, let peerId): if boxed { @@ -134,8 +135,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck): - return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any)]) + case .message(let flags, let flags2, let id, let fromId, let fromBoostsApplied, let peerId, let savedPeerId, let fwdFrom, let viaBotId, let viaBusinessBotId, let replyTo, let date, let message, let media, let replyMarkup, let entities, let views, let forwards, let replies, let editDate, let postAuthor, let groupedId, let reactions, let restrictionReason, let ttlPeriod, let quickReplyShortcutId, let effect, let factcheck, let reportDeliveryUntilDate): + return ("message", [("flags", flags as Any), ("flags2", flags2 as Any), ("id", id as Any), ("fromId", fromId as Any), ("fromBoostsApplied", fromBoostsApplied as Any), ("peerId", peerId as Any), ("savedPeerId", savedPeerId as Any), ("fwdFrom", fwdFrom as Any), ("viaBotId", viaBotId as Any), ("viaBusinessBotId", viaBusinessBotId as Any), ("replyTo", replyTo as Any), ("date", date as Any), ("message", message as Any), ("media", media as Any), ("replyMarkup", replyMarkup as Any), ("entities", entities as Any), ("views", views as Any), ("forwards", forwards as Any), ("replies", replies as Any), ("editDate", editDate as Any), ("postAuthor", postAuthor as Any), ("groupedId", groupedId as Any), ("reactions", reactions as Any), ("restrictionReason", restrictionReason as Any), ("ttlPeriod", ttlPeriod as Any), ("quickReplyShortcutId", quickReplyShortcutId as Any), ("effect", effect as Any), ("factcheck", factcheck as Any), ("reportDeliveryUntilDate", reportDeliveryUntilDate as Any)]) case .messageEmpty(let flags, let id, let peerId): return ("messageEmpty", [("flags", flags as Any), ("id", id as Any), ("peerId", peerId as Any)]) case .messageService(let flags, let id, let fromId, let peerId, let replyTo, let date, let action, let reactions, let ttlPeriod): @@ -224,6 +225,8 @@ public extension Api { if Int(_2!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { _28 = Api.parse(reader, signature: signature) as? Api.FactCheck } } + var _29: Int32? + if Int(_2!) & Int(1 << 5) != 0 {_29 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -252,8 +255,9 @@ public extension Api { let _c26 = (Int(_1!) & Int(1 << 30) == 0) || _26 != nil let _c27 = (Int(_2!) & Int(1 << 2) == 0) || _27 != nil let _c28 = (Int(_2!) & Int(1 << 3) == 0) || _28 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 { - return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28) + let _c29 = (Int(_2!) & Int(1 << 5) == 0) || _29 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 && _c29 { + return Api.Message.message(flags: _1!, flags2: _2!, id: _3!, fromId: _4, fromBoostsApplied: _5, peerId: _6!, savedPeerId: _7, fwdFrom: _8, viaBotId: _9, viaBusinessBotId: _10, replyTo: _11, date: _12!, message: _13!, media: _14, replyMarkup: _15, entities: _16, views: _17, forwards: _18, replies: _19, editDate: _20, postAuthor: _21, groupedId: _22, reactions: _23, restrictionReason: _24, ttlPeriod: _25, quickReplyShortcutId: _26, effect: _27, factcheck: _28, reportDeliveryUntilDate: _29) } else { return nil diff --git a/submodules/TelegramApi/Sources/Api19.swift b/submodules/TelegramApi/Sources/Api19.swift index 05a69b3a82..415ec1d40d 100644 --- a/submodules/TelegramApi/Sources/Api19.swift +++ b/submodules/TelegramApi/Sources/Api19.swift @@ -1011,11 +1011,11 @@ public extension Api { public extension Api { enum PhoneCall: TypeConstructorDescription { case phoneCall(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAOrB: Buffer, keyFingerprint: Int64, protocol: Api.PhoneCallProtocol, connections: [Api.PhoneConnection], startDate: Int32, customParameters: Api.DataJSON?, conferenceCall: Api.InputGroupCall?) - case phoneCallAccepted(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gB: Buffer, protocol: Api.PhoneCallProtocol) - case phoneCallDiscarded(flags: Int32, id: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?) + case phoneCallAccepted(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gB: Buffer, protocol: Api.PhoneCallProtocol, conferenceCall: Api.InputGroupCall?) + case phoneCallDiscarded(flags: Int32, id: Int64, reason: Api.PhoneCallDiscardReason?, duration: Int32?, conferenceCall: Api.InputGroupCall?) case phoneCallEmpty(id: Int64) - case phoneCallRequested(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAHash: Buffer, protocol: Api.PhoneCallProtocol) - case phoneCallWaiting(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, protocol: Api.PhoneCallProtocol, receiveDate: Int32?) + case phoneCallRequested(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, gAHash: Buffer, protocol: Api.PhoneCallProtocol, conferenceCall: Api.InputGroupCall?) + case phoneCallWaiting(flags: Int32, id: Int64, accessHash: Int64, date: Int32, adminId: Int64, participantId: Int64, protocol: Api.PhoneCallProtocol, receiveDate: Int32?, conferenceCall: Api.InputGroupCall?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -1041,9 +1041,9 @@ public extension Api { if Int(flags) & Int(1 << 7) != 0 {customParameters!.serialize(buffer, true)} if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)} break - case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`): + case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`, let conferenceCall): if boxed { - buffer.appendInt32(912311057) + buffer.appendInt32(587035009) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -1053,15 +1053,17 @@ public extension Api { serializeInt64(participantId, buffer: buffer, boxed: false) serializeBytes(gB, buffer: buffer, boxed: false) `protocol`.serialize(buffer, true) + if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)} break - case .phoneCallDiscarded(let flags, let id, let reason, let duration): + case .phoneCallDiscarded(let flags, let id, let reason, let duration, let conferenceCall): if boxed { - buffer.appendInt32(1355435489) + buffer.appendInt32(-103656189) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 0) != 0 {reason!.serialize(buffer, true)} if Int(flags) & Int(1 << 1) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)} break case .phoneCallEmpty(let id): if boxed { @@ -1069,9 +1071,9 @@ public extension Api { } serializeInt64(id, buffer: buffer, boxed: false) break - case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`): + case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`, let conferenceCall): if boxed { - buffer.appendInt32(347139340) + buffer.appendInt32(1161174115) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -1081,10 +1083,11 @@ public extension Api { serializeInt64(participantId, buffer: buffer, boxed: false) serializeBytes(gAHash, buffer: buffer, boxed: false) `protocol`.serialize(buffer, true) + if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)} break - case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate): + case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate, let conferenceCall): if boxed { - buffer.appendInt32(-987599081) + buffer.appendInt32(-288085928) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -1094,6 +1097,7 @@ public extension Api { serializeInt64(participantId, buffer: buffer, boxed: false) `protocol`.serialize(buffer, true) if Int(flags) & Int(1 << 0) != 0 {serializeInt32(receiveDate!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 8) != 0 {conferenceCall!.serialize(buffer, true)} break } } @@ -1102,16 +1106,16 @@ public extension Api { switch self { case .phoneCall(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAOrB, let keyFingerprint, let `protocol`, let connections, let startDate, let customParameters, let conferenceCall): return ("phoneCall", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gAOrB", gAOrB as Any), ("keyFingerprint", keyFingerprint as Any), ("`protocol`", `protocol` as Any), ("connections", connections as Any), ("startDate", startDate as Any), ("customParameters", customParameters as Any), ("conferenceCall", conferenceCall as Any)]) - case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`): - return ("phoneCallAccepted", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gB", gB as Any), ("`protocol`", `protocol` as Any)]) - case .phoneCallDiscarded(let flags, let id, let reason, let duration): - return ("phoneCallDiscarded", [("flags", flags as Any), ("id", id as Any), ("reason", reason as Any), ("duration", duration as Any)]) + case .phoneCallAccepted(let flags, let id, let accessHash, let date, let adminId, let participantId, let gB, let `protocol`, let conferenceCall): + return ("phoneCallAccepted", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gB", gB as Any), ("`protocol`", `protocol` as Any), ("conferenceCall", conferenceCall as Any)]) + case .phoneCallDiscarded(let flags, let id, let reason, let duration, let conferenceCall): + return ("phoneCallDiscarded", [("flags", flags as Any), ("id", id as Any), ("reason", reason as Any), ("duration", duration as Any), ("conferenceCall", conferenceCall as Any)]) case .phoneCallEmpty(let id): return ("phoneCallEmpty", [("id", id as Any)]) - case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`): - return ("phoneCallRequested", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gAHash", gAHash as Any), ("`protocol`", `protocol` as Any)]) - case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate): - return ("phoneCallWaiting", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("`protocol`", `protocol` as Any), ("receiveDate", receiveDate as Any)]) + case .phoneCallRequested(let flags, let id, let accessHash, let date, let adminId, let participantId, let gAHash, let `protocol`, let conferenceCall): + return ("phoneCallRequested", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("gAHash", gAHash as Any), ("`protocol`", `protocol` as Any), ("conferenceCall", conferenceCall as Any)]) + case .phoneCallWaiting(let flags, let id, let accessHash, let date, let adminId, let participantId, let `protocol`, let receiveDate, let conferenceCall): + return ("phoneCallWaiting", [("flags", flags as Any), ("id", id as Any), ("accessHash", accessHash as Any), ("date", date as Any), ("adminId", adminId as Any), ("participantId", participantId as Any), ("`protocol`", `protocol` as Any), ("receiveDate", receiveDate as Any), ("conferenceCall", conferenceCall as Any)]) } } @@ -1189,6 +1193,10 @@ public extension Api { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol } + var _9: Api.InputGroupCall? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -1197,8 +1205,9 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallAccepted(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gB: _7!, protocol: _8!) + let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.PhoneCall.phoneCallAccepted(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gB: _7!, protocol: _8!, conferenceCall: _9) } else { return nil @@ -1215,12 +1224,17 @@ public extension Api { } } var _4: Int32? if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } + var _5: Api.InputGroupCall? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _5 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.PhoneCall.phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4) + let _c5 = (Int(_1!) & Int(1 << 8) == 0) || _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.PhoneCall.phoneCallDiscarded(flags: _1!, id: _2!, reason: _3, duration: _4, conferenceCall: _5) } else { return nil @@ -1256,6 +1270,10 @@ public extension Api { if let signature = reader.readInt32() { _8 = Api.parse(reader, signature: signature) as? Api.PhoneCallProtocol } + var _9: Api.InputGroupCall? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -1264,8 +1282,9 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallRequested(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAHash: _7!, protocol: _8!) + let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.PhoneCall.phoneCallRequested(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, gAHash: _7!, protocol: _8!, conferenceCall: _9) } else { return nil @@ -1290,6 +1309,10 @@ public extension Api { } var _8: Int32? if Int(_1!) & Int(1 << 0) != 0 {_8 = reader.readInt32() } + var _9: Api.InputGroupCall? + if Int(_1!) & Int(1 << 8) != 0 {if let signature = reader.readInt32() { + _9 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -1298,8 +1321,9 @@ public extension Api { let _c6 = _6 != nil let _c7 = _7 != nil let _c8 = (Int(_1!) & Int(1 << 0) == 0) || _8 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 { - return Api.PhoneCall.phoneCallWaiting(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, protocol: _7!, receiveDate: _8) + let _c9 = (Int(_1!) & Int(1 << 8) == 0) || _9 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { + return Api.PhoneCall.phoneCallWaiting(flags: _1!, id: _2!, accessHash: _3!, date: _4!, adminId: _5!, participantId: _6!, protocol: _7!, receiveDate: _8, conferenceCall: _9) } else { return nil @@ -1310,6 +1334,7 @@ public extension Api { } public extension Api { enum PhoneCallDiscardReason: TypeConstructorDescription { + case phoneCallDiscardReasonAllowGroupCall(encryptedKey: Buffer) case phoneCallDiscardReasonBusy case phoneCallDiscardReasonDisconnect case phoneCallDiscardReasonHangup @@ -1317,6 +1342,12 @@ public extension Api { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { + case .phoneCallDiscardReasonAllowGroupCall(let encryptedKey): + if boxed { + buffer.appendInt32(-1344096199) + } + serializeBytes(encryptedKey, buffer: buffer, boxed: false) + break case .phoneCallDiscardReasonBusy: if boxed { buffer.appendInt32(-84416311) @@ -1346,6 +1377,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { + case .phoneCallDiscardReasonAllowGroupCall(let encryptedKey): + return ("phoneCallDiscardReasonAllowGroupCall", [("encryptedKey", encryptedKey as Any)]) case .phoneCallDiscardReasonBusy: return ("phoneCallDiscardReasonBusy", []) case .phoneCallDiscardReasonDisconnect: @@ -1357,6 +1390,17 @@ public extension Api { } } + public static func parse_phoneCallDiscardReasonAllowGroupCall(_ reader: BufferReader) -> PhoneCallDiscardReason? { + var _1: Buffer? + _1 = parseBytes(reader) + let _c1 = _1 != nil + if _c1 { + return Api.PhoneCallDiscardReason.phoneCallDiscardReasonAllowGroupCall(encryptedKey: _1!) + } + else { + return nil + } + } public static func parse_phoneCallDiscardReasonBusy(_ reader: BufferReader) -> PhoneCallDiscardReason? { return Api.PhoneCallDiscardReason.phoneCallDiscardReasonBusy } diff --git a/submodules/TelegramApi/Sources/Api37.swift b/submodules/TelegramApi/Sources/Api37.swift index 950c0fb8cc..0ff454f46f 100644 --- a/submodules/TelegramApi/Sources/Api37.swift +++ b/submodules/TelegramApi/Sources/Api37.swift @@ -7483,6 +7483,27 @@ public extension Api.functions.messages { }) } } +public extension Api.functions.messages { + static func reportMessagesDelivery(flags: Int32, peer: Api.InputPeer, id: [Int32]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1517122453) + serializeInt32(flags, buffer: buffer, boxed: false) + peer.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(id.count)) + for item in id { + serializeInt32(item, buffer: buffer, boxed: false) + } + return (FunctionDescription(name: "messages.reportMessagesDelivery", parameters: [("flags", String(describing: flags)), ("peer", String(describing: peer)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } +} public extension Api.functions.messages { static func reportReaction(peer: Api.InputPeer, id: Int32, reactionPeer: Api.InputPeer) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() @@ -9587,11 +9608,12 @@ public extension Api.functions.phone { } } public extension Api.functions.phone { - static func createConferenceCall(peer: Api.InputPhoneCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func createConferenceCall(peer: Api.InputPhoneCall, keyFingerprint: Int64) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1828162221) + buffer.appendInt32(-540472917) peer.serialize(buffer, true) - return (FunctionDescription(name: "phone.createConferenceCall", parameters: [("peer", String(describing: peer))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in + serializeInt64(keyFingerprint, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.createConferenceCall", parameters: [("peer", String(describing: peer)), ("keyFingerprint", String(describing: keyFingerprint))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in let reader = BufferReader(buffer) var result: Api.phone.PhoneCall? if let signature = reader.readInt32() { @@ -9834,15 +9856,16 @@ public extension Api.functions.phone { } } public extension Api.functions.phone { - static func joinGroupCall(flags: Int32, call: Api.InputGroupCall, joinAs: Api.InputPeer, inviteHash: String?, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func joinGroupCall(flags: Int32, call: Api.InputGroupCall, joinAs: Api.InputPeer, inviteHash: String?, keyFingerprint: Int64?, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(-1322057861) + buffer.appendInt32(-702669325) serializeInt32(flags, buffer: buffer, boxed: false) call.serialize(buffer, true) joinAs.serialize(buffer, true) if Int(flags) & Int(1 << 1) != 0 {serializeString(inviteHash!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt64(keyFingerprint!, buffer: buffer, boxed: false)} params.serialize(buffer, true) - return (FunctionDescription(name: "phone.joinGroupCall", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("joinAs", String(describing: joinAs)), ("inviteHash", String(describing: inviteHash)), ("params", String(describing: params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + return (FunctionDescription(name: "phone.joinGroupCall", parameters: [("flags", String(describing: flags)), ("call", String(describing: call)), ("joinAs", String(describing: joinAs)), ("inviteHash", String(describing: inviteHash)), ("keyFingerprint", String(describing: keyFingerprint)), ("params", String(describing: params))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { @@ -9915,15 +9938,16 @@ public extension Api.functions.phone { } } public extension Api.functions.phone { - static func requestCall(flags: Int32, userId: Api.InputUser, randomId: Int32, gAHash: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func requestCall(flags: Int32, userId: Api.InputUser, conferenceCall: Api.InputGroupCall?, randomId: Int32, gAHash: Buffer, `protocol`: Api.PhoneCallProtocol) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1124046573) + buffer.appendInt32(-1497079796) serializeInt32(flags, buffer: buffer, boxed: false) userId.serialize(buffer, true) + if Int(flags) & Int(1 << 1) != 0 {conferenceCall!.serialize(buffer, true)} serializeInt32(randomId, buffer: buffer, boxed: false) serializeBytes(gAHash, buffer: buffer, boxed: false) `protocol`.serialize(buffer, true) - return (FunctionDescription(name: "phone.requestCall", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("randomId", String(describing: randomId)), ("gAHash", String(describing: gAHash)), ("`protocol`", String(describing: `protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in + return (FunctionDescription(name: "phone.requestCall", parameters: [("flags", String(describing: flags)), ("userId", String(describing: userId)), ("conferenceCall", String(describing: conferenceCall)), ("randomId", String(describing: randomId)), ("gAHash", String(describing: gAHash)), ("`protocol`", String(describing: `protocol`))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.PhoneCall? in let reader = BufferReader(buffer) var result: Api.phone.PhoneCall? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCallsUI/Sources/CallController.swift b/submodules/TelegramCallsUI/Sources/CallController.swift index ab8cb6912b..67cb22a211 100644 --- a/submodules/TelegramCallsUI/Sources/CallController.swift +++ b/submodules/TelegramCallsUI/Sources/CallController.swift @@ -522,6 +522,13 @@ public final class CallController: ViewController { let _ = self?.dismiss() } + displayNode.conferenceAddParticipant = { [weak self] in + guard let self else { + return + } + self.conferenceAddParticipant() + } + self.controllerNode.presentCallRating = { [weak self] callId, isVideo in if let strongSelf = self, !strongSelf.presentedCallRating { strongSelf.presentedCallRating = true @@ -667,6 +674,35 @@ public final class CallController: ViewController { }) } + private func conferenceAddParticipant() { + let controller = self.call.context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams( + context: self.call.context, + filter: [.onlyWriteable], + hasChatListSelector: true, + hasContactSelector: true, + hasGlobalSearch: true, + title: "Add Participant", + pretendPresentedInModal: false + )) + controller.peerSelected = { [weak self, weak controller] peer, _ in + controller?.dismiss() + + guard let self else { + return + } + guard case let .call(call) = self.call else { + return + } + guard let call = call as? PresentationCallImpl else { + return + } + let _ = call.requestAddToConference(peerId: peer.id) + } + self.dismiss() + + (self.call.context.sharedContext.mainWindow?.viewController as? NavigationController)?.pushViewController(controller) + } + @objc private func backPressed() { self.dismiss() } diff --git a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 608651f188..eb1d460a5d 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -60,6 +60,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP var dismissedInteractively: (() -> Void)? var dismissAllTooltips: (() -> Void)? var restoreUIForPictureInPicture: ((@escaping (Bool) -> Void) -> Void)? + var conferenceAddParticipant: (() -> Void)? private var emojiKey: (data: Data, resolvedKey: [String])? private var validLayout: (layout: ContainerViewLayout, navigationBarHeight: CGFloat)? @@ -129,6 +130,14 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP guard let self else { return } + + #if DEBUG + if self.sharedContext.immediateExperimentalUISettings.conferenceCalls { + self.conferenceAddParticipant?() + return + } + #endif + self.call.toggleIsMuted() } self.callScreen.endCallAction = { [weak self] in @@ -157,6 +166,12 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP } self.restoreUIForPictureInPicture?(completion) } + self.callScreen.conferenceAddParticipant = { [weak self] in + guard let self else { + return + } + self.conferenceAddParticipant?() + } self.callScreenState = PrivateCallScreen.State( strings: presentationData.strings, diff --git a/submodules/TelegramCallsUI/Sources/PresentationCall.swift b/submodules/TelegramCallsUI/Sources/PresentationCall.swift index 72bc1eb8e5..6df318a5fd 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCall.swift @@ -550,9 +550,9 @@ public final class PresentationCallImpl: PresentationCall { presentationState = PresentationCallState(state: .terminating(reason), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) case let .terminated(id, reason, options): presentationState = PresentationCallState(state: .terminated(id, reason, self.callWasActive && (options.contains(.reportRating) || self.shouldPresentCallRating)), videoState: mappedVideoState, remoteVideoState: .inactive, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) - case let .requesting(ringing): + case let .requesting(ringing, _): presentationState = PresentationCallState(state: .requesting(ringing), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) - case let .active(_, _, keyVisualHash, _, _, _, _, _, _): + case let .active(_, _, keyVisualHash, _, _, _, _, _, _), let .switchedToConference(_, keyVisualHash, _): self.callWasActive = true if let callContextState = callContextState { switch callContextState.state { @@ -585,216 +585,233 @@ public final class PresentationCallImpl: PresentationCall { } } + var conferenceCallData: (key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference)? + var conferenceFromCallId: CallId? + switch sessionState.state { + case let .active(id, key, keyVisualHash, _, _, _, _, _, conferenceCall): + if let conferenceCall { + conferenceFromCallId = id + conferenceCallData = (key, keyVisualHash, conferenceCall) + } + case let .switchedToConference(key, keyVisualHash, conferenceCall): + conferenceCallData = (key, keyVisualHash, conferenceCall) + default: + break + } + + if let (key, keyVisualHash, conferenceCall) = conferenceCallData { + if self.conferenceCallDisposable == nil { + presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) + + self.conferenceCallDisposable = (self.context.engine.calls.getCurrentGroupCall(callId: conferenceCall.id, accessHash: conferenceCall.accessHash) + |> delay(sessionState.isOutgoing ? 0.0 : 2.0, queue: .mainQueue()) + |> deliverOnMainQueue).startStrict(next: { [weak self] result in + guard let self, let result else { + return + } + + let conferenceCall = PresentationGroupCallImpl( + accountContext: self.context, + audioSession: self.audioSession, + callKitIntegration: self.callKitIntegration, + getDeviceAccessData: self.getDeviceAccessData, + initialCall: EngineGroupCallDescription( + id: result.info.id, + accessHash: result.info.accessHash, + title: nil, + scheduleTimestamp: nil, + subscribedToScheduled: false, + isStream: false + ), + internalId: CallSessionInternalId(), + peerId: nil, + isChannel: false, + invite: nil, + joinAsPeerId: nil, + isStream: false, + encryptionKey: (key, 1), + conferenceFromCallId: conferenceFromCallId, + isConference: true, + sharedAudioDevice: self.sharedAudioDevice + ) + self.conferenceCall = conferenceCall + + conferenceCall.setIsMuted(action: .muted(isPushToTalkActive: !self.isMutedValue)) + + let accountPeerId = conferenceCall.account.peerId + let videoEndpoints: Signal<(local: String?, remote: PresentationGroupCallRequestedVideo?), NoError> = conferenceCall.members + |> map { members -> (local: String?, remote: PresentationGroupCallRequestedVideo?) in + guard let members else { + return (nil, nil) + } + var local: String? + var remote: PresentationGroupCallRequestedVideo? + for participant in members.participants { + if let video = participant.requestedPresentationVideoChannel(minQuality: .thumbnail, maxQuality: .full) ?? participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: .full) { + if participant.peer.id == accountPeerId { + local = video.endpointId + } else { + if remote == nil { + remote = video + } + } + } + } + return (local, remote) + } + |> distinctUntilChanged(isEqual: { lhs, rhs in + return lhs == rhs + }) + + let remoteIsConnectedAggregated = combineLatest(queue: .mainQueue(), + self.remoteConferenceIsConnected.get(), + conferenceCall.hasActiveIncomingData + ) + |> map { remoteConferenceIsConnected, hasActiveIncomingData -> Bool in + //return remoteConferenceIsConnected || hasActiveIncomingData + return true + } + |> distinctUntilChanged + + var startTimestamp: Double? + self.ongoingContextStateDisposable = (combineLatest(queue: .mainQueue(), + conferenceCall.state, + videoEndpoints, + conferenceCall.signalBars, + conferenceCall.isFailed, + remoteIsConnectedAggregated + ) + |> deliverOnMainQueue).startStrict(next: { [weak self] callState, videoEndpoints, signalBars, isFailed, remoteIsConnectedAggregated in + guard let self else { + return + } + + var mappedLocalVideoState: PresentationCallState.VideoState = .inactive + var mappedRemoteVideoState: PresentationCallState.RemoteVideoState = .inactive + + if let local = videoEndpoints.local { + mappedLocalVideoState = .active(isScreencast: false, endpointId: local) + } + if let remote = videoEndpoints.remote { + mappedRemoteVideoState = .active(endpointId: remote.endpointId) + } + + self.localVideoEndpointId = videoEndpoints.local + self.remoteVideoEndpointId = videoEndpoints.remote?.endpointId + + if let conferenceCall = self.conferenceCall { + var requestedVideo: [PresentationGroupCallRequestedVideo] = [] + if let remote = videoEndpoints.remote { + requestedVideo.append(remote) + } + conferenceCall.setRequestedVideoList(items: requestedVideo) + } + + var isConnected = false + let mappedState: PresentationCallState.State + if isFailed { + mappedState = .terminating(.error(.disconnected)) + } else { + switch callState.networkState { + case .connecting: + mappedState = .connecting(keyVisualHash) + case .connected: + isConnected = true + if remoteIsConnectedAggregated { + let timestamp = startTimestamp ?? CFAbsoluteTimeGetCurrent() + startTimestamp = timestamp + mappedState = .active(timestamp, signalBars, keyVisualHash) + } else { + mappedState = .connecting(keyVisualHash) + } + } + } + + self.updateConferenceIsConnected(isConnected: isConnected) + + if !self.didDropCall && !self.droppedCall { + let presentationState = PresentationCallState( + state: mappedState, + videoState: mappedLocalVideoState, + remoteVideoState: mappedRemoteVideoState, + remoteAudioState: .active, + remoteBatteryLevel: .normal + ) + self.statePromise.set(presentationState) + self.updateTone(presentationState, callContextState: nil, previous: nil) + } + }) + + self.ongoingContextIsFailedDisposable = (conferenceCall.isFailed + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak self] _ in + guard let self else { + return + } + if !self.didDropCall { + self.didDropCall = true + self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil)) + } + }) + + self.ongoingContextIsDroppedDisposable = (conferenceCall.canBeRemoved + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue).startStrict(next: { [weak self] _ in + guard let self else { + return + } + if !self.didDropCall { + self.didDropCall = true + self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil)) + } + }) + + var audioLevelId: UInt32? + let audioLevel = conferenceCall.audioLevels |> map { audioLevels -> Float in + var result: Float = 0 + for item in audioLevels { + if let audioLevelId { + if item.1 == audioLevelId { + result = item.2 + break + } + } else { + if item.1 != 0 { + audioLevelId = item.1 + result = item.2 + break + } + } + } + + return result + } + + self.audioLevelDisposable = (audioLevel + |> deliverOnMainQueue).start(next: { [weak self] level in + if let strongSelf = self { + strongSelf.audioLevelPromise.set(level) + } + }) + }) + } + } + switch sessionState.state { case .requesting: if let _ = audioSessionControl { self.audioSessionShouldBeActive.set(true) } - case let .active(id, key, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall): - if let conferenceCall, self.conferenceCallDisposable == nil { - presentationState = PresentationCallState(state: .connecting(nil), videoState: mappedVideoState, remoteVideoState: mappedRemoteVideoState, remoteAudioState: mappedRemoteAudioState, remoteBatteryLevel: mappedRemoteBatteryLevel) - - self.conferenceCallDisposable = (self.context.engine.calls.getCurrentGroupCall(callId: conferenceCall.id, accessHash: conferenceCall.accessHash) - |> delay(sessionState.isOutgoing ? 0.0 : 2.0, queue: .mainQueue()) - |> deliverOnMainQueue).startStrict(next: { [weak self] result in - guard let self, let result else { - return - } - - let conferenceCall = PresentationGroupCallImpl( - accountContext: self.context, - audioSession: self.audioSession, - callKitIntegration: self.callKitIntegration, - getDeviceAccessData: self.getDeviceAccessData, - initialCall: EngineGroupCallDescription( - id: result.info.id, - accessHash: result.info.accessHash, - title: nil, - scheduleTimestamp: nil, - subscribedToScheduled: false, - isStream: false - ), - internalId: CallSessionInternalId(), - peerId: nil, - isChannel: false, - invite: nil, - joinAsPeerId: nil, - isStream: false, - encryptionKey: key, - conferenceFromCallId: id, - isConference: true, - sharedAudioDevice: self.sharedAudioDevice - ) - self.conferenceCall = conferenceCall - - conferenceCall.setIsMuted(action: .muted(isPushToTalkActive: !self.isMutedValue)) - - let accountPeerId = conferenceCall.account.peerId - let videoEndpoints: Signal<(local: String?, remote: PresentationGroupCallRequestedVideo?), NoError> = conferenceCall.members - |> map { members -> (local: String?, remote: PresentationGroupCallRequestedVideo?) in - guard let members else { - return (nil, nil) - } - var local: String? - var remote: PresentationGroupCallRequestedVideo? - for participant in members.participants { - if let video = participant.requestedPresentationVideoChannel(minQuality: .thumbnail, maxQuality: .full) ?? participant.requestedVideoChannel(minQuality: .thumbnail, maxQuality: .full) { - if participant.peer.id == accountPeerId { - local = video.endpointId - } else { - if remote == nil { - remote = video - } - } - } - } - return (local, remote) - } - |> distinctUntilChanged(isEqual: { lhs, rhs in - return lhs == rhs - }) - - let remoteIsConnectedAggregated = combineLatest(queue: .mainQueue(), - self.remoteConferenceIsConnected.get(), - conferenceCall.hasActiveIncomingData - ) - |> map { remoteConferenceIsConnected, hasActiveIncomingData -> Bool in - return remoteConferenceIsConnected || hasActiveIncomingData - } - |> distinctUntilChanged - - var startTimestamp: Double? - self.ongoingContextStateDisposable = (combineLatest(queue: .mainQueue(), - conferenceCall.state, - videoEndpoints, - conferenceCall.signalBars, - conferenceCall.isFailed, - remoteIsConnectedAggregated - ) - |> deliverOnMainQueue).startStrict(next: { [weak self] callState, videoEndpoints, signalBars, isFailed, remoteIsConnectedAggregated in - guard let self else { - return - } - - var mappedLocalVideoState: PresentationCallState.VideoState = .inactive - var mappedRemoteVideoState: PresentationCallState.RemoteVideoState = .inactive - - if let local = videoEndpoints.local { - mappedLocalVideoState = .active(isScreencast: false, endpointId: local) - } - if let remote = videoEndpoints.remote { - mappedRemoteVideoState = .active(endpointId: remote.endpointId) - } - - self.localVideoEndpointId = videoEndpoints.local - self.remoteVideoEndpointId = videoEndpoints.remote?.endpointId - - if let conferenceCall = self.conferenceCall { - var requestedVideo: [PresentationGroupCallRequestedVideo] = [] - if let remote = videoEndpoints.remote { - requestedVideo.append(remote) - } - conferenceCall.setRequestedVideoList(items: requestedVideo) - } - - var isConnected = false - let mappedState: PresentationCallState.State - if isFailed { - mappedState = .terminating(.error(.disconnected)) - } else { - switch callState.networkState { - case .connecting: - mappedState = .connecting(keyVisualHash) - case .connected: - isConnected = true - if remoteIsConnectedAggregated { - let timestamp = startTimestamp ?? CFAbsoluteTimeGetCurrent() - startTimestamp = timestamp - mappedState = .active(timestamp, signalBars, keyVisualHash) - } else { - mappedState = .connecting(keyVisualHash) - } - } - } - - self.updateConferenceIsConnected(isConnected: isConnected) - - if !self.didDropCall && !self.droppedCall { - let presentationState = PresentationCallState( - state: mappedState, - videoState: mappedLocalVideoState, - remoteVideoState: mappedRemoteVideoState, - remoteAudioState: .active, - remoteBatteryLevel: .normal - ) - self.statePromise.set(presentationState) - self.updateTone(presentationState, callContextState: nil, previous: nil) - } - }) - - self.ongoingContextIsFailedDisposable = (conferenceCall.isFailed - |> filter { $0 } - |> take(1) - |> deliverOnMainQueue).startStrict(next: { [weak self] _ in - guard let self else { - return - } - if !self.didDropCall { - self.didDropCall = true - self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil)) - } - }) - - self.ongoingContextIsDroppedDisposable = (conferenceCall.canBeRemoved - |> filter { $0 } - |> take(1) - |> deliverOnMainQueue).startStrict(next: { [weak self] _ in - guard let self else { - return - } - if !self.didDropCall { - self.didDropCall = true - self.callSessionManager.drop(internalId: self.internalId, reason: .disconnect, debugLog: .single(nil)) - } - }) - - var audioLevelId: UInt32? - let audioLevel = conferenceCall.audioLevels |> map { audioLevels -> Float in - var result: Float = 0 - for item in audioLevels { - if let audioLevelId { - if item.1 == audioLevelId { - result = item.2 - break - } - } else { - if item.1 != 0 { - audioLevelId = item.1 - result = item.2 - break - } - } - } - - return result - } - - self.audioLevelDisposable = (audioLevel - |> deliverOnMainQueue).start(next: { [weak self] level in - if let strongSelf = self { - strongSelf.audioLevelPromise.set(level) - } - }) - }) - } - + case let .active(id, key, _, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall): if conferenceCall == nil, self.isExpectedToBeConference { self.createConferenceIfPossible() } self.audioSessionShouldBeActive.set(true) - if self.isExpectedToBeConference { + if self.isExpectedToBeConference || conferenceCallData != nil { if sessionState.isOutgoing { self.callKitIntegration?.reportOutgoingCallConnected(uuid: sessionState.id, at: Date()) } @@ -861,6 +878,8 @@ public final class PresentationCallImpl: PresentationCall { }) } } + case .switchedToConference: + self.audioSessionShouldBeActive.set(true) case let .terminated(_, _, options): self.audioSessionShouldBeActive.set(true) if wasActive { @@ -1093,9 +1112,9 @@ public final class PresentationCallImpl: PresentationCall { } private func sendConferenceSignalingMessage(dict: [String: Any]) { - if let data = try? JSONSerialization.data(withJSONObject: dict) { + /*if let data = try? JSONSerialization.data(withJSONObject: dict) { self.context.account.callSessionManager.sendSignalingData(internalId: self.internalId, data: data) - } + }*/ } private func updateIsAudioSessionActive(_ value: Bool) { @@ -1272,6 +1291,32 @@ public final class PresentationCallImpl: PresentationCall { self.videoCapturer?.setIsVideoEnabled(!isPaused) } + public func requestAddToConference(peerId: EnginePeer.Id) -> Disposable { + var conferenceCall: (conference: GroupCallReference, encryptionKey: Data)? + if let sessionState = self.sessionState { + switch sessionState.state { + case let .active(_, key, _, _, _, _, _, _, conferenceCallValue): + if let conferenceCallValue { + conferenceCall = (conferenceCallValue, key) + } + case let .switchedToConference(key, _, conferenceCallValue): + conferenceCall = (conferenceCallValue, key) + default: + break + } + } + guard let conferenceCall else { + return EmptyDisposable + } + return (self.callSessionManager.request(peerId: peerId, isVideo: false, enableVideo: true, conferenceCall: conferenceCall) + |> deliverOnMainQueue).startStandalone(next: { [weak self] requestedInternalId in + guard let self else { + return + } + let _ = self + }) + } + public func setCurrentAudioOutput(_ output: AudioSessionOutput) { guard self.currentAudioOutputValue != output else { return diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 632a0705dc..5f4584f698 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -536,7 +536,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { |> mapToSignal { areVideoCallsAvailable -> Signal in let isVideoPossible: Bool = areVideoCallsAvailable - return context.account.callSessionManager.request(peerId: peerId, isVideo: isVideo, enableVideo: isVideoPossible, internalId: internalId) + return context.account.callSessionManager.request(peerId: peerId, isVideo: isVideo, enableVideo: isVideoPossible, conferenceCall: nil, internalId: internalId) } return (combineLatest(queue: .mainQueue(), request, networkType |> take(1), context.account.postbox.peerView(id: peerId) |> map { peerView -> Bool in diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index b9974d49ab..cd67f6fc57 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -865,7 +865,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private var screencastStateDisposable: Disposable? public let isStream: Bool - private let encryptionKey: Data? + private let encryptionKey: (key: Data, fingerprint: Int64)? private let sharedAudioDevice: OngoingCallContext.AudioDevice? private let conferenceFromCallId: CallId? @@ -885,7 +885,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { invite: String?, joinAsPeerId: EnginePeer.Id?, isStream: Bool, - encryptionKey: Data?, + encryptionKey: (key: Data, fingerprint: Int64)?, conferenceFromCallId: CallId?, isConference: Bool, sharedAudioDevice: OngoingCallContext.AudioDevice? @@ -1711,16 +1711,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } var encryptionKey: Data? - encryptionKey = self.encryptionKey - - #if DEBUG - if encryptionKey == nil { - encryptionKey = Data(count: 256) - } + encryptionKey = self.encryptionKey?.key if "".isEmpty { encryptionKey = nil } - #endif genericCallContext = .call(OngoingGroupCallContext(audioSessionActive: self.audioSessionActive.get(), video: self.videoCapturer, requestMediaChannelDescriptions: { [weak self] ssrcs, completion in let disposable = MetaDisposable() @@ -1860,7 +1854,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { preferMuted: true, joinPayload: joinPayload, peerAdminIds: peerAdminIds, - inviteHash: strongSelf.invite + inviteHash: strongSelf.invite, + keyFingerprint: strongSelf.encryptionKey?.fingerprint ) |> deliverOnMainQueue).start(next: { joinCallResult in guard let strongSelf = self else { diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index a348b6c1e8..3d4675694a 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -126,7 +126,7 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { switch messsage { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let chatPeerId = messagePeerId return chatPeerId.peerId case let .messageEmpty(_, _, peerId): @@ -142,7 +142,7 @@ func apiMessagePeerId(_ messsage: Api.Message) -> PeerId? { func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { switch message { - case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, fromId, _, chatPeerId, savedPeerId, fwdHeader, viaBotId, viaBusinessBotId, replyTo, _, _, media, _, entities, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = chatPeerId.peerId var result = [peerId] @@ -270,7 +270,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { func apiMessageAssociatedMessageIds(_ message: Api.Message) -> (replyIds: ReferencedReplyMessageIds, generalIds: [MessageId])? { switch message { - case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, chatPeerId, _, _, _, _, replyTo, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): if let replyTo = replyTo { let peerId: PeerId = chatPeerId.peerId @@ -654,7 +654,7 @@ func messageTextEntitiesFromApiEntities(_ entities: [Api.MessageEntity]) -> [Mes extension StoreMessage { convenience init?(apiMessage: Api.Message, accountPeerId: PeerId, peerIsForum: Bool, namespace: MessageId.Namespace = Namespaces.Message.Cloud) { switch apiMessage { - case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck): + case let .message(flags, flags2, id, fromId, boosts, chatPeerId, savedPeerId, fwdFrom, viaBotId, viaBusinessBotId, replyTo, date, message, media, replyMarkup, entities, views, forwards, replies, editDate, postAuthor, groupingId, reactions, restrictionReason, ttlPeriod, quickReplyShortcutId, messageEffectId, factCheck, _): var attributes: [MessageAttribute] = [] if (flags2 & (1 << 4)) != 0 { diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 16edfd16f0..770fa5b8d3 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -200,6 +200,8 @@ extension PhoneCallDiscardReason { self = .hangup case .phoneCallDiscardReasonMissed: self = .missed + case .phoneCallDiscardReasonAllowGroupCall: + self = .hangup } } } diff --git a/submodules/TelegramCore/Sources/State/AccountStateManager.swift b/submodules/TelegramCore/Sources/State/AccountStateManager.swift index 1ebfc231be..79ce8fdbb5 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManager.swift @@ -134,19 +134,22 @@ public final class AccountStateManager { public let timestamp: Int32 public let peer: EnginePeer public let isVideo: Bool + public let isConference: Bool init( callId: Int64, callAccessHash: Int64, timestamp: Int32, peer: EnginePeer, - isVideo: Bool + isVideo: Bool, + isConference: Bool ) { self.callId = callId self.callAccessHash = callAccessHash self.timestamp = timestamp self.peer = peer self.isVideo = isVideo + self.isConference = isConference } } @@ -2160,7 +2163,7 @@ public final class AccountStateManager { switch update { case let .updatePhoneCall(phoneCall): switch phoneCall { - case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, _, _): + case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, _, _, conferenceCall): guard let peer = peers.first(where: { $0.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)) }) else { return nil } @@ -2169,7 +2172,8 @@ public final class AccountStateManager { callAccessHash: accessHash, timestamp: date, peer: EnginePeer(peer), - isVideo: (flags & (1 << 6)) != 0 + isVideo: (flags & (1 << 6)) != 0, + isConference: conferenceCall != nil ) default: break diff --git a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift index 6246791dfb..0fe4702964 100644 --- a/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift +++ b/submodules/TelegramCore/Sources/State/ApplyUpdateMessage.swift @@ -104,7 +104,7 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes var updatedTimestamp: Int32? if let apiMessage = apiMessage { switch apiMessage { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): updatedTimestamp = date case .messageEmpty: break @@ -400,7 +400,7 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage } else if let message = messages.first, let apiMessage = result.messages.first { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { namespace = Namespaces.Message.ScheduledCloud - } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { + } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud } } diff --git a/submodules/TelegramCore/Sources/State/CallSessionManager.swift b/submodules/TelegramCore/Sources/State/CallSessionManager.swift index 80aa426de0..65fe389de6 100644 --- a/submodules/TelegramCore/Sources/State/CallSessionManager.swift +++ b/submodules/TelegramCore/Sources/State/CallSessionManager.swift @@ -73,32 +73,35 @@ extension GroupCallReference { } enum CallSessionInternalState { - case ringing(id: Int64, accessHash: Int64, gAHash: Data, b: Data, versions: [String]) - case accepting(id: Int64, accessHash: Int64, gAHash: Data, b: Data, disposable: Disposable) + case ringing(id: Int64, accessHash: Int64, gAHash: Data, b: Data, versions: [String], conferenceCall: GroupCallReference?) + case accepting(id: Int64, accessHash: Int64, gAHash: Data, b: Data, conferenceCall: GroupCallReference?, disposable: Disposable) case awaitingConfirmation(id: Int64, accessHash: Int64, gAHash: Data, b: Data, config: SecretChatEncryptionConfig) - case requesting(a: Data, disposable: Disposable) - case requested(id: Int64, accessHash: Int64, a: Data, gA: Data, config: SecretChatEncryptionConfig, remoteConfirmationTimestamp: Int32?) - case confirming(id: Int64, accessHash: Int64, key: Data, keyId: Int64, keyVisualHash: Data, disposable: Disposable) + case requesting(a: Data, conferenceCall: GroupCallReference?, disposable: Disposable) + case requested(id: Int64, accessHash: Int64, a: Data, gA: Data, config: SecretChatEncryptionConfig, remoteConfirmationTimestamp: Int32?, conferenceCall: GroupCallReference?) + case confirming(id: Int64, accessHash: Int64, key: Data, keyId: Int64, keyVisualHash: Data, conferenceCall: GroupCallReference?, disposable: Disposable) case active(id: Int64, accessHash: Int64, beginTimestamp: Int32, key: Data, keyId: Int64, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?) + case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference) case dropping(reason: CallSessionTerminationReason, disposable: Disposable) case terminated(id: Int64?, accessHash: Int64?, reason: CallSessionTerminationReason, reportRating: Bool, sendDebugLogs: Bool) var stableId: Int64? { switch self { - case let .ringing(id, _, _, _, _): + case let .ringing(id, _, _, _, _, _): return id - case let .accepting(id, _, _, _, _): + case let .accepting(id, _, _, _, _, _): return id case let .awaitingConfirmation(id, _, _, _, _): return id case .requesting: return nil - case let .requested(id, _, _, _, _, _): + case let .requested(id, _, _, _, _, _, _): return id - case let .confirming(id, _, _, _, _, _): + case let .confirming(id, _, _, _, _, _, _): return id case let .active(id, _, _, _, _, _, _, _, _, _, _, _): return id + case .switchedToConference: + return nil case .dropping: return nil case let .terminated(id, _, _, _, _): @@ -161,8 +164,9 @@ public struct CallTerminationOptions: OptionSet { public enum CallSessionState { case ringing case accepting - case requesting(ringing: Bool) + case requesting(ringing: Bool, conferenceCall: GroupCallReference?) case active(id: CallId, key: Data, keyVisualHash: Data, connections: CallSessionConnectionSet, maxLayer: Int32, version: String, customParameters: String?, allowsP2P: Bool, conferenceCall: GroupCallReference?) + case switchedToConference(key: Data, keyVisualHash: Data, conferenceCall: GroupCallReference) case dropping(reason: CallSessionTerminationReason) case terminated(id: CallId?, reason: CallSessionTerminationReason, options: CallTerminationOptions) @@ -172,12 +176,12 @@ public enum CallSessionState { self = .ringing case .accepting, .awaitingConfirmation: self = .accepting - case .requesting: - self = .requesting(ringing: false) - case .confirming: - self = .requesting(ringing: true) - case let .requested(_, _, _, _, _, remoteConfirmationTimestamp): - self = .requesting(ringing: remoteConfirmationTimestamp != nil) + case let .requesting(_, conferenceCall, _): + self = .requesting(ringing: false, conferenceCall: conferenceCall) + case let .confirming(_, _, _, _, _, conferenceCall, _): + self = .requesting(ringing: true, conferenceCall: conferenceCall) + case let .requested(_, _, _, _, _, remoteConfirmationTimestamp, conferenceCall): + self = .requesting(ringing: remoteConfirmationTimestamp != nil, conferenceCall: conferenceCall) case let .active(id, accessHash, _, key, _, keyVisualHash, connections, maxLayer, version, customParameters, allowsP2P, conferenceCall): self = .active(id: CallId(id: id, accessHash: accessHash), key: key, keyVisualHash: keyVisualHash, connections: connections, maxLayer: maxLayer, version: version, customParameters: customParameters, allowsP2P: allowsP2P, conferenceCall: conferenceCall) case let .dropping(reason, _): @@ -197,6 +201,8 @@ public enum CallSessionState { callId = nil } self = .terminated(id: callId, reason: reason, options: options) + case let .switchedToConference(key, keyVisualHash, conferenceCall): + self = .switchedToConference(key: key, keyVisualHash: keyVisualHash, conferenceCall: conferenceCall) } } } @@ -330,6 +336,7 @@ private final class CallSessionContext { let isOutgoing: Bool var type: CallSession.CallType var isVideoPossible: Bool + let pendingConference: (conference: GroupCallReference, encryptionKey: Data)? var state: CallSessionInternalState let subscribers = Bag<(CallSession) -> Void>() var signalingReceiver: (([Data]) -> Void)? @@ -346,11 +353,12 @@ private final class CallSessionContext { } } - init(peerId: PeerId, isOutgoing: Bool, type: CallSession.CallType, isVideoPossible: Bool, state: CallSessionInternalState) { + init(peerId: PeerId, isOutgoing: Bool, type: CallSession.CallType, isVideoPossible: Bool, pendingConference: (conference: GroupCallReference, encryptionKey: Data)?, state: CallSessionInternalState) { self.peerId = peerId self.isOutgoing = isOutgoing self.type = type self.isVideoPossible = isVideoPossible + self.pendingConference = pendingConference self.state = state } @@ -566,7 +574,7 @@ private final class CallSessionManagerContext { } } - private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data, versions: [String], isVideo: Bool) -> CallSessionInternalId? { + private func addIncoming(peerId: PeerId, stableId: CallSessionStableId, accessHash: Int64, timestamp: Int32, gAHash: Data, versions: [String], isVideo: Bool, conferenceCall: GroupCallReference?) -> CallSessionInternalId? { if self.contextIdByStableId[stableId] != nil { return nil } @@ -582,7 +590,7 @@ private final class CallSessionManagerContext { //#endif let internalId = CallSessionManager.getStableIncomingUUID(stableId: stableId) - let context = CallSessionContext(peerId: peerId, isOutgoing: false, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions)) + let context = CallSessionContext(peerId: peerId, isOutgoing: false, type: isVideo ? .video : .audio, isVideoPossible: isVideoPossible, pendingConference: nil, state: .ringing(id: stableId, accessHash: accessHash, gAHash: gAHash, b: b, versions: versions, conferenceCall: conferenceCall)) self.contexts[internalId] = context let queue = self.queue @@ -619,7 +627,7 @@ private final class CallSessionManagerContext { var wasRinging = false let isVideo = context.type == .video switch context.state { - case let .ringing(id, accessHash, _, _, _): + case let .ringing(id, accessHash, _, _, _, _): wasRinging = true let internalReason: DropCallSessionReason switch reason { @@ -633,7 +641,7 @@ private final class CallSessionManagerContext { internalReason = .missed } dropData = (id, accessHash, internalReason) - case let .accepting(id, accessHash, _, _, disposable): + case let .accepting(id, accessHash, _, _, _, disposable): dropData = (id, accessHash, .abort) disposable.dispose() case let .active(id, accessHash, beginTimestamp, _, _, _, _, _, _, _, _, _): @@ -648,14 +656,16 @@ private final class CallSessionManagerContext { internalReason = .missed } dropData = (id, accessHash, internalReason) + case .switchedToConference: + break case .dropping, .terminated: break case let .awaitingConfirmation(id, accessHash, _, _, _): dropData = (id, accessHash, .abort) - case let .confirming(id, accessHash, _, _, _, disposable): + case let .confirming(id, accessHash, _, _, _, _, disposable): disposable.dispose() dropData = (id, accessHash, .abort) - case let .requested(id, accessHash, _, _, _, _): + case let .requested(id, accessHash, _, _, _, _, _): let internalReason: DropCallSessionReason switch reason { case .busy, .hangUp: @@ -666,7 +676,7 @@ private final class CallSessionManagerContext { internalReason = .missed } dropData = (id, accessHash, internalReason) - case let .requesting(_, disposable): + case let .requesting(_, _, disposable): disposable.dispose() context.state = .terminated(id: nil, accessHash: nil, reason: .ended(.hungUp), reportRating: false, sendDebugLogs: false) self.contextUpdated(internalId: internalId) @@ -689,6 +699,8 @@ private final class CallSessionManagerContext { mappedReason = .ended(.hungUp) case .missed: mappedReason = .ended(.missed) + case .switchToConference: + mappedReason = .ended(.hungUp) } context.state = .dropping(reason: mappedReason, disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: reason) |> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in @@ -729,6 +741,38 @@ private final class CallSessionManagerContext { } } + func dropToConference(internalId: CallSessionInternalId, encryptedGroupKey: Data) { + if let context = self.contexts[internalId] { + var dropData: (CallSessionStableId, Int64)? + let isVideo = context.type == .video + switch context.state { + case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _): + dropData = (id, accessHash) + default: + break + } + + if let (id, accessHash) = dropData { + self.contextIdByStableId.removeValue(forKey: id) + context.state = .dropping(reason: .ended(.hungUp), disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: .switchToConference(encryptedGroupKey: encryptedGroupKey)) + |> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in + if let strongSelf = self { + if let context = strongSelf.contexts[internalId] { + context.state = .terminated(id: id, accessHash: accessHash, reason: .ended(.hungUp), reportRating: reportRating, sendDebugLogs: sendDebugLogs) + strongSelf.contextUpdated(internalId: internalId) + if context.isEmpty { + strongSelf.contexts.removeValue(forKey: internalId) + } + } + } + })) + self.contextUpdated(internalId: internalId) + } + } else { + self.contextUpdated(internalId: internalId) + } + } + func dropAll() { let contexts = self.contexts for (internalId, _) in contexts { @@ -739,9 +783,9 @@ private final class CallSessionManagerContext { func accept(internalId: CallSessionInternalId) { if let context = self.contexts[internalId] { switch context.state { - case let .ringing(id, accessHash, gAHash, b, _): + case let .ringing(id, accessHash, gAHash, b, _, conferenceCall): let acceptVersions = self.versions.map({ $0.version }) - context.state = .accepting(id: id, accessHash: accessHash, gAHash: gAHash, b: b, disposable: (acceptCallSession(accountPeerId: self.accountPeerId, postbox: self.postbox, network: self.network, stableId: id, accessHash: accessHash, b: b, maxLayer: self.maxLayer, versions: acceptVersions) |> deliverOn(self.queue)).start(next: { [weak self] result in + context.state = .accepting(id: id, accessHash: accessHash, gAHash: gAHash, b: b, conferenceCall: conferenceCall, disposable: (acceptCallSession(accountPeerId: self.accountPeerId, postbox: self.postbox, network: self.network, stableId: id, accessHash: accessHash, b: b, maxLayer: self.maxLayer, versions: acceptVersions) |> deliverOn(self.queue)).start(next: { [weak self] result in if let strongSelf = self, let context = strongSelf.contexts[internalId] { if case .accepting = context.state { switch result { @@ -835,7 +879,7 @@ private final class CallSessionManagerContext { switch call { case .phoneCallEmpty: break - case let .phoneCallAccepted(_, id, _, _, _, _, gB, remoteProtocol): + case let .phoneCallAccepted(_, id, _, _, _, _, gB, remoteProtocol, conferenceCall): let remoteVersions: [String] switch remoteProtocol { case let .phoneCallProtocol(_, _, _, versions): @@ -849,7 +893,7 @@ private final class CallSessionManagerContext { if let context = self.contexts[internalId] { switch context.state { - case let .requested(_, accessHash, a, gA, config, _): + case let .requested(_, accessHash, a, gA, config, _, _): let p = config.p.makeData() if !MTCheckIsSafeGAOrB(self.network.encryptionProvider, gA, p) { self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) @@ -874,10 +918,14 @@ private final class CallSessionManagerContext { let keyVisualHash = MTSha256(key + gA) - context.state = .confirming(id: id, accessHash: accessHash, key: key, keyId: keyId, keyVisualHash: keyVisualHash, disposable: (confirmCallSession(network: self.network, stableId: id, accessHash: accessHash, gA: gA, keyFingerprint: keyId, maxLayer: self.maxLayer, versions: selectedVersions) |> deliverOnMainQueue).start(next: { [weak self] updatedCall in + context.state = .confirming(id: id, accessHash: accessHash, key: key, keyId: keyId, keyVisualHash: keyVisualHash, conferenceCall: conferenceCall.flatMap(GroupCallReference.init), disposable: (confirmCallSession(network: self.network, stableId: id, accessHash: accessHash, gA: gA, keyFingerprint: keyId, maxLayer: self.maxLayer, versions: selectedVersions) |> deliverOnMainQueue).start(next: { [weak self] updatedCall in if let strongSelf = self, let context = strongSelf.contexts[internalId], case .confirming = context.state { if let updatedCall = updatedCall { strongSelf.updateSession(updatedCall, completion: { _ in }) + + if let pendingConference = context.pendingConference { + strongSelf.dropToConference(internalId: internalId, encryptedGroupKey: pendingConference.encryptionKey) + } } else { strongSelf.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) } @@ -891,7 +939,8 @@ private final class CallSessionManagerContext { assertionFailure() } } - case let .phoneCallDiscarded(flags, id, reason, _): + case let .phoneCallDiscarded(flags, id, reason, _, conferenceCall): + let _ = conferenceCall let reportRating = (flags & (1 << 2)) != 0 let sendDebugLogs = (flags & (1 << 3)) != 0 if let internalId = self.contextIdByStableId[id] { @@ -899,47 +948,53 @@ private final class CallSessionManagerContext { let parsedReason: CallSessionTerminationReason if let reason = reason { switch reason { - case .phoneCallDiscardReasonBusy: - parsedReason = .ended(.busy) - case .phoneCallDiscardReasonDisconnect: - parsedReason = .error(.disconnected) - case .phoneCallDiscardReasonHangup: - parsedReason = .ended(.hungUp) - case .phoneCallDiscardReasonMissed: - parsedReason = .ended(.missed) + case .phoneCallDiscardReasonBusy: + parsedReason = .ended(.busy) + case .phoneCallDiscardReasonDisconnect: + parsedReason = .error(.disconnected) + case .phoneCallDiscardReasonHangup: + parsedReason = .ended(.hungUp) + case .phoneCallDiscardReasonMissed: + parsedReason = .ended(.missed) + case .phoneCallDiscardReasonAllowGroupCall: + parsedReason = .ended(.hungUp) } } else { parsedReason = .ended(.hungUp) } switch context.state { - case let .accepting(id, accessHash, _, _, disposable): - disposable.dispose() + case let .accepting(id, accessHash, _, _, _, disposable): + disposable.dispose() + context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) + self.contextUpdated(internalId: internalId) + case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, conferenceCall): + if let conferenceCall, case let .phoneCallDiscardReasonAllowGroupCall(encryptedGroupKey) = reason { + context.state = .switchedToConference(key: encryptedGroupKey.makeData(), keyVisualHash: MTSha256(encryptedGroupKey.makeData()), conferenceCall: conferenceCall) + } else { context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) - self.contextUpdated(internalId: internalId) - case let .active(id, accessHash, _, _, _, _, _, _, _, _, _, _): - context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) - self.contextUpdated(internalId: internalId) - case let .awaitingConfirmation(id, accessHash, _, _, _): - context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) - self.contextUpdated(internalId: internalId) - case let .requested(id, accessHash, _, _, _, _): - context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) - self.contextUpdated(internalId: internalId) - case let .confirming(id, accessHash, _, _, _, disposable): - disposable.dispose() - context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) - self.contextUpdated(internalId: internalId) - case let .requesting(_, disposable): - disposable.dispose() - context.state = .terminated(id: nil, accessHash: nil, reason: parsedReason, reportRating: false, sendDebugLogs: false) - self.contextUpdated(internalId: internalId) - case let .ringing(id, accessHash, _, _, _): - context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) - self.ringingStatesUpdated() - self.contextUpdated(internalId: internalId) - case .dropping, .terminated: - break + } + self.contextUpdated(internalId: internalId) + case let .awaitingConfirmation(id, accessHash, _, _, _): + context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) + self.contextUpdated(internalId: internalId) + case let .requested(id, accessHash, _, _, _, _, _): + context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) + self.contextUpdated(internalId: internalId) + case let .confirming(id, accessHash, _, _, _, _, disposable): + disposable.dispose() + context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) + self.contextUpdated(internalId: internalId) + case let .requesting(_, _, disposable): + disposable.dispose() + context.state = .terminated(id: nil, accessHash: nil, reason: parsedReason, reportRating: false, sendDebugLogs: false) + self.contextUpdated(internalId: internalId) + case let .ringing(id, accessHash, _, _, _, _): + context.state = .terminated(id: id, accessHash: accessHash, reason: parsedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs) + self.ringingStatesUpdated() + self.contextUpdated(internalId: internalId) + case .dropping, .terminated, .switchedToConference: + break } } else { //assertionFailure() @@ -950,7 +1005,7 @@ private final class CallSessionManagerContext { if let internalId = self.contextIdByStableId[id] { if let context = self.contexts[internalId] { switch context.state { - case .accepting, .active, .dropping, .requesting, .ringing, .terminated, .requested: + case .accepting, .active, .dropping, .requesting, .ringing, .terminated, .requested, .switchedToConference: break case let .awaitingConfirmation(_, accessHash, gAHash, b, config): if let (key, calculatedKeyId, keyVisualHash) = self.makeSessionEncryptionKey(config: config, gAHash: gAHash, b: b, gA: gAOrB.makeData()) { @@ -981,7 +1036,7 @@ private final class CallSessionManagerContext { } else { self.drop(internalId: internalId, reason: .disconnect, debugLog: .single(nil)) } - case let .confirming(id, accessHash, key, keyId, keyVisualHash, _): + case let .confirming(id, accessHash, key, keyId, keyVisualHash, _, _): switch callProtocol { case let .phoneCallProtocol(_, _, maxLayer, versions): if !versions.isEmpty { @@ -1007,7 +1062,7 @@ private final class CallSessionManagerContext { assertionFailure() } } - case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, requestedProtocol): + case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, gAHash, requestedProtocol, conferenceCall): let isVideo = (flags & (1 << 6)) != 0 let versions: [String] switch requestedProtocol { @@ -1015,7 +1070,7 @@ private final class CallSessionManagerContext { versions = libraryVersions } if self.contextIdByStableId[id] == nil { - let internalId = self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData(), versions: versions, isVideo: isVideo) + let internalId = self.addIncoming(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)), stableId: id, accessHash: accessHash, timestamp: date, gAHash: gAHash.makeData(), versions: versions, isVideo: isVideo, conferenceCall: conferenceCall.flatMap(GroupCallReference.init)) if let internalId = internalId { var resultRingingStateValue: CallSessionRingingState? for ringingState in self.ringingStatesValue() { @@ -1032,13 +1087,13 @@ private final class CallSessionManagerContext { } } } - case let .phoneCallWaiting(_, id, _, _, _, _, _, receiveDate): + case let .phoneCallWaiting(_, id, _, _, _, _, _, receiveDate, conferenceCall): if let internalId = self.contextIdByStableId[id] { if let context = self.contexts[internalId] { switch context.state { - case let .requested(id, accessHash, a, gA, config, remoteConfirmationTimestamp): + case let .requested(id, accessHash, a, gA, config, remoteConfirmationTimestamp, _): if let receiveDate = receiveDate, remoteConfirmationTimestamp == nil { - context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: receiveDate) + context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: receiveDate, conferenceCall: conferenceCall.flatMap(GroupCallReference.init)) self.contextUpdated(internalId: internalId) } default: @@ -1101,17 +1156,17 @@ private final class CallSessionManagerContext { return (key, keyId, keyVisualHash) } - func request(peerId: PeerId, internalId: CallSessionInternalId, isVideo: Bool, enableVideo: Bool) -> CallSessionInternalId? { + func request(peerId: PeerId, internalId: CallSessionInternalId, isVideo: Bool, enableVideo: Bool, conferenceCall: (conference: GroupCallReference, encryptionKey: Data)?) -> CallSessionInternalId? { let aBytes = malloc(256)! let randomStatus = SecRandomCopyBytes(nil, 256, aBytes.assumingMemoryBound(to: UInt8.self)) let a = Data(bytesNoCopy: aBytes, count: 256, deallocator: .free) if randomStatus == 0 { - self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, type: isVideo ? .video : .audio, isVideoPossible: enableVideo || isVideo, state: .requesting(a: a, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.filteredVersions(enableVideo: true), isVideo: isVideo) |> deliverOn(queue)).start(next: { [weak self] result in + self.contexts[internalId] = CallSessionContext(peerId: peerId, isOutgoing: true, type: isVideo ? .video : .audio, isVideoPossible: enableVideo || isVideo, pendingConference: conferenceCall, state: .requesting(a: a, conferenceCall: conferenceCall?.conference, disposable: (requestCallSession(postbox: self.postbox, network: self.network, peerId: peerId, a: a, maxLayer: self.maxLayer, versions: self.filteredVersions(enableVideo: true), isVideo: isVideo, conferenceCall: conferenceCall?.conference) |> deliverOn(queue)).start(next: { [weak self] result in if let strongSelf = self, let context = strongSelf.contexts[internalId] { if case .requesting = context.state { switch result { case let .success(id, accessHash, config, gA, remoteConfirmationTimestamp): - context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: remoteConfirmationTimestamp) + context.state = .requested(id: id, accessHash: accessHash, a: a, gA: gA, config: config, remoteConfirmationTimestamp: remoteConfirmationTimestamp, conferenceCall: conferenceCall?.conference) strongSelf.contextIdByStableId[id] = internalId strongSelf.contextUpdated(internalId: internalId) strongSelf.deliverCallSignalingData(id: id) @@ -1206,12 +1261,12 @@ public final class CallSessionManager { } } - public func request(peerId: PeerId, isVideo: Bool, enableVideo: Bool, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal { + public func request(peerId: PeerId, isVideo: Bool, enableVideo: Bool, conferenceCall: (conference: GroupCallReference, encryptionKey: Data)?, internalId: CallSessionInternalId = CallSessionInternalId()) -> Signal { return Signal { [weak self] subscriber in let disposable = MetaDisposable() self?.withContext { context in - if let internalId = context.request(peerId: peerId, internalId: internalId, isVideo: isVideo, enableVideo: enableVideo) { + if let internalId = context.request(peerId: peerId, internalId: internalId, isVideo: isVideo, enableVideo: enableVideo, conferenceCall: conferenceCall) { subscriber.putNext(internalId) subscriber.putCompletion() } @@ -1358,7 +1413,7 @@ private enum RequestCallSessionResult { case failed(CallSessionError) } -private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data, maxLayer: Int32, versions: [String], isVideo: Bool) -> Signal { +private func requestCallSession(postbox: Postbox, network: Network, peerId: PeerId, a: Data, maxLayer: Int32, versions: [String], isVideo: Bool, conferenceCall: GroupCallReference?) -> Signal { return validatedEncryptionConfig(postbox: postbox, network: network) |> mapToSignal { config -> Signal in return postbox.transaction { transaction -> Signal in @@ -1378,15 +1433,18 @@ private func requestCallSession(postbox: Postbox, network: Network, peerId: Peer if isVideo { callFlags |= 1 << 0 } + if conferenceCall != nil { + callFlags |= 1 << 1 + } - return network.request(Api.functions.phone.requestCall(flags: callFlags, userId: inputUser, randomId: Int32(bitPattern: arc4random()), gAHash: Buffer(data: gAHash), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer, libraryVersions: versions))) + return network.request(Api.functions.phone.requestCall(flags: callFlags, userId: inputUser, conferenceCall: conferenceCall.flatMap { Api.InputGroupCall.inputGroupCall(id: $0.id, accessHash: $0.accessHash) }, randomId: Int32(bitPattern: arc4random()), gAHash: Buffer(data: gAHash), protocol: .phoneCallProtocol(flags: (1 << 0) | (1 << 1), minLayer: minLayer, maxLayer: maxLayer, libraryVersions: versions))) |> map { result -> RequestCallSessionResult in switch result { case let .phoneCall(phoneCall, _): switch phoneCall { - case let .phoneCallRequested(_, id, accessHash, _, _, _, _, _): + case let .phoneCallRequested(_, id, accessHash, _, _, _, _, _, _): return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: nil) - case let .phoneCallWaiting(_, id, accessHash, _, _, _, _, receiveDate): + case let .phoneCallWaiting(_, id, accessHash, _, _, _, _, receiveDate, _): return .success(id: id, accessHash: accessHash, config: config, gA: ga, remoteConfirmationTimestamp: receiveDate) default: return .failed(.generic) @@ -1439,23 +1497,26 @@ private enum DropCallSessionReason { case busy case disconnect case missed + case switchToConference(encryptedGroupKey: Data) } private func dropCallSession(network: Network, addUpdates: @escaping (Api.Updates) -> Void, stableId: CallSessionStableId, accessHash: Int64, isVideo: Bool, reason: DropCallSessionReason) -> Signal<(Bool, Bool), NoError> { var mappedReason: Api.PhoneCallDiscardReason var duration: Int32 = 0 switch reason { - case .abort: - mappedReason = .phoneCallDiscardReasonHangup - case let .hangUp(value): - duration = value - mappedReason = .phoneCallDiscardReasonHangup - case .busy: - mappedReason = .phoneCallDiscardReasonBusy - case .disconnect: - mappedReason = .phoneCallDiscardReasonDisconnect - case .missed: - mappedReason = .phoneCallDiscardReasonMissed + case .abort: + mappedReason = .phoneCallDiscardReasonHangup + case let .hangUp(value): + duration = value + mappedReason = .phoneCallDiscardReasonHangup + case .busy: + mappedReason = .phoneCallDiscardReasonBusy + case .disconnect: + mappedReason = .phoneCallDiscardReasonDisconnect + case .missed: + mappedReason = .phoneCallDiscardReasonMissed + case let .switchToConference(encryptedGroupKey): + mappedReason = .phoneCallDiscardReasonAllowGroupCall(encryptedKey: Buffer(data: encryptedGroupKey)) } var callFlags: Int32 = 0 @@ -1478,7 +1539,7 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update switch update { case .updatePhoneCall(let phoneCall): switch phoneCall { - case let .phoneCallDiscarded(flags, _, _, _): + case let .phoneCallDiscarded(flags, _, _, _, _): reportRating = (flags & (1 << 2)) != 0 sendDebugLogs = (flags & (1 << 3)) != 0 default: @@ -1501,33 +1562,7 @@ private func dropCallSession(network: Network, addUpdates: @escaping (Api.Update } private func createConferenceCall(postbox: Postbox, network: Network, accountPeerId: PeerId, callId: CallId) -> Signal { - #if DEBUG && false - if "".isEmpty { - return _internal_resolvePeerByName(postbox: postbox, network: network, accountPeerId: accountPeerId, name: "qwfqwfqwefqwef22", referrer: nil) - |> mapToSignal { result -> Signal in - switch result { - case .progress: - return .never() - case let .result(peerId): - return .single(peerId) - } - } - |> mapToSignal { peerId -> Signal in - guard let peerId else { - return .single(nil) - } - return _internal_updatedCurrentPeerGroupCall(postbox: postbox, network: network, accountPeerId: accountPeerId, peerId: peerId) - |> map { result -> GroupCallReference? in - guard let result else { - return nil - } - return GroupCallReference(id: result.id, accessHash: result.accessHash) - } - } - } - #endif - - return network.request(Api.functions.phone.createConferenceCall(peer: .inputPhoneCall(id: callId.id, accessHash: callId.accessHash))) + return network.request(Api.functions.phone.createConferenceCall(peer: .inputPhoneCall(id: callId.id, accessHash: callId.accessHash), keyFingerprint: 1)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index eec637f81f..ab1ad09559 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -1745,7 +1745,7 @@ public final class PendingMessageManager { if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { isScheduled = true } - if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage { + if case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage { if (flags2 & (1 << 4)) != 0 { isScheduled = true } @@ -1780,7 +1780,7 @@ public final class PendingMessageManager { namespace = Namespaces.Message.QuickReplyCloud } else if let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp { namespace = Namespaces.Message.ScheduledCloud - } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { + } else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud } } diff --git a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift index 9ece0b2395..a670df9508 100644 --- a/submodules/TelegramCore/Sources/State/UpdateMessageService.swift +++ b/submodules/TelegramCore/Sources/State/UpdateMessageService.swift @@ -58,7 +58,7 @@ class UpdateMessageService: NSObject, MTMessageService { self.putNext(groups) } case let .updateShortChatMessage(flags, id, fromId, chatId, message, pts, ptsCount, date, fwdFrom, viaBotId, replyHeader, entities, ttlPeriod): - let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: .peerUser(userId: fromId), fromBoostsApplied: nil, peerId: Api.Peer.peerChat(chatId: chatId), savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { @@ -74,7 +74,7 @@ class UpdateMessageService: NSObject, MTMessageService { let generatedPeerId = Api.Peer.peerUser(userId: userId) - let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil) + let generatedMessage = Api.Message.message(flags: flags, flags2: 0, id: id, fromId: generatedFromId, fromBoostsApplied: nil, peerId: generatedPeerId, savedPeerId: nil, fwdFrom: fwdFrom, viaBotId: viaBotId, viaBusinessBotId: nil, replyTo: replyHeader, date: date, message: message, media: Api.MessageMedia.messageMediaEmpty, replyMarkup: nil, entities: entities, views: nil, forwards: nil, replies: nil, editDate: nil, postAuthor: nil, groupedId: nil, reactions: nil, restrictionReason: nil, ttlPeriod: ttlPeriod, quickReplyShortcutId: nil, effect: nil, factcheck: nil, reportDeliveryUntilDate: nil) let update = Api.Update.updateNewMessage(message: generatedMessage, pts: pts, ptsCount: ptsCount) let groups = groupUpdates([update], users: [], chats: [], date: date, seqRange: nil) if groups.count != 0 { diff --git a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift index af8b1148d5..5e7805a44a 100644 --- a/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/State/UpdatesApiUtils.swift @@ -104,7 +104,7 @@ extension Api.MessageMedia { extension Api.Message { var rawId: Int32 { switch self { - case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, id, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return id case let .messageEmpty(_, id, _): return id @@ -115,7 +115,7 @@ extension Api.Message { func id(namespace: MessageId.Namespace = Namespaces.Message.Cloud) -> MessageId? { switch self { - case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, flags2, id, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): var namespace = namespace if (flags2 & (1 << 4)) != 0 { namespace = Namespaces.Message.ScheduledCloud @@ -136,7 +136,7 @@ extension Api.Message { var peerId: PeerId? { switch self { - case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, messagePeerId, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): let peerId: PeerId = messagePeerId.peerId return peerId case let .messageEmpty(_, _, peerId): @@ -149,7 +149,7 @@ extension Api.Message { var timestamp: Int32? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, date, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return date case let .messageService(_, _, _, _, _, date, _, _, _): return date @@ -160,7 +160,7 @@ extension Api.Message { var preCachedResources: [(MediaResource, Data)]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedResources default: return nil @@ -169,7 +169,7 @@ extension Api.Message { var preCachedStories: [StoryId: Api.StoryItem]? { switch self { - case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _): + case let .message(_, _, _, _, _, _, _, _, _, _, _, _, _, media, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _): return media?.preCachedStories default: return nil diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index edb2d6bef8..206838bbe9 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -435,7 +435,7 @@ public struct JoinGroupCallResult { public var jsonParams: String } -func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, callId: Int64, accessHash: Int64, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil) -> Signal { +func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, callId: Int64, accessHash: Int64, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, keyFingerprint: Int64?) -> Signal { return account.postbox.transaction { transaction -> Api.InputPeer? in if let joinAs = joinAs { return transaction.getPeer(joinAs).flatMap(apiInputPeer) @@ -457,8 +457,11 @@ func _internal_joinGroupCall(account: Account, peerId: PeerId?, joinAs: PeerId?, if let _ = inviteHash { flags |= (1 << 1) } - - let joinRequest = account.network.request(Api.functions.phone.joinGroupCall(flags: flags, call: .inputGroupCall(id: callId, accessHash: accessHash), joinAs: inputJoinAs, inviteHash: inviteHash, params: .dataJSON(data: joinPayload))) + if keyFingerprint != nil { + flags |= (1 << 3) + } + + let joinRequest = account.network.request(Api.functions.phone.joinGroupCall(flags: flags, call: .inputGroupCall(id: callId, accessHash: accessHash), joinAs: inputJoinAs, inviteHash: inviteHash, keyFingerprint: keyFingerprint, params: .dataJSON(data: joinPayload))) |> `catch` { error -> Signal in if error.errorDescription == "GROUPCALL_ANONYMOUS_FORBIDDEN" { return .fail(.anonymousNotAllowed) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift index 065476404d..150ab35df5 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift @@ -57,8 +57,8 @@ public extension TelegramEngine { return _internal_getGroupCallParticipants(account: self.account, callId: callId, accessHash: accessHash, offset: offset, ssrcs: ssrcs, limit: limit, sortAscending: sortAscending) } - public func joinGroupCall(peerId: PeerId?, joinAs: PeerId?, callId: Int64, accessHash: Int64, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil) -> Signal { - return _internal_joinGroupCall(account: self.account, peerId: peerId, joinAs: joinAs, callId: callId, accessHash: accessHash, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash) + public func joinGroupCall(peerId: PeerId?, joinAs: PeerId?, callId: Int64, accessHash: Int64, preferMuted: Bool, joinPayload: String, peerAdminIds: Signal<[PeerId], NoError>, inviteHash: String? = nil, keyFingerprint: Int64?) -> Signal { + return _internal_joinGroupCall(account: self.account, peerId: peerId, joinAs: joinAs, callId: callId, accessHash: accessHash, preferMuted: preferMuted, joinPayload: joinPayload, peerAdminIds: peerAdminIds, inviteHash: inviteHash, keyFingerprint: keyFingerprint) } public func joinGroupCallAsScreencast(callId: Int64, accessHash: Int64, joinPayload: String) -> Signal { diff --git a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift index d38cc2e68d..8073195d30 100644 --- a/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift +++ b/submodules/TelegramUI/Components/Calls/CallScreen/Sources/PrivateCallScreen.swift @@ -226,6 +226,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu public var backAction: (() -> Void)? public var closeAction: (() -> Void)? public var restoreUIForPictureInPicture: ((@escaping (Bool) -> Void) -> Void)? + public var conferenceAddParticipant: (() -> Void)? private let pipView: PrivateCallPictureInPictureView private var pipContentSource: AnyObject? diff --git a/submodules/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 965c46f324..ab50f4e095 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 965c46f32425cb270e88ab0aab7c3593b5be574e +Subproject commit ab50f4e095d5793c39dc54b740a982fc3ba27ea5